Skip to content

Commit

Permalink
Merge pull request #34 from kvalev/kvv-album-delete-restore
Browse files Browse the repository at this point in the history
Rework moments deletion and restoration
  • Loading branch information
kvalev authored Nov 16, 2021
2 parents 3a34553 + 22a6b92 commit 0a10789
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 84 deletions.
2 changes: 1 addition & 1 deletion frontend/src/component/album/clipboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default {
},
data() {
return {
deletable: ["album", "moment", "state"],
deletable: ["album"],
features: this.$config.settings().features,
expanded: false,
dialog: {
Expand Down
8 changes: 4 additions & 4 deletions internal/entity/album.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album {
return result
}

// FindLabelAlbums finds all label albums (including deletes ones) or returns nil.
func FindLabelAlbums() (result Albums) {
// Both "label" and "country / year" album have the same album type,
// so to distinguish between the two we have few options:
Expand All @@ -253,7 +254,6 @@ func FindLabelAlbums() (result Albums) {
if err := UnscopedDb().
Where("album_type = ?", AlbumMoment).
Where("album_year IS NULL").
Where("deleted_at IS NULL").
Find(&result).Error; err != nil {

log.Errorf("album: %s (not found)", err)
Expand All @@ -263,11 +263,11 @@ func FindLabelAlbums() (result Albums) {
return result
}

// FindCountriesByYearAlbums finds all moment albums (including deleted ones) or returns nil.
func FindCountriesByYearAlbums() (result Albums) {
if err := UnscopedDb().
Where("album_type = ?", AlbumMoment).
Where("album_year IS NOT NULL").
Where("deleted_at IS NULL").
Find(&result).Error; err != nil {

log.Errorf("album: %s (not found)", err)
Expand All @@ -277,9 +277,9 @@ func FindCountriesByYearAlbums() (result Albums) {
return result
}

// FindAlbumByType finds matching albums or returns nil.
// FindAlbumByType finds all albums (including deleted ones) with the given type or returns nil.
func FindAlbumsByType(albumType string) (result Albums) {
if err := UnscopedDb().Where("album_type = ? AND deleted_at IS NULL", albumType).Find(&result).Error; err != nil {
if err := UnscopedDb().Where("album_type = ?", albumType).Find(&result).Error; err != nil {
log.Errorf("album: %s (not found)", err)
return nil
}
Expand Down
127 changes: 58 additions & 69 deletions internal/photoprism/moments.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ func (w *Moments) Start() (err error) {
if results, err := query.AlbumFolders(1); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindAlbumsByType(entity.AlbumFolder) {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
Expand All @@ -80,17 +80,16 @@ func (w *Moments) Start() (err error) {
}

if a := entity.FindFolderAlbum(mom.Path); a != nil {
// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: folder album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.FileCount)

if a.DeletedAt != nil {
// Nothing to do.
log.Tracef("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else if err := a.UpdateFolder(mom.Path, f.Serialize()); err != nil {
// restore the folder album if it has been automatically deleted
restoreAlbum(a)

// Update folder search filter when path changes
if err := a.UpdateFolder(mom.Path, f.Serialize()); err != nil {
log.Errorf("moments: %s (update folder)", err.Error())
} else {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
}
} else if a := entity.NewFolderAlbum(mom.Title(), mom.Path, f.Serialize(), w.conf.Settings().Folders.SortOrder); a != nil {
a.AlbumYear = mom.FolderYear
Expand All @@ -106,34 +105,28 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
log.Infof("moments: empty folder album %s will be deleted (%s)", txt.Quote(album.AlbumTitle), album.AlbumFilter)
album.Delete()
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}

// All years and months.
if results, err := query.MomentsTime(1); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindAlbumsByType(entity.AlbumMonth) {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
if a := entity.FindAlbumBySlug(mom.Slug(), entity.AlbumMonth); a != nil {
// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: month album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.PhotoCount)

if !a.Deleted() {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else if err := a.Restore(); err != nil {
log.Errorf("moments: %s (restore month)", err.Error())
} else {
log.Infof("moments: %s restored", txt.Quote(a.AlbumTitle))
}
// restore the month album if it has been automatically deleted
restoreAlbum(a)
} else if a := entity.NewMonthAlbum(mom.Title(), mom.Slug(), mom.Year, mom.Month); a != nil {
if err := a.Create(); err != nil {
log.Errorf("moments: %s", err)
Expand All @@ -143,19 +136,18 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
log.Infof("moments: empty month album %s will be deleted (%s)", txt.Quote(album.AlbumTitle), album.AlbumFilter)
album.Delete()
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}

// Countries by year.
if results, err := query.MomentsCountriesByYear(threshold); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindCountriesByYearAlbums() {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
Expand All @@ -166,16 +158,12 @@ func (w *Moments) Start() (err error) {
}

if a := entity.FindAlbumBySlug(mom.Slug(), entity.AlbumMoment); a != nil {
// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: moment album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.PhotoCount)

if a.Deleted() {
// Nothing to do.
log.Tracef("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
}
// restore the moment album if it has been automatically deleted
restoreAlbum(a)
} else if a := entity.NewMomentsAlbum(mom.Title(), mom.Slug(), f.Serialize()); a != nil {
a.AlbumYear = mom.Year
a.AlbumCountry = mom.Country
Expand All @@ -188,7 +176,7 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}
Expand All @@ -197,9 +185,9 @@ func (w *Moments) Start() (err error) {
if results, err := query.MomentsCountries(threshold); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindAlbumsByType(entity.AlbumCountry) {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
Expand All @@ -209,16 +197,12 @@ func (w *Moments) Start() (err error) {
}

if a := entity.FindAlbumBySlug(mom.Slug(), entity.AlbumCountry); a != nil {
// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: country album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.PhotoCount)

if a.Deleted() {
// Nothing to do.
log.Tracef("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
}
// restore the country album if it has been automatically deleted
restoreAlbum(a)
} else if a := entity.NewCountryAlbum(mom.Title(), mom.Slug(), f.Serialize()); a != nil {
a.AlbumCountry = mom.Country

Expand All @@ -230,7 +214,7 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}
Expand All @@ -239,9 +223,9 @@ func (w *Moments) Start() (err error) {
if results, err := query.MomentsStates(1); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindAlbumsByType(entity.AlbumState) {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
Expand All @@ -252,16 +236,12 @@ func (w *Moments) Start() (err error) {
}

if a := entity.FindAlbumBySlug(mom.Slug(), entity.AlbumState); a != nil {
// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: state album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.PhotoCount)

if a.Deleted() {
// Nothing to do.
log.Tracef("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
}
// restore the state album if it has been automatically deleted
restoreAlbum(a)
} else if a := entity.NewStateAlbum(mom.Title(), mom.Slug(), f.Serialize()); a != nil {
a.AlbumCountry = mom.Country

Expand All @@ -273,19 +253,18 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
log.Infof("moments: empty state album %s will be deleted (%s)", txt.Quote(album.AlbumTitle), album.AlbumFilter)
album.Delete()
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}

// Popular labels.
if results, err := query.MomentsLabels(threshold); err != nil {
log.Errorf("moments: %s", err.Error())
} else {
emptyAlbumbs := make(map[string]entity.Album)
emptyAlbums := make(map[string]entity.Album)
for _, a := range entity.FindLabelAlbums() {
emptyAlbumbs[a.AlbumUID] = a
emptyAlbums[a.AlbumUID] = a
}

for _, mom := range results {
Expand All @@ -297,8 +276,8 @@ func (w *Moments) Start() (err error) {
if a := entity.FindAlbumBySlug(mom.Slug(), entity.AlbumMoment); a != nil {
log.Tracef("moments: %s already exists (%s)", txt.Quote(mom.Title()), f.Serialize())

// mark the album as non-empty to prevent deletion
delete(emptyAlbumbs, a.AlbumUID)
// Mark the album as non-empty to prevent deletion.
delete(emptyAlbums, a.AlbumUID)
log.Infof("moments: label album %s is not empty, has %d photos", txt.Quote(a.AlbumTitle), mom.PhotoCount)

if f.Serialize() == a.AlbumFilter || a.DeletedAt != nil {
Expand Down Expand Up @@ -330,7 +309,7 @@ func (w *Moments) Start() (err error) {
}
}

for _, album := range emptyAlbumbs {
for _, album := range emptyAlbums {
deleteAlbumIfEmpty(album)
}
}
Expand All @@ -357,6 +336,16 @@ func (w *Moments) Cancel() {
mutex.MainWorker.Cancel()
}

func restoreAlbum(a *entity.Album) {
if !a.Deleted() {
log.Tracef("moments: %s already exists (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
} else if err := a.Restore(); err != nil {
log.Errorf("moments: %s (restore %s)", err.Error(), a.AlbumType)
} else {
log.Infof("moments: %s restored", txt.Quote(a.AlbumTitle))
}
}

func deleteAlbumIfEmpty(album entity.Album) {
// The threshold will naturaly rise when people upload more photos, so all of a sudden some
// albums might be considered empty if they drop below the dynamic threshold. To prevent that
Expand All @@ -371,9 +360,9 @@ func deleteAlbumIfEmpty(album entity.Album) {
if _, count, err := search.Photos(f); err != nil {
log.Errorf("moments: %s (photo search for album %s)", err, txt.Quote(album.AlbumTitle))
} else if count > 0 {
log.Infof("moments: moment album %s is below threshold, but will not be deleted", txt.Quote(album.AlbumTitle))
log.Infof("moments: %s album %s is below threshold, but will not be deleted", album.AlbumType, txt.Quote(album.AlbumTitle))
} else if count == 0 {
log.Infof("moments: empty moment album %s will be deleted (%s)", txt.Quote(album.AlbumTitle), album.AlbumFilter)
log.Infof("moments: empty %s album %s will be deleted (%s)", album.AlbumType, txt.Quote(album.AlbumTitle), album.AlbumFilter)
log.Warn("moments: album deletion is running in dry-run mode, so the album will not be deleted yet")
// album.Delete()
}
Expand Down
10 changes: 0 additions & 10 deletions internal/query/albums.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/pkg/txt"
)

// Albums returns a slice of albums.
Expand Down Expand Up @@ -46,15 +45,6 @@ func AlbumCoverByUID(uid string) (file entity.File, err error) {
}
}

// Automatically hide empty months.
if a.AlbumType == entity.AlbumMonth {
if err := a.Delete(); err != nil {
log.Errorf("album: %s (hide %s)", err, a.AlbumType)
} else {
log.Infof("album: %s hidden", txt.Quote(a.AlbumTitle))
}
}

return file, fmt.Errorf("no cover found")
}

Expand Down

0 comments on commit 0a10789

Please sign in to comment.