mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Albums: Remove photo from review when adding it to an album #4229
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -384,22 +384,22 @@ func CloneAlbums(router *gin.RouterGroup) {
|
||||
|
||||
var added []entity.PhotoAlbum
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
cloneAlbum, err := query.AlbumByUID(uid)
|
||||
for _, albumUid := range f.Albums {
|
||||
cloneAlbum, queryErr := query.AlbumByUID(albumUid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
if queryErr != nil {
|
||||
log.Errorf("album: %s", queryErr)
|
||||
continue
|
||||
}
|
||||
|
||||
photos, err := search.AlbumPhotos(cloneAlbum, 10000, false)
|
||||
photos, queryErr := search.AlbumPhotos(cloneAlbum, 100000, false)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
if queryErr != nil {
|
||||
log.Errorf("album: %s", queryErr)
|
||||
continue
|
||||
}
|
||||
|
||||
added = append(added, a.AddPhotos(photos.UIDs())...)
|
||||
added = append(added, a.AddPhotos(photos)...)
|
||||
}
|
||||
|
||||
if len(added) > 0 {
|
||||
@@ -466,7 +466,9 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
added := a.AddPhotos(photos.UIDs())
|
||||
conf := get.Config()
|
||||
|
||||
added := a.AddPhotos(photos)
|
||||
|
||||
if len(added) > 0 {
|
||||
if len(added) == 1 {
|
||||
@@ -481,6 +483,34 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
||||
// Auto-approve photos that have been added to an album,
|
||||
// see https://github.com/photoprism/photoprism/issues/4229
|
||||
if conf.Settings().Features.Review {
|
||||
var approved entity.Photos
|
||||
|
||||
for _, p := range photos {
|
||||
// Skip photos that are not in review.
|
||||
if p.Approved() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Approve photo and update YAML backup file.
|
||||
if err = p.Approve(); err != nil {
|
||||
log.Errorf("approve: %s", err)
|
||||
} else {
|
||||
approved = append(approved, p)
|
||||
SavePhotoAsYaml(&p)
|
||||
}
|
||||
}
|
||||
|
||||
// Update client UI and counts if photos has been approved.
|
||||
if len(approved) > 0 {
|
||||
UpdateClientConfig()
|
||||
|
||||
event.EntitiesUpdated("photos", approved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": photos.UIDs(), "added": added})
|
||||
|
||||
@@ -57,10 +57,10 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
for _, p := range photos {
|
||||
if err := p.Archive(); err != nil {
|
||||
log.Errorf("archive: %s", err)
|
||||
if archiveErr := p.Archive(); archiveErr != nil {
|
||||
log.Errorf("archive: %s", archiveErr)
|
||||
} else {
|
||||
SavePhotoAsYaml(p)
|
||||
SavePhotoAsYaml(&p)
|
||||
}
|
||||
}
|
||||
} else if err := entity.Db().Where("photo_uid IN (?)", f.Photos).Delete(&entity.Photo{}).Error; err != nil {
|
||||
@@ -120,10 +120,10 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
for _, p := range photos {
|
||||
if err := p.Restore(); err != nil {
|
||||
if err = p.Restore(); err != nil {
|
||||
log.Errorf("restore: %s", err)
|
||||
} else {
|
||||
SavePhotoAsYaml(p)
|
||||
SavePhotoAsYaml(&p)
|
||||
}
|
||||
}
|
||||
} else if err := entity.Db().Unscoped().Model(&entity.Photo{}).Where("photo_uid IN (?)", f.Photos).
|
||||
@@ -187,7 +187,7 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
|
||||
log.Errorf("approve: %s", err)
|
||||
} else {
|
||||
approved = append(approved, p)
|
||||
SavePhotoAsYaml(p)
|
||||
SavePhotoAsYaml(&p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
|
||||
// Fetch selection from index.
|
||||
if photos, err := query.SelectedPhotos(f); err == nil {
|
||||
for _, p := range photos {
|
||||
SavePhotoAsYaml(p)
|
||||
SavePhotoAsYaml(&p)
|
||||
}
|
||||
|
||||
event.EntitiesUpdated("photos", photos)
|
||||
@@ -405,12 +405,12 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
||||
event.AuditWarn([]string{ClientIP(c), s.UserName, "delete", path.Join(p.PhotoPath, p.PhotoName+"*")})
|
||||
|
||||
// Remove all related files from storage.
|
||||
n, err := photoprism.DeletePhoto(p, true, true)
|
||||
n, deleteErr := photoprism.DeletePhoto(&p, true, true)
|
||||
|
||||
numFiles += n
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("delete: %s", err)
|
||||
if deleteErr != nil {
|
||||
log.Errorf("delete: %s", deleteErr)
|
||||
} else {
|
||||
deleted = append(deleted, p)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,12 @@ import (
|
||||
)
|
||||
|
||||
// SavePhotoAsYaml saves photo data as YAML file.
|
||||
func SavePhotoAsYaml(p entity.Photo) {
|
||||
func SavePhotoAsYaml(p *entity.Photo) {
|
||||
if p == nil {
|
||||
log.Debugf("api: photo is nil (update yaml)")
|
||||
return
|
||||
}
|
||||
|
||||
c := get.Config()
|
||||
|
||||
// Write YAML sidecar file (optional).
|
||||
@@ -114,7 +119,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
SavePhotoAsYaml(p)
|
||||
SavePhotoAsYaml(&p)
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
@@ -225,7 +230,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
SavePhotoAsYaml(m)
|
||||
SavePhotoAsYaml(&m)
|
||||
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ func LikePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
SavePhotoAsYaml(m)
|
||||
SavePhotoAsYaml(&m)
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func DislikePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
SavePhotoAsYaml(m)
|
||||
SavePhotoAsYaml(&m)
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ func CreateUserPasscode(router *gin.RouterGroup) {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeGenerateFailed.Error(), clean.Error(err)}, s.RefID)
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
||||
return
|
||||
} else if passcode, err = entity.NewPasscode(user.UID(), key.String(), rnd.RecoveryCode()); err != nil {
|
||||
} else if passcode, err = entity.NewPasscode(user.GetUID(), key.String(), rnd.RecoveryCode()); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeCreateFailed.Error(), clean.Error(err)}, s.RefID)
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
||||
return
|
||||
|
||||
@@ -93,7 +93,7 @@ func UpdateUserPassword(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Update tokens if user matches with session.
|
||||
if s.User().UserUID == u.UID() {
|
||||
if s.User().UserUID == u.GetUID() {
|
||||
s.SetPreviewToken(u.PreviewToken)
|
||||
s.SetDownloadToken(u.DownloadToken)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func wsWriter(ws *websocket.Conn, writeMutex *sync.Mutex, connId string) {
|
||||
}
|
||||
case 4:
|
||||
ev = strings.Join(ch[2:4], ".")
|
||||
if acl.ChannelUser.Equal(ch[0]) && ch[1] == user.UID() || acl.Events.AllowAll(acl.Resource(ch[2]), user.AclRole(), wsSubscribePerms) {
|
||||
if acl.ChannelUser.Equal(ch[0]) && ch[1] == user.GetUID() || acl.Events.AllowAll(acl.Resource(ch[2]), user.AclRole(), wsSubscribePerms) {
|
||||
// Send to matching user uid.
|
||||
wsSendMessage(ev, msg.Fields, ws, writeMutex)
|
||||
} else if acl.ChannelSession.Equal(ch[0]) && ch[1] == sid {
|
||||
|
||||
@@ -100,7 +100,7 @@ func clientsAddAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
rows[0] = []string{
|
||||
client.UID(),
|
||||
client.GetUID(),
|
||||
client.Name(),
|
||||
client.AuthInfo(),
|
||||
client.UserInfo(),
|
||||
|
||||
@@ -59,7 +59,7 @@ func clientsListAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
rows[i] = []string{
|
||||
client.UID(),
|
||||
client.GetUID(),
|
||||
client.Name(),
|
||||
client.AuthInfo(),
|
||||
client.UserInfo(),
|
||||
|
||||
@@ -51,12 +51,12 @@ func clientsRemoveAction(ctx *cli.Context) error {
|
||||
|
||||
if !ctx.Bool("force") {
|
||||
actionPrompt := promptui.Prompt{
|
||||
Label: fmt.Sprintf("Delete client %s?", m.UID()),
|
||||
Label: fmt.Sprintf("Delete client %s?", m.GetUID()),
|
||||
IsConfirm: true,
|
||||
}
|
||||
|
||||
if _, err := actionPrompt.Run(); err != nil {
|
||||
log.Infof("client %s was not deleted", m.UID())
|
||||
log.Infof("client %s was not deleted", m.GetUID())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func clientsRemoveAction(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("client %s has been deleted", m.UID())
|
||||
log.Infof("client %s has been deleted", m.GetUID())
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ func usersListAction(ctx *cli.Context) error {
|
||||
// Display report.
|
||||
for i, user := range users {
|
||||
rows[i] = []string{
|
||||
user.UID(),
|
||||
user.GetUID(),
|
||||
user.Username(),
|
||||
user.AclRole().Pretty(),
|
||||
user.AuthInfo(),
|
||||
|
||||
@@ -783,19 +783,21 @@ func (m *Album) ZipName() string {
|
||||
}
|
||||
|
||||
// AddPhotos adds photos to an existing album.
|
||||
func (m *Album) AddPhotos(UIDs []string) (added PhotoAlbums) {
|
||||
func (m *Album) AddPhotos(photos PhotosInterface) (added PhotoAlbums) {
|
||||
if !m.HasID() {
|
||||
return added
|
||||
}
|
||||
|
||||
// Add album entries.
|
||||
for _, uid := range UIDs {
|
||||
if !rnd.IsUID(uid, PhotoUID) {
|
||||
for _, photoUid := range photos.UIDs() {
|
||||
if !rnd.IsUID(photoUid, PhotoUID) {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: uid, Hidden: false}
|
||||
// Add photo to album.
|
||||
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: photoUid, Hidden: false}
|
||||
|
||||
// Save album entry.
|
||||
if err := entry.Save(); err != nil {
|
||||
log.Errorf("album: %s (add to album %s)", err.Error(), m)
|
||||
} else {
|
||||
|
||||
@@ -518,7 +518,12 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
||||
AlbumType: AlbumManual,
|
||||
AlbumTitle: "Test Title",
|
||||
}
|
||||
added := album.AddPhotos([]string{"ps6sg6be2lvl0yh7", "ps6sg6be2lvl0yh8"})
|
||||
|
||||
photo1 := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||
photo2 := PhotoFixtures.Get("Photo01")
|
||||
photos := Photos{photo1, photo2}
|
||||
|
||||
added := album.AddPhotos(photos)
|
||||
|
||||
var entries PhotoAlbums
|
||||
|
||||
@@ -532,7 +537,7 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
||||
}
|
||||
|
||||
if len(entries) < 2 {
|
||||
t.Error("at least one album entry expected")
|
||||
t.Fatal("at least one album entry expected")
|
||||
}
|
||||
|
||||
var a Album
|
||||
@@ -542,17 +547,17 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
first_photo_updatedAt := strings.Split(entries[0].UpdatedAt.String(), ".")[0]
|
||||
second_photo_updatedAt := strings.Split(entries[1].UpdatedAt.String(), ".")[0]
|
||||
album_updatedAt := strings.Split(a.UpdatedAt.String(), ".")[0]
|
||||
firstUpdatedAt := strings.Split(entries[0].UpdatedAt.String(), ".")[0]
|
||||
secondUpdatedAt := strings.Split(entries[1].UpdatedAt.String(), ".")[0]
|
||||
albumUpdatedAt := strings.Split(a.UpdatedAt.String(), ".")[0]
|
||||
|
||||
assert.Truef(
|
||||
t, first_photo_updatedAt <= album_updatedAt,
|
||||
t, firstUpdatedAt <= albumUpdatedAt,
|
||||
"Expected the UpdatedAt field of an album to be updated when"+
|
||||
" new photos are added",
|
||||
)
|
||||
assert.Truef(
|
||||
t, second_photo_updatedAt <= album_updatedAt,
|
||||
t, secondUpdatedAt <= albumUpdatedAt,
|
||||
"Expected the UpdatedAt field of an album to be updated when"+
|
||||
" new photos are added",
|
||||
)
|
||||
|
||||
@@ -100,8 +100,8 @@ func FindClientByUID(uid string) *Client {
|
||||
return m
|
||||
}
|
||||
|
||||
// UID returns the client uid string.
|
||||
func (m *Client) UID() string {
|
||||
// GetUID returns the client uid string.
|
||||
func (m *Client) GetUID() string {
|
||||
return m.ClientUID
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func (m *Client) String() string {
|
||||
if m == nil {
|
||||
return report.NotAssigned
|
||||
} else if m.HasUID() {
|
||||
return m.UID()
|
||||
return m.GetUID()
|
||||
} else if m.HasName() {
|
||||
return m.Name()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestFindClient(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, m.UserUID, UserFixtures.Get("alice").UserUID)
|
||||
assert.Equal(t, expected.ClientUID, m.UID())
|
||||
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||
assert.NotEmpty(t, m.CreatedAt)
|
||||
assert.NotEmpty(t, m.UpdatedAt)
|
||||
})
|
||||
@@ -44,7 +44,7 @@ func TestFindClient(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, m.UserUID, UserFixtures.Get("bob").UserUID)
|
||||
assert.Equal(t, expected.ClientUID, m.UID())
|
||||
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||
assert.NotEmpty(t, m.CreatedAt)
|
||||
assert.NotEmpty(t, m.UpdatedAt)
|
||||
})
|
||||
@@ -58,7 +58,7 @@ func TestFindClient(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Empty(t, m.UserUID)
|
||||
assert.Equal(t, expected.ClientUID, m.UID())
|
||||
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||
assert.NotEmpty(t, m.CreatedAt)
|
||||
assert.NotEmpty(t, m.UpdatedAt)
|
||||
})
|
||||
@@ -411,7 +411,7 @@ func TestClient_VerifySecret(t *testing.T) {
|
||||
t.Fatal("result should not be nil")
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.ClientUID, m.UID())
|
||||
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||
assert.False(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||
assert.False(t, m.VerifySecret(""))
|
||||
@@ -430,7 +430,7 @@ func TestClient_VerifySecret(t *testing.T) {
|
||||
t.Fatal("result should not be nil")
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.ClientUID, m.UID())
|
||||
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||
assert.True(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||
assert.False(t, m.VerifySecret(""))
|
||||
|
||||
@@ -248,7 +248,7 @@ func (m *Session) SetClient(c *Client) *Session {
|
||||
}
|
||||
|
||||
m.client = c
|
||||
m.ClientUID = c.UID()
|
||||
m.ClientUID = c.GetUID()
|
||||
m.ClientName = c.ClientName
|
||||
m.AuthProvider = c.Provider().String()
|
||||
m.AuthMethod = c.Method().String()
|
||||
|
||||
@@ -75,7 +75,7 @@ func DeleteClientSessions(client *Client, authMethod authn.MethodType, limit int
|
||||
q := Db()
|
||||
|
||||
if client.HasUID() {
|
||||
q = q.Where("client_uid = ?", client.UID())
|
||||
q = q.Where("client_uid = ?", client.GetUID())
|
||||
} else if client.HasName() {
|
||||
q = q.Where("client_name = ?", client.Name())
|
||||
} else {
|
||||
|
||||
@@ -203,8 +203,8 @@ func FindUserByUID(uid string) *User {
|
||||
return FindUser(User{UserUID: uid})
|
||||
}
|
||||
|
||||
// UID returns the unique id as string.
|
||||
func (m *User) UID() string {
|
||||
// GetUID returns the unique id as string.
|
||||
func (m *User) GetUID() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -591,7 +591,7 @@ func (m *User) Passcode(t authn.KeyType) *Passcode {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FindPasscode(Passcode{UID: m.UID(), KeyType: t.String()})
|
||||
return FindPasscode(Passcode{UID: m.GetUID(), KeyType: t.String()})
|
||||
}
|
||||
|
||||
// Username returns the user's login name as sanitized string.
|
||||
@@ -711,7 +711,7 @@ func (m *User) Settings() *UserSettings {
|
||||
if m.UserSettings != nil {
|
||||
m.UserSettings.UserUID = m.UserUID
|
||||
return m.UserSettings
|
||||
} else if m.UID() == "" {
|
||||
} else if m.GetUID() == "" {
|
||||
m.UserSettings = &UserSettings{}
|
||||
return m.UserSettings
|
||||
} else if err := CreateUserSettings(m); err != nil {
|
||||
@@ -726,7 +726,7 @@ func (m *User) Details() *UserDetails {
|
||||
if m.UserDetails != nil {
|
||||
m.UserDetails.UserUID = m.UserUID
|
||||
return m.UserDetails
|
||||
} else if m.UID() == "" {
|
||||
} else if m.GetUID() == "" {
|
||||
m.UserDetails = &UserDetails{}
|
||||
return m.UserDetails
|
||||
} else if err := CreateUserDetails(m); err != nil {
|
||||
@@ -1080,7 +1080,7 @@ func (m *User) RegenerateTokens() error {
|
||||
|
||||
// RefreshShares updates the list of shares.
|
||||
func (m *User) RefreshShares() *User {
|
||||
m.UserShares = FindUserShares(m.UID())
|
||||
m.UserShares = FindUserShares(m.GetUID())
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -1133,8 +1133,8 @@ func (m *User) RedeemToken(token string) (n int) {
|
||||
|
||||
// Find shares.
|
||||
for _, link := range links {
|
||||
if found := FindUserShare(UserShare{UserUID: m.UID(), ShareUID: link.ShareUID}); found == nil {
|
||||
share := NewUserShare(m.UID(), link.ShareUID, link.Perm, link.ExpiresAt())
|
||||
if found := FindUserShare(UserShare{UserUID: m.GetUID(), ShareUID: link.ShareUID}); found == nil {
|
||||
share := NewUserShare(m.GetUID(), link.ShareUID, link.Perm, link.ExpiresAt())
|
||||
share.LinkUID = link.LinkUID
|
||||
share.Comment = link.Comment
|
||||
|
||||
|
||||
@@ -69,13 +69,13 @@ func CreateUserDetails(user *User) error {
|
||||
return fmt.Errorf("user is nil")
|
||||
}
|
||||
|
||||
if user.UID() == "" {
|
||||
if user.GetUID() == "" {
|
||||
return fmt.Errorf("empty user uid")
|
||||
}
|
||||
|
||||
user.UserDetails = NewUserDetails(user.UID())
|
||||
user.UserDetails = NewUserDetails(user.GetUID())
|
||||
|
||||
if err := Db().Where("user_uid = ?", user.UID()).First(user.UserDetails).Error; err == nil {
|
||||
if err := Db().Where("user_uid = ?", user.GetUID()).First(user.UserDetails).Error; err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,13 @@ func CreateUserSettings(user *User) error {
|
||||
return fmt.Errorf("user is nil")
|
||||
}
|
||||
|
||||
if user.UID() == "" {
|
||||
if user.GetUID() == "" {
|
||||
return fmt.Errorf("empty user uid")
|
||||
}
|
||||
|
||||
user.UserSettings = &UserSettings{}
|
||||
|
||||
if err := Db().Where("user_uid = ?", user.UID()).First(user.UserSettings).Error; err == nil {
|
||||
if err := Db().Where("user_uid = ?", user.GetUID()).First(user.UserSettings).Error; err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestUserShares_Contains(t *testing.T) {
|
||||
|
||||
func TestNewUserShare(t *testing.T) {
|
||||
expires := TimeStamp().Add(time.Hour * 48)
|
||||
m := NewUserShare(Admin.UID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
|
||||
m := NewUserShare(Admin.GetUID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
|
||||
|
||||
assert.True(t, m.HasID())
|
||||
assert.True(t, rnd.IsRefID(m.RefID))
|
||||
@@ -76,7 +76,7 @@ func TestFindUserShare(t *testing.T) {
|
||||
|
||||
func TestFindUserShares(t *testing.T) {
|
||||
t.Run("Alice", func(t *testing.T) {
|
||||
found := FindUserShares(UserFixtures.Pointer("alice").UID())
|
||||
found := FindUserShares(UserFixtures.Pointer("alice").GetUID())
|
||||
assert.NotNil(t, found)
|
||||
assert.Len(t, found, 1)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// Map is an alias for map[string]interface{}.
|
||||
type Map map[string]interface{}
|
||||
type Map = map[string]interface{}
|
||||
|
||||
// ModelValues extracts Values from an entity model.
|
||||
func ModelValues(m interface{}, omit ...string) (result Map, omitted []interface{}, err error) {
|
||||
|
||||
@@ -32,19 +32,6 @@ var MetadataEstimateInterval = 24 * 7 * time.Hour // 7 Days
|
||||
|
||||
var photoMutex = sync.Mutex{}
|
||||
|
||||
type Photos []Photo
|
||||
|
||||
// UIDs returns a slice of photo UIDs.
|
||||
func (m Photos) UIDs() []string {
|
||||
result := make([]string, len(m))
|
||||
|
||||
for i, el := range m {
|
||||
result[i] = el.PhotoUID
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MapKey returns a key referencing time and location for indexing.
|
||||
func MapKey(takenAt time.Time, cellId string) string {
|
||||
return path.Join(strconv.FormatInt(takenAt.Unix(), 36), cellId)
|
||||
@@ -222,6 +209,21 @@ func SavePhotoForm(model Photo, form form.Photo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the numeric entity ID.
|
||||
func (m *Photo) GetID() uint {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// HasID checks if the photo has an id and uid assigned to it.
|
||||
func (m *Photo) HasID() bool {
|
||||
return m.ID > 0 && m.PhotoUID != ""
|
||||
}
|
||||
|
||||
// GetUID returns the unique entity id.
|
||||
func (m *Photo) GetUID() string {
|
||||
return m.PhotoUID
|
||||
}
|
||||
|
||||
// String returns the id or name as string.
|
||||
func (m *Photo) String() string {
|
||||
if m.PhotoName != "" {
|
||||
@@ -527,11 +529,6 @@ func (m *Photo) PreloadMany() {
|
||||
m.PreloadAlbums()
|
||||
}
|
||||
|
||||
// HasID checks if the photo has an id and uid assigned to it.
|
||||
func (m *Photo) HasID() bool {
|
||||
return m.ID > 0 && m.PhotoUID != ""
|
||||
}
|
||||
|
||||
// NoCameraSerial checks if the photo has no CameraSerial
|
||||
func (m *Photo) NoCameraSerial() bool {
|
||||
return m.CameraSerial == ""
|
||||
@@ -731,11 +728,17 @@ func (m *Photo) AllFiles() (files Files) {
|
||||
|
||||
// Archive removes the photo from albums and flags it as archived (soft delete).
|
||||
func (m *Photo) Archive() error {
|
||||
if !m.HasID() {
|
||||
return fmt.Errorf("photo has no id")
|
||||
} else if m.DeletedAt != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deletedAt := TimeStamp()
|
||||
|
||||
if err := Db().Model(&PhotoAlbum{}).Where("photo_uid = ?", m.PhotoUID).UpdateColumn("hidden", true).Error; err != nil {
|
||||
return err
|
||||
} else if err := m.Update("deleted_at", deletedAt); err != nil {
|
||||
} else if err = m.Update("deleted_at", deletedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -744,8 +747,14 @@ func (m *Photo) Archive() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore removes the archive flag (undo soft delete).
|
||||
// Restore removes the photo from the archive (reverses soft delete).
|
||||
func (m *Photo) Restore() error {
|
||||
if !m.HasID() {
|
||||
return fmt.Errorf("photo has no id")
|
||||
} else if m.DeletedAt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.Update("deleted_at", gorm.Expr("NULL")); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -757,7 +766,7 @@ func (m *Photo) Restore() error {
|
||||
|
||||
// Delete deletes the photo from the index.
|
||||
func (m *Photo) Delete(permanently bool) (files Files, err error) {
|
||||
if m.ID < 1 || m.PhotoUID == "" {
|
||||
if !m.HasID() {
|
||||
return files, fmt.Errorf("invalid photo id %d / uid %s", m.ID, clean.Log(m.PhotoUID))
|
||||
}
|
||||
|
||||
@@ -834,7 +843,7 @@ func (m *Photo) React(user *User, reaction react.Emoji) error {
|
||||
return m.UnReact(user)
|
||||
}
|
||||
|
||||
return NewReaction(m.PhotoUID, user.UID()).React(reaction).Save()
|
||||
return NewReaction(m.PhotoUID, user.GetUID()).React(reaction).Save()
|
||||
}
|
||||
|
||||
// UnReact deletes a previous user reaction, if any.
|
||||
@@ -843,7 +852,7 @@ func (m *Photo) UnReact(user *User) error {
|
||||
return fmt.Errorf("unknown user")
|
||||
}
|
||||
|
||||
if r := FindReaction(m.PhotoUID, user.UID()); r != nil {
|
||||
if r := FindReaction(m.PhotoUID, user.GetUID()); r != nil {
|
||||
return r.Delete()
|
||||
}
|
||||
|
||||
@@ -884,13 +893,31 @@ func (m *Photo) SetStack(stack int8) {
|
||||
}
|
||||
}
|
||||
|
||||
// Approve approves a photo in review.
|
||||
// Approved checks if the photo is not in review.
|
||||
func (m *Photo) Approved() bool {
|
||||
if !m.HasID() {
|
||||
return false
|
||||
} else if m.PhotoQuality >= 3 || m.PhotoType != MediaImage || m.EditedAt != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Approve approves the photo if it is in review.
|
||||
func (m *Photo) Approve() error {
|
||||
if m.PhotoQuality >= 3 {
|
||||
if !m.HasID() {
|
||||
return fmt.Errorf("photo has no id")
|
||||
} else if m.PhotoQuality >= 3 {
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore photo if archived.
|
||||
if err := m.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edited := TimeStamp()
|
||||
m.EditedAt = &edited
|
||||
m.PhotoQuality = m.QualityScore()
|
||||
|
||||
@@ -132,7 +132,7 @@ func (m *Photo) EstimateLocation(force bool) {
|
||||
m.RemoveLocationLabels()
|
||||
m.EstimateCountry()
|
||||
} else if len(mostRecent) == 1 || m.UnknownCamera() {
|
||||
m.AdoptPlace(recentPhoto, SrcEstimate, false)
|
||||
m.AdoptPlace(&recentPhoto, SrcEstimate, false)
|
||||
} else {
|
||||
p1 := mostRecent[0]
|
||||
p2 := mostRecent[1]
|
||||
@@ -143,7 +143,7 @@ func (m *Photo) EstimateLocation(force bool) {
|
||||
if estimate := movement.EstimatePosition(m.TakenAt); movement.Km() < 100 && estimate.Accuracy < Accuracy1Km {
|
||||
m.SetPosition(estimate, SrcEstimate, false)
|
||||
} else {
|
||||
m.AdoptPlace(recentPhoto, SrcEstimate, false)
|
||||
m.AdoptPlace(&recentPhoto, SrcEstimate, false)
|
||||
}
|
||||
}
|
||||
} else if recentPhoto.HasCountry() {
|
||||
|
||||
16
internal/entity/photo_interface.go
Normal file
16
internal/entity/photo_interface.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package entity
|
||||
|
||||
// PhotoInterface represents an abstract Photo entity interface.
|
||||
type PhotoInterface interface {
|
||||
GetID() uint
|
||||
HasID() bool
|
||||
GetUID() string
|
||||
Approve() error
|
||||
Restore() error
|
||||
}
|
||||
|
||||
// PhotosInterface represents a Photo slice provider interface.
|
||||
type PhotosInterface interface {
|
||||
UIDs() []string
|
||||
Photos() []PhotoInterface
|
||||
}
|
||||
@@ -86,8 +86,10 @@ func (m *Photo) SetPosition(pos geo.Position, source string, force bool) {
|
||||
}
|
||||
|
||||
// AdoptPlace sets the place based on another photo.
|
||||
func (m *Photo) AdoptPlace(other Photo, source string, force bool) {
|
||||
if SrcPriority[m.PlaceSrc] > SrcPriority[source] && !force {
|
||||
func (m *Photo) AdoptPlace(other *Photo, source string, force bool) {
|
||||
if other == nil {
|
||||
return
|
||||
} else if SrcPriority[m.PlaceSrc] > SrcPriority[source] && !force {
|
||||
return
|
||||
} else if other.Place == nil {
|
||||
return
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
||||
place := PlaceFixtures.Get("mexico")
|
||||
t.Run("SrcAuto", func(t *testing.T) {
|
||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: -1, PhotoLng: 1, PlaceSrc: SrcAuto}
|
||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 15, PhotoLng: -11, PlaceSrc: SrcManual}
|
||||
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 15, PhotoLng: -11, PlaceSrc: SrcManual}
|
||||
assert.Nil(t, p.Place)
|
||||
assert.Equal(t, "", p.PlaceID)
|
||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||
@@ -47,7 +47,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
||||
})
|
||||
t.Run("SrcManual", func(t *testing.T) {
|
||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
||||
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
||||
assert.Nil(t, p.Place)
|
||||
assert.Equal(t, "", p.PlaceID)
|
||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||
@@ -62,7 +62,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
||||
})
|
||||
t.Run("Force", func(t *testing.T) {
|
||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
||||
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
||||
assert.Nil(t, p.Place)
|
||||
assert.Equal(t, "", p.PlaceID)
|
||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||
|
||||
@@ -808,8 +808,8 @@ func TestPhoto_AllFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPhoto_Archive(t *testing.T) {
|
||||
t.Run("archive not yet archived photo", func(t *testing.T) {
|
||||
m := &Photo{PhotoTitle: "HappyLilly"}
|
||||
t.Run("NotYetArchived", func(t *testing.T) {
|
||||
m := &Photo{ID: 10000, PhotoUID: "csd7ybn092yzcp52", PhotoTitle: "HappyLilly"}
|
||||
assert.Empty(t, m.DeletedAt)
|
||||
err := m.Archive()
|
||||
if err != nil {
|
||||
|
||||
26
internal/entity/photos.go
Normal file
26
internal/entity/photos.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package entity
|
||||
|
||||
// Photos represents a list of photos.
|
||||
type Photos []Photo
|
||||
|
||||
// Photos returns the result as a slice of Photo.
|
||||
func (m Photos) Photos() []PhotoInterface {
|
||||
result := make([]PhotoInterface, len(m))
|
||||
|
||||
for i := range m {
|
||||
result[i] = &m[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UIDs returns tbe photo UIDs as string slice.
|
||||
func (m Photos) UIDs() []string {
|
||||
result := make([]string, len(m))
|
||||
|
||||
for i, photo := range m {
|
||||
result[i] = photo.GetUID()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -85,9 +85,9 @@ func (w *CleanUp) Start(opt CleanUpOptions) (thumbs int, orphans int, sidecars i
|
||||
}
|
||||
|
||||
// Deletes the index entry and remaining sidecar files outside the "originals" folder.
|
||||
if n, err := DeletePhoto(p, true, false); err != nil {
|
||||
if n, deleteErr := DeletePhoto(&p, true, false); deleteErr != nil {
|
||||
sidecars += n
|
||||
log.Errorf("cleanup: %s (remove orphans)", err)
|
||||
log.Errorf("cleanup: %s (remove orphans)", deleteErr)
|
||||
} else {
|
||||
orphans++
|
||||
sidecars += n
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package photoprism
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -10,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
// DeletePhoto removes a photo from the index and optionally all related media files.
|
||||
func DeletePhoto(p entity.Photo, mediaFiles bool, originals bool) (numFiles int, err error) {
|
||||
func DeletePhoto(p *entity.Photo, mediaFiles bool, originals bool) (numFiles int, err error) {
|
||||
if p == nil {
|
||||
return 0, errors.New("photo is nil")
|
||||
}
|
||||
|
||||
yamlFileName := p.YamlFileName(Config().OriginalsPath(), Config().SidecarPath())
|
||||
|
||||
// Permanently remove photo from index.
|
||||
|
||||
@@ -19,7 +19,7 @@ type ImportOptions struct {
|
||||
// SetUser sets the user who performs the import operation.
|
||||
func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
||||
if o != nil && user != nil {
|
||||
o.UID = user.UID()
|
||||
o.UID = user.GetUID()
|
||||
}
|
||||
|
||||
return o
|
||||
@@ -28,7 +28,7 @@ func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
||||
// ImportOptionsCopy returns import options for copying files to originals (read-only).
|
||||
func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
UID: entity.Admin.GetUID(),
|
||||
Action: ActionImport,
|
||||
Path: importPath,
|
||||
Move: false,
|
||||
@@ -45,7 +45,7 @@ func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
||||
// ImportOptionsMove returns import options for moving files to originals (modifies import directory).
|
||||
func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
UID: entity.Admin.GetUID(),
|
||||
Action: ActionImport,
|
||||
Path: importPath,
|
||||
Move: true,
|
||||
@@ -62,7 +62,7 @@ func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
||||
// ImportOptionsUpload returns options for importing user uploads.
|
||||
func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
UID: entity.Admin.GetUID(),
|
||||
Action: ActionUpload,
|
||||
Path: uploadPath,
|
||||
Move: true,
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestIndex_MediaFile(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, "", mediaFile.metaData.Title)
|
||||
|
||||
result := ind.UserMediaFile(mediaFile, indexOpt, "blue-go-video.mp4", "", entity.Admin.UID())
|
||||
result := ind.UserMediaFile(mediaFile, indexOpt, "blue-go-video.mp4", "", entity.Admin.GetUID())
|
||||
|
||||
assert.Equal(t, "Blue Gopher", mediaFile.metaData.Title)
|
||||
assert.Equal(t, IndexStatus("added"), result.Status)
|
||||
|
||||
@@ -19,7 +19,7 @@ type IndexOptions struct {
|
||||
// NewIndexOptions returns new index options instance.
|
||||
func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchived bool) IndexOptions {
|
||||
result := IndexOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
UID: entity.Admin.GetUID(),
|
||||
Action: ActionIndex,
|
||||
Path: path,
|
||||
Rescan: rescan,
|
||||
@@ -42,7 +42,7 @@ func (o *IndexOptions) SkipUnchanged() bool {
|
||||
// SetUser sets the user who performs the index operation.
|
||||
func (o *IndexOptions) SetUser(user *entity.User) *IndexOptions {
|
||||
if o != nil && user != nil {
|
||||
o.UID = user.UID()
|
||||
o.UID = user.GetUID()
|
||||
}
|
||||
|
||||
return o
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/ulule/deepcopier"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -98,14 +100,91 @@ type Photo struct {
|
||||
UpdatedAt time.Time `json:"UpdatedAt" select:"photos.updated_at"`
|
||||
EditedAt time.Time `json:"EditedAt,omitempty" select:"photos.edited_at"`
|
||||
CheckedAt time.Time `json:"CheckedAt,omitempty" select:"photos.checked_at"`
|
||||
DeletedAt time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"`
|
||||
DeletedAt *time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"`
|
||||
|
||||
Files []entity.File `json:"Files"`
|
||||
}
|
||||
|
||||
// GetID returns the numeric entity ID.
|
||||
func (m *Photo) GetID() uint {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// HasID checks if the photo has an id and uid assigned to it.
|
||||
func (m *Photo) HasID() bool {
|
||||
return m.ID > 0 && m.PhotoUID != ""
|
||||
}
|
||||
|
||||
// GetUID returns the unique entity id.
|
||||
func (m *Photo) GetUID() string {
|
||||
return m.PhotoUID
|
||||
}
|
||||
|
||||
// Approve approves the photo if it is in review.
|
||||
func (m *Photo) Approve() error {
|
||||
if !m.HasID() {
|
||||
return fmt.Errorf("photo has no id")
|
||||
} else if m.PhotoQuality >= 3 {
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore photo if archived.
|
||||
if err := m.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edited := entity.TimeStamp()
|
||||
|
||||
if err := UnscopedDb().
|
||||
Table(entity.Photo{}.TableName()).
|
||||
Where("photo_uid = ?", m.GetUID()).
|
||||
UpdateColumns(entity.Map{
|
||||
"deleted_at": gorm.Expr("NULL"),
|
||||
"edited_at": &edited,
|
||||
"photo_quality": 3}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.EditedAt = edited
|
||||
m.PhotoQuality = 3
|
||||
m.DeletedAt = nil
|
||||
|
||||
// Update precalculated photo and file counts.
|
||||
if err := entity.UpdateCounts(); err != nil {
|
||||
log.Warnf("index: %s (update counts)", err)
|
||||
}
|
||||
|
||||
event.Publish("count.review", event.Data{
|
||||
"count": -1,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore removes the photo from the archive (reverses soft delete).
|
||||
func (m *Photo) Restore() error {
|
||||
if !m.HasID() {
|
||||
return fmt.Errorf("photo has no id")
|
||||
} else if m.DeletedAt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := UnscopedDb().
|
||||
Table(entity.Photo{}.TableName()).
|
||||
Where("photo_uid = ?", m.GetUID()).
|
||||
UpdateColumn("deleted_at", gorm.Expr("NULL")).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.DeletedAt = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPlayable returns true if the photo has a related video/animation that is playable.
|
||||
func (photo *Photo) IsPlayable() bool {
|
||||
switch photo.PhotoType {
|
||||
func (m *Photo) IsPlayable() bool {
|
||||
switch m.PhotoType {
|
||||
case entity.MediaVideo, entity.MediaLive, entity.MediaAnimated:
|
||||
return true
|
||||
default:
|
||||
@@ -114,31 +193,42 @@ func (photo *Photo) IsPlayable() bool {
|
||||
}
|
||||
|
||||
// ShareBase returns a meaningful file name for sharing.
|
||||
func (photo *Photo) ShareBase(seq int) string {
|
||||
func (m *Photo) ShareBase(seq int) string {
|
||||
var name string
|
||||
|
||||
if photo.PhotoTitle != "" {
|
||||
name = txt.Title(slug.MakeLang(photo.PhotoTitle, "en"))
|
||||
if m.PhotoTitle != "" {
|
||||
name = txt.Title(slug.MakeLang(m.PhotoTitle, "en"))
|
||||
} else {
|
||||
name = photo.PhotoUID
|
||||
name = m.PhotoUID
|
||||
}
|
||||
|
||||
taken := photo.TakenAtLocal.Format("20060102-150405")
|
||||
taken := m.TakenAtLocal.Format("20060102-150405")
|
||||
|
||||
if seq > 0 {
|
||||
return fmt.Sprintf("%s-%s (%d).%s", taken, name, seq, photo.FileType)
|
||||
return fmt.Sprintf("%s-%s (%d).%s", taken, name, seq, m.FileType)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s.%s", taken, name, photo.FileType)
|
||||
return fmt.Sprintf("%s-%s.%s", taken, name, m.FileType)
|
||||
}
|
||||
|
||||
type PhotoResults []Photo
|
||||
|
||||
// UIDs returns a slice of photo UIDs.
|
||||
func (photos PhotoResults) UIDs() []string {
|
||||
result := make([]string, len(photos))
|
||||
// Photos returns the result as a slice of Photo.
|
||||
func (m PhotoResults) Photos() []entity.PhotoInterface {
|
||||
result := make([]entity.PhotoInterface, len(m))
|
||||
|
||||
for i, el := range photos {
|
||||
for i := range m {
|
||||
result[i] = &m[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UIDs returns a slice of photo UIDs.
|
||||
func (m PhotoResults) UIDs() []string {
|
||||
result := make([]string, len(m))
|
||||
|
||||
for i, el := range m {
|
||||
result[i] = el.PhotoUID
|
||||
}
|
||||
|
||||
@@ -146,14 +236,14 @@ func (photos PhotoResults) UIDs() []string {
|
||||
}
|
||||
|
||||
// Merge consecutive file results that belong to the same photo.
|
||||
func (photos PhotoResults) Merge() (merged PhotoResults, count int, err error) {
|
||||
count = len(photos)
|
||||
func (m PhotoResults) Merge() (merged PhotoResults, count int, err error) {
|
||||
count = len(m)
|
||||
merged = make(PhotoResults, 0, count)
|
||||
|
||||
var i int
|
||||
var photoId uint
|
||||
|
||||
for _, photo := range photos {
|
||||
for _, photo := range m {
|
||||
file := entity.File{}
|
||||
|
||||
if err = deepcopier.Copy(&file).From(photo); err != nil {
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestPhotosResults_Merged(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
@@ -71,7 +71,7 @@ func TestPhotosResults_Merged(t *testing.T) {
|
||||
ID: 22222,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
@@ -141,7 +141,7 @@ func TestPhotosResults_UIDs(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
@@ -200,7 +200,7 @@ func TestPhotosResults_UIDs(t *testing.T) {
|
||||
ID: 22222,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
@@ -267,7 +267,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenAtLocal: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenSrc: "",
|
||||
@@ -330,7 +330,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenAtLocal: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenSrc: "",
|
||||
@@ -394,7 +394,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenAtLocal: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||
TakenSrc: "",
|
||||
|
||||
@@ -25,40 +25,40 @@ func UserPhotosViewerResults(f form.SearchPhotos, sess *entity.Session, contentU
|
||||
}
|
||||
|
||||
// ViewerResult returns a new photo viewer result.
|
||||
func (photo Photo) ViewerResult(contentUri, apiUri, previewToken, downloadToken string) viewer.Result {
|
||||
func (m Photo) ViewerResult(contentUri, apiUri, previewToken, downloadToken string) viewer.Result {
|
||||
return viewer.Result{
|
||||
UID: photo.PhotoUID,
|
||||
Title: photo.PhotoTitle,
|
||||
TakenAtLocal: photo.TakenAtLocal,
|
||||
Description: photo.PhotoDescription,
|
||||
Favorite: photo.PhotoFavorite,
|
||||
Playable: photo.IsPlayable(),
|
||||
DownloadUrl: viewer.DownloadUrl(photo.FileHash, apiUri, downloadToken),
|
||||
Width: photo.FileWidth,
|
||||
Height: photo.FileHeight,
|
||||
UID: m.PhotoUID,
|
||||
Title: m.PhotoTitle,
|
||||
TakenAtLocal: m.TakenAtLocal,
|
||||
Description: m.PhotoDescription,
|
||||
Favorite: m.PhotoFavorite,
|
||||
Playable: m.IsPlayable(),
|
||||
DownloadUrl: viewer.DownloadUrl(m.FileHash, apiUri, downloadToken),
|
||||
Width: m.FileWidth,
|
||||
Height: m.FileHeight,
|
||||
Thumbs: thumb.Public{
|
||||
Fit720: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit720], contentUri, previewToken),
|
||||
Fit1280: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken),
|
||||
Fit1920: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit1920], contentUri, previewToken),
|
||||
Fit2048: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit2048], contentUri, previewToken),
|
||||
Fit2560: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit2560], contentUri, previewToken),
|
||||
Fit3840: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken),
|
||||
Fit4096: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit4096], contentUri, previewToken),
|
||||
Fit7680: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit7680], contentUri, previewToken),
|
||||
Fit720: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit720], contentUri, previewToken),
|
||||
Fit1280: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken),
|
||||
Fit1920: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit1920], contentUri, previewToken),
|
||||
Fit2048: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit2048], contentUri, previewToken),
|
||||
Fit2560: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit2560], contentUri, previewToken),
|
||||
Fit3840: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken),
|
||||
Fit4096: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit4096], contentUri, previewToken),
|
||||
Fit7680: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit7680], contentUri, previewToken),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ViewerJSON returns the results as photo viewer JSON.
|
||||
func (photos PhotoResults) ViewerJSON(contentUri, apiUri, previewToken, downloadToken string) ([]byte, error) {
|
||||
return json.Marshal(photos.ViewerResults(contentUri, apiUri, previewToken, downloadToken))
|
||||
func (m PhotoResults) ViewerJSON(contentUri, apiUri, previewToken, downloadToken string) ([]byte, error) {
|
||||
return json.Marshal(m.ViewerResults(contentUri, apiUri, previewToken, downloadToken))
|
||||
}
|
||||
|
||||
// ViewerResults returns the results photo viewer formatted.
|
||||
func (photos PhotoResults) ViewerResults(contentUri, apiUri, previewToken, downloadToken string) (results viewer.Results) {
|
||||
results = make(viewer.Results, 0, len(photos))
|
||||
func (m PhotoResults) ViewerResults(contentUri, apiUri, previewToken, downloadToken string) (results viewer.Results) {
|
||||
results = make(viewer.Results, 0, len(m))
|
||||
|
||||
for _, p := range photos {
|
||||
for _, p := range m {
|
||||
results = append(results, p.ViewerResult(contentUri, apiUri, previewToken, downloadToken))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestPhotoResults_ViewerJSON(t *testing.T) {
|
||||
ID: 111111,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
@@ -71,7 +71,7 @@ func TestPhotoResults_ViewerJSON(t *testing.T) {
|
||||
ID: 22222,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
DeletedAt: time.Time{},
|
||||
DeletedAt: &time.Time{},
|
||||
TakenAt: time.Time{},
|
||||
TakenAtLocal: time.Time{},
|
||||
TakenSrc: "",
|
||||
|
||||
Reference in New Issue
Block a user