2020-12-12 22:02:14 +01:00
package entity
import (
2020-12-27 07:43:39 +01:00
"sync"
2020-12-12 22:02:14 +01:00
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/pkg/rnd"
)
2020-12-27 07:43:39 +01:00
var photoMergeMutex = sync . Mutex { }
2020-12-12 22:02:14 +01:00
// ResolvePrimary ensures there is only one primary file for a photo.
func ( m * Photo ) ResolvePrimary ( ) error {
var file File
if err := Db ( ) . Where ( "file_primary = 1 AND photo_id = ?" , m . ID ) . First ( & file ) . Error ; err == nil && file . ID > 0 {
return file . ResolvePrimary ( )
}
return nil
}
2021-11-24 14:32:40 +01:00
// Stackable tests if the photo may be stacked.
func ( m * Photo ) Stackable ( ) bool {
if ! m . HasID ( ) || m . PhotoStack == IsUnstacked || m . PhotoName == "" {
return false
}
return true
}
2020-12-12 22:02:14 +01:00
// Identical returns identical photos that can be merged.
func ( m * Photo ) Identical ( includeMeta , includeUuid bool ) ( identical Photos , err error ) {
2021-11-24 14:32:40 +01:00
if ! m . Stackable ( ) {
2020-12-12 22:02:14 +01:00
return identical , nil
}
2021-11-24 14:32:40 +01:00
includeMeta = includeMeta && m . TrustedLocation ( ) && m . TrustedTime ( )
includeUuid = includeUuid && rnd . IsUUID ( m . UUID )
2020-12-12 22:02:14 +01:00
switch {
2021-11-24 14:32:40 +01:00
case includeMeta && includeUuid :
2020-12-12 22:02:14 +01:00
if err := Db ( ) .
2021-11-24 14:32:40 +01:00
Where ( "(taken_at = ? AND taken_src = 'meta' AND place_src <> 'estimate' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) " +
2020-12-19 19:15:32 +01:00
"OR (uuid = ? AND photo_stack > -1)" +
2020-12-12 22:02:14 +01:00
"OR (photo_path = ? AND photo_name = ?)" ,
m . TakenAt , m . CellID , m . CameraSerial , m . CameraID , m . UUID , m . PhotoPath , m . PhotoName ) .
2020-12-27 07:43:39 +01:00
Order ( "photo_quality DESC, id ASC" ) . Find ( & identical ) . Error ; err != nil {
2020-12-12 22:02:14 +01:00
return identical , err
}
2021-11-24 14:32:40 +01:00
case includeMeta :
2020-12-12 22:02:14 +01:00
if err := Db ( ) .
2021-11-24 14:32:40 +01:00
Where ( "(taken_at = ? AND taken_src = 'meta' AND place_src <> 'estimate' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) " +
2020-12-12 22:02:14 +01:00
"OR (photo_path = ? AND photo_name = ?)" ,
m . TakenAt , m . CellID , m . CameraSerial , m . CameraID , m . PhotoPath , m . PhotoName ) .
2020-12-27 07:43:39 +01:00
Order ( "photo_quality DESC, id ASC" ) . Find ( & identical ) . Error ; err != nil {
2020-12-12 22:02:14 +01:00
return identical , err
}
2021-11-24 14:32:40 +01:00
case includeUuid :
2020-12-12 22:02:14 +01:00
if err := Db ( ) .
2020-12-19 19:15:32 +01:00
Where ( "(uuid = ? AND photo_stack > -1) OR (photo_path = ? AND photo_name = ?)" ,
2020-12-12 22:02:14 +01:00
m . UUID , m . PhotoPath , m . PhotoName ) .
2020-12-27 07:43:39 +01:00
Order ( "photo_quality DESC, id ASC" ) . Find ( & identical ) . Error ; err != nil {
2020-12-12 22:02:14 +01:00
return identical , err
}
default :
if err := Db ( ) .
Where ( "photo_path = ? AND photo_name = ?" , m . PhotoPath , m . PhotoName ) .
2020-12-27 07:43:39 +01:00
Order ( "photo_quality DESC, id ASC" ) . Find ( & identical ) . Error ; err != nil {
2020-12-12 22:02:14 +01:00
return identical , err
}
}
return identical , nil
}
// Merge photo with identical ones.
func ( m * Photo ) Merge ( mergeMeta , mergeUuid bool ) ( original Photo , merged Photos , err error ) {
2020-12-27 07:43:39 +01:00
photoMergeMutex . Lock ( )
defer photoMergeMutex . Unlock ( )
2020-12-12 22:02:14 +01:00
identical , err := m . Identical ( mergeMeta , mergeUuid )
if len ( identical ) < 2 || err != nil {
return Photo { } , merged , err
}
logResult := func ( res * gorm . DB ) {
if res . Error != nil {
log . Errorf ( "merge: %s" , res . Error . Error ( ) )
err = res . Error
}
}
for i , merge := range identical {
if i == 0 {
original = merge
log . Debugf ( "photo: merging id %d with %d identical" , original . ID , len ( identical ) - 1 )
continue
}
2021-08-29 13:26:05 +02:00
deleted := TimeStamp ( )
2020-12-12 22:02:14 +01:00
logResult ( UnscopedDb ( ) . Exec ( "UPDATE `files` SET photo_id = ?, photo_uid = ?, file_primary = 0 WHERE photo_id = ?" , original . ID , original . PhotoUID , merge . ID ) )
2021-08-29 13:26:05 +02:00
logResult ( UnscopedDb ( ) . Exec ( "UPDATE `photos` SET photo_quality = -1, deleted_at = ? WHERE id = ?" , TimeStamp ( ) , merge . ID ) )
2020-12-12 22:02:14 +01:00
switch DbDialect ( ) {
case MySQL :
logResult ( UnscopedDb ( ) . Exec ( "UPDATE IGNORE `photos_keywords` SET `photo_id` = ? WHERE photo_id = ?" , original . ID , merge . ID ) )
logResult ( UnscopedDb ( ) . Exec ( "UPDATE IGNORE `photos_labels` SET `photo_id` = ? WHERE photo_id = ?" , original . ID , merge . ID ) )
logResult ( UnscopedDb ( ) . Exec ( "UPDATE IGNORE `photos_albums` SET `photo_uid` = ? WHERE photo_uid = ?" , original . PhotoUID , merge . PhotoUID ) )
case SQLite :
logResult ( UnscopedDb ( ) . Exec ( "UPDATE OR IGNORE `photos_keywords` SET `photo_id` = ? WHERE photo_id = ?" , original . ID , merge . ID ) )
logResult ( UnscopedDb ( ) . Exec ( "UPDATE OR IGNORE `photos_labels` SET `photo_id` = ? WHERE photo_id = ?" , original . ID , merge . ID ) )
logResult ( UnscopedDb ( ) . Exec ( "UPDATE OR IGNORE `photos_albums` SET `photo_uid` = ? WHERE photo_uid = ?" , original . PhotoUID , merge . PhotoUID ) )
default :
2021-10-01 16:34:29 +02:00
log . Warnf ( "sql: unsupported dialect %s" , DbDialect ( ) )
2020-12-12 22:02:14 +01:00
}
merge . DeletedAt = & deleted
merge . PhotoQuality = - 1
merged = append ( merged , merge )
}
if original . ID != m . ID {
2021-08-29 13:26:05 +02:00
deleted := TimeStamp ( )
2020-12-12 22:02:14 +01:00
m . DeletedAt = & deleted
m . PhotoQuality = - 1
}
return original , merged , err
}