2020-01-05 14:18:40 +01:00
package query
2019-12-11 07:37:39 +01:00
2020-04-21 10:23:27 +02:00
import (
2020-12-11 19:39:42 +01:00
"fmt"
2020-07-20 19:48:31 +02:00
"path"
2020-05-07 21:46:00 +02:00
"strings"
2020-04-21 10:23:27 +02:00
"github.com/photoprism/photoprism/internal/entity"
)
2019-12-11 07:37:39 +01:00
2020-05-24 22:16:06 +02:00
// FilesByPath returns a slice of files in a given originals folder.
2020-06-22 21:21:02 +02:00
func FilesByPath ( limit , offset int , rootName , pathName string ) ( files entity . Files , err error ) {
2020-05-24 22:16:06 +02:00
if strings . HasPrefix ( pathName , "/" ) {
pathName = pathName [ 1 : ]
}
err = Db ( ) .
Table ( "files" ) . Select ( "files.*" ) .
Joins ( "JOIN photos ON photos.id = files.photo_id AND photos.deleted_at IS NULL" ) .
Where ( "files.file_missing = 0" ) .
2020-05-27 13:40:21 +02:00
Where ( "files.file_root = ? AND photos.photo_path = ?" , rootName , pathName ) .
2020-05-24 22:16:06 +02:00
Order ( "files.file_name" ) .
2020-06-22 21:21:02 +02:00
Limit ( limit ) . Offset ( offset ) .
2020-05-24 22:16:06 +02:00
Find ( & files ) . Error
return files , err
}
2020-06-07 10:09:35 +02:00
// Files returns not-missing and not-deleted file entities in the range of limit and offset sorted by id.
2020-06-22 21:21:02 +02:00
func Files ( limit , offset int , pathName string , includeMissing bool ) ( files entity . Files , err error ) {
2020-05-24 22:16:06 +02:00
if strings . HasPrefix ( pathName , "/" ) {
pathName = pathName [ 1 : ]
2020-05-07 21:46:00 +02:00
}
2020-06-07 10:09:35 +02:00
stmt := Db ( )
if ! includeMissing {
stmt = stmt . Where ( "file_missing = 0" )
}
2020-05-07 21:46:00 +02:00
2020-05-24 22:16:06 +02:00
if pathName != "" {
stmt = stmt . Where ( "files.file_name LIKE ?" , pathName + "/%" )
2020-05-07 21:46:00 +02:00
}
err = stmt . Order ( "id" ) . Limit ( limit ) . Offset ( offset ) . Find ( & files ) . Error
2019-12-11 07:37:39 +01:00
2020-05-07 19:42:04 +02:00
return files , err
2019-12-11 07:37:39 +01:00
}
2021-08-21 16:36:00 +02:00
// FilesByUID finds files for the given UIDs.
2020-06-01 09:45:24 +02:00
func FilesByUID ( u [ ] string , limit int , offset int ) ( files entity . Files , err error ) {
2020-05-23 20:58:58 +02:00
if err := Db ( ) . Where ( "(photo_uid IN (?) AND file_primary = 1) OR file_uid IN (?)" , u , u ) . Preload ( "Photo" ) . Limit ( limit ) . Offset ( offset ) . Find ( & files ) . Error ; err != nil {
2019-12-11 07:37:39 +01:00
return files , err
}
return files , nil
}
2021-08-21 16:36:00 +02:00
// FileByPhotoUID finds a file for the given photo UID.
2020-05-23 20:58:58 +02:00
func FileByPhotoUID ( u string ) ( file entity . File , err error ) {
2020-06-22 15:16:26 +02:00
if err := Db ( ) . Where ( "photo_uid = ? AND file_primary = 1" , u ) . Preload ( "Photo" ) . First ( & file ) . Error ; err != nil {
2019-12-11 07:37:39 +01:00
return file , err
}
return file , nil
}
2021-08-21 16:36:00 +02:00
// VideoByPhotoUID finds a video for the given photo UID.
2020-05-23 20:58:58 +02:00
func VideoByPhotoUID ( u string ) ( file entity . File , err error ) {
2020-06-22 15:16:26 +02:00
if err := Db ( ) . Where ( "photo_uid = ? AND file_video = 1" , u ) . Preload ( "Photo" ) . First ( & file ) . Error ; err != nil {
2020-05-20 10:42:48 +02:00
return file , err
}
return file , nil
}
2021-08-21 16:36:00 +02:00
// FileByUID finds a file entity for the given UID.
2020-05-23 20:58:58 +02:00
func FileByUID ( uid string ) ( file entity . File , err error ) {
2020-06-22 15:16:26 +02:00
if err := Db ( ) . Where ( "file_uid = ?" , uid ) . Preload ( "Photo" ) . First ( & file ) . Error ; err != nil {
2019-12-11 07:37:39 +01:00
return file , err
}
return file , nil
}
2020-05-20 10:42:48 +02:00
// FileByHash finds a file with a given hash string.
2020-05-08 15:41:01 +02:00
func FileByHash ( fileHash string ) ( file entity . File , err error ) {
2020-06-22 15:16:26 +02:00
if err := Db ( ) . Where ( "file_hash = ?" , fileHash ) . Preload ( "Photo" ) . First ( & file ) . Error ; err != nil {
2019-12-11 07:37:39 +01:00
return file , err
}
return file , nil
}
2020-04-21 10:23:27 +02:00
2020-12-11 19:39:42 +01:00
// RenameFile renames an indexed file.
func RenameFile ( srcRoot , srcName , destRoot , destName string ) error {
if srcRoot == "" || srcName == "" || destRoot == "" || destName == "" {
2022-01-05 11:40:44 +01:00
return fmt . Errorf ( "cannot rename %s/%s to %s/%s" , srcRoot , srcName , destRoot , destName )
2020-12-11 19:39:42 +01:00
}
2020-12-11 22:09:11 +01:00
return Db ( ) . Exec ( "UPDATE files SET file_root = ?, file_name = ?, file_missing = 0, deleted_at = NULL WHERE file_root = ? AND file_name = ?" , destRoot , destName , srcRoot , srcName ) . Error
2020-12-11 19:39:42 +01:00
}
2020-04-21 10:23:27 +02:00
// SetPhotoPrimary sets a new primary image file for a photo.
2022-03-30 20:36:25 +02:00
func SetPhotoPrimary ( photoUID , fileUID string ) ( err error ) {
2021-01-24 20:40:40 +01:00
if photoUID == "" {
return fmt . Errorf ( "photo uid is missing" )
}
var files [ ] string
if fileUID != "" {
// Do nothing.
2022-01-05 16:37:19 +01:00
} else if err := Db ( ) . Model ( entity . File { } ) . Where ( "photo_uid = ? AND file_missing = 0 AND file_type = 'jpg'" , photoUID ) . Order ( "file_width DESC, file_hdr DESC" ) . Limit ( 1 ) . Pluck ( "file_uid" , & files ) . Error ; err != nil {
2021-01-24 20:40:40 +01:00
return err
} else if len ( files ) == 0 {
2022-01-05 11:40:44 +01:00
return fmt . Errorf ( "cannot find primary file for %s" , photoUID )
2021-01-24 20:40:40 +01:00
} else {
fileUID = files [ 0 ]
}
if fileUID == "" {
return fmt . Errorf ( "file uid is missing" )
}
2022-03-30 20:36:25 +02:00
if err = Db ( ) . Model ( entity . File { } ) .
Where ( "photo_uid = ? AND file_uid <> ?" , photoUID , fileUID ) .
2022-04-04 00:02:08 +02:00
Update ( "file_primary" , 0 ) . Error ; err != nil {
2022-03-30 20:36:25 +02:00
return err
} else if err = Db ( ) .
Model ( entity . File { } ) . Where ( "photo_uid = ? AND file_uid = ?" , photoUID , fileUID ) .
2022-04-04 00:02:08 +02:00
Update ( "file_primary" , 1 ) . Error ; err != nil {
2022-03-30 20:36:25 +02:00
return err
} else {
entity . File { PhotoUID : photoUID } . RegenerateIndex ( )
}
return nil
2020-04-21 10:23:27 +02:00
}
2020-05-18 15:45:55 +02:00
// SetFileError updates the file error column.
2020-05-23 20:58:58 +02:00
func SetFileError ( fileUID , errorString string ) {
2022-04-04 00:02:08 +02:00
if err := Db ( ) . Model ( entity . File { } ) . Where ( "file_uid = ?" , fileUID ) . Update ( "file_error" , errorString ) . Error ; err != nil {
2022-04-03 12:43:21 +02:00
log . Errorf ( "files: %s (set error)" , err . Error ( ) )
2020-05-18 15:45:55 +02:00
}
}
2020-07-17 12:47:12 +02:00
type FileMap map [ string ] int64
// IndexedFiles returns a map of already indexed files with their mod time unix timestamp as value.
func IndexedFiles ( ) ( result FileMap , err error ) {
result = make ( FileMap )
2020-07-20 19:48:31 +02:00
type File struct {
FileRoot string
2020-07-17 12:47:12 +02:00
FileName string
2020-07-17 16:09:55 +02:00
ModTime int64
2020-07-17 12:47:12 +02:00
}
2020-07-20 19:48:31 +02:00
// Query known duplicates.
var duplicates [ ] File
2020-07-17 12:47:12 +02:00
2020-07-20 19:48:31 +02:00
if err := UnscopedDb ( ) . Raw ( "SELECT file_root, file_name, mod_time FROM duplicates" ) . Scan ( & duplicates ) . Error ; err != nil {
return result , err
}
for _ , row := range duplicates {
result [ path . Join ( row . FileRoot , row . FileName ) ] = row . ModTime
}
// Query indexed files.
var files [ ] File
2020-07-17 12:47:12 +02:00
2020-11-21 23:28:03 +01:00
if err := UnscopedDb ( ) . Raw ( "SELECT file_root, file_name, mod_time FROM files WHERE file_missing = 0 AND deleted_at IS NULL" ) . Scan ( & files ) . Error ; err != nil {
2020-07-17 12:47:12 +02:00
return result , err
}
for _ , row := range files {
2020-07-20 19:48:31 +02:00
result [ path . Join ( row . FileRoot , row . FileName ) ] = row . ModTime
2020-07-17 12:47:12 +02:00
}
return result , err
}
2021-09-30 15:50:10 +02:00
2021-09-30 16:11:45 +02:00
// OrphanFiles finds files without a photo.
2021-09-30 15:50:10 +02:00
func OrphanFiles ( ) ( files entity . Files , err error ) {
err = UnscopedDb ( ) .
Raw ( ` SELECT * FROM files WHERE photo_id NOT IN (SELECT id FROM photos) ` ) .
Find ( & files ) . Error
return files , err
}