diff --git a/internal/entity/album_fixtures.go b/internal/entity/album_fixtures.go index 8a92c5eb4..df0c26985 100644 --- a/internal/entity/album_fixtures.go +++ b/internal/entity/album_fixtures.go @@ -245,6 +245,54 @@ var AlbumFixtures = AlbumMap{ UpdatedAt: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC), DeletedAt: nil, }, + "california-duplicate-1": { + ID: 1000009, + AlbumUID: "at1lxuqipotaab12", + AlbumSlug: "california-usa", + AlbumPath: "", + AlbumType: AlbumState, + AlbumTitle: "California / USA", + AlbumLocation: "", + AlbumCategory: "", + AlbumCaption: "", + AlbumDescription: "", + AlbumNotes: "", + AlbumFilter: "country:us state:California", + AlbumOrder: "newest", + AlbumTemplate: "", + AlbumCountry: "us", + AlbumYear: 0, + AlbumMonth: 0, + AlbumDay: 0, + AlbumFavorite: false, + CreatedAt: time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC), + DeletedAt: nil, + }, + "california-duplicate-2": { + ID: 1000010, + AlbumUID: "at1lxuqipotaab19", + AlbumSlug: "california", + AlbumPath: "", + AlbumType: AlbumState, + AlbumTitle: "California", + AlbumLocation: "", + AlbumCategory: "", + AlbumCaption: "", + AlbumDescription: "", + AlbumNotes: "", + AlbumFilter: "public:true country:us state:California", + AlbumOrder: "newest", + AlbumTemplate: "", + AlbumCountry: "us", + AlbumYear: 0, + AlbumMonth: 0, + AlbumDay: 0, + AlbumFavorite: false, + CreatedAt: time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC), + DeletedAt: nil, + }, } // CreateAlbumFixtures inserts known entities into the database for testing. diff --git a/internal/photoprism/moments.go b/internal/photoprism/moments.go index 031b6782f..f3cc302ac 100644 --- a/internal/photoprism/moments.go +++ b/internal/photoprism/moments.go @@ -6,6 +6,8 @@ import ( "runtime/debug" "strconv" + "github.com/dustin/go-humanize/english" + "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" @@ -54,6 +56,13 @@ func (w *Moments) Start() (err error) { defer mutex.MainWorker.Stop() + // Remove duplicate moments. + if removed, err := query.RemoveDuplicateMoments(); err != nil { + log.Warnf("moments: %s (remove duplicates)", err) + } else if removed > 0 { + log.Infof("moments: removed %s", english.Plural(removed, "duplicate", "duplicates")) + } + counts := query.Counts{} counts.Refresh() diff --git a/internal/query/moments.go b/internal/query/moments.go index b13c1d868..241fc8be2 100644 --- a/internal/query/moments.go +++ b/internal/query/moments.go @@ -7,6 +7,7 @@ import ( "github.com/gosimple/slug" + "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/maps" "github.com/photoprism/photoprism/pkg/txt" ) @@ -262,3 +263,24 @@ func MomentsLabels(threshold int) (results Moments, err error) { return results, nil } + +// RemoveDuplicateMoments deletes generated albums with duplicate slug or filter. +func RemoveDuplicateMoments() (removed int, err error) { + if res := UnscopedDb().Exec(`DELETE FROM links WHERE share_uid + IN (SELECT a.album_uid FROM albums a JOIN albums b ON a.album_type = b.album_type + AND a.album_type <> ? AND a.id > b.id WHERE (a.album_slug = b.album_slug + OR a.album_filter = b.album_filter) GROUP BY a.album_uid)`, entity.AlbumDefault); res.Error != nil { + return removed, err + } + + if res := UnscopedDb().Exec(`DELETE FROM albums WHERE id + IN (SELECT a.id FROM albums a JOIN albums b ON a.album_type = b.album_type + AND a.album_type <> ? AND a.id > b.id WHERE (a.album_slug = b.album_slug + OR a.album_filter = b.album_filter) GROUP BY a.album_uid)`, entity.AlbumDefault); res.Error != nil { + return removed, err + } else if res.RowsAffected > 0 { + removed = int(res.RowsAffected) + } + + return removed, nil +} diff --git a/internal/query/moments_test.go b/internal/query/moments_test.go index f1371a57e..ac00697e7 100644 --- a/internal/query/moments_test.go +++ b/internal/query/moments_test.go @@ -3,6 +3,8 @@ package query import ( "testing" + "github.com/dustin/go-humanize/english" + "github.com/stretchr/testify/assert" ) @@ -205,3 +207,14 @@ func TestMoment_Title(t *testing.T) { assert.Equal(t, "December", moment.Title()) }) } + +func TestRemoveDuplicateMoments(t *testing.T) { + t.Run("Success", func(t *testing.T) { + if removed, err := RemoveDuplicateMoments(); err != nil { + t.Fatal(err) + } else { + t.Logf("moments: removed %s", english.Plural(removed, "duplicate", "duplicates")) + assert.Equal(t, 2, removed) + } + }) +}