2022-02-27 17:32:54 +01:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-05-17 01:10:47 +02:00
|
|
|
|
2022-05-17 00:57:07 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/fs"
|
2022-02-27 17:32:54 +01:00
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
|
|
"github.com/photoprism/photoprism/internal/form"
|
2022-04-15 09:42:07 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/media"
|
2022-02-27 17:32:54 +01:00
|
|
|
)
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
const MegaByte = 1024 * 1024
|
|
|
|
|
2022-02-27 17:32:54 +01:00
|
|
|
// FileSelection represents a selection filter to include/exclude certain files.
|
|
|
|
type FileSelection struct {
|
2022-04-15 09:42:07 +02:00
|
|
|
MaxSize int
|
|
|
|
Media []string
|
|
|
|
OmitMedia []string
|
|
|
|
Types []string
|
|
|
|
OmitTypes []string
|
|
|
|
Primary bool
|
|
|
|
Originals bool
|
|
|
|
Hidden bool
|
|
|
|
Private bool
|
|
|
|
Archived bool
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// DownloadSelection selects files to download.
|
|
|
|
func DownloadSelection(mediaRaw, mediaSidecar, originals bool) FileSelection {
|
|
|
|
omitMedia := make([]string, 0, 2)
|
|
|
|
|
|
|
|
if !mediaRaw {
|
|
|
|
omitMedia = append(omitMedia, media.Raw.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if !mediaSidecar {
|
|
|
|
omitMedia = append(omitMedia, media.Sidecar.String())
|
|
|
|
}
|
|
|
|
|
2022-02-27 17:32:54 +01:00
|
|
|
return FileSelection{
|
2022-04-15 09:42:07 +02:00
|
|
|
OmitMedia: omitMedia,
|
|
|
|
Originals: originals,
|
|
|
|
Private: true,
|
|
|
|
Archived: true,
|
2023-02-22 23:14:43 +01:00
|
|
|
Hidden: true,
|
2022-04-15 09:42:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShareSelection selects files to share, for example for upload via WebDAV.
|
2022-05-16 23:59:28 +02:00
|
|
|
func ShareSelection(originals bool) FileSelection {
|
2022-05-17 00:57:07 +02:00
|
|
|
var omitMedia []string
|
|
|
|
var omitTypes []string
|
|
|
|
|
|
|
|
if !originals {
|
|
|
|
omitMedia = []string{
|
|
|
|
media.Unknown.String(),
|
|
|
|
media.Raw.String(),
|
|
|
|
media.Sidecar.String(),
|
|
|
|
media.Text.String(),
|
|
|
|
media.Other.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
omitTypes = []string{
|
|
|
|
fs.ImagePNG.String(),
|
|
|
|
fs.ImageWebP.String(),
|
|
|
|
fs.ImageTIFF.String(),
|
2022-09-15 00:43:08 +02:00
|
|
|
fs.ImageAVIF.String(),
|
2022-10-25 06:19:56 +02:00
|
|
|
fs.ImageHEIC.String(),
|
2022-05-17 00:57:07 +02:00
|
|
|
fs.ImageBMP.String(),
|
|
|
|
fs.ImageGIF.String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
return FileSelection{
|
2022-05-16 23:59:28 +02:00
|
|
|
Originals: originals,
|
2022-05-17 00:57:07 +02:00
|
|
|
OmitMedia: omitMedia,
|
|
|
|
OmitTypes: omitTypes,
|
2022-05-16 23:59:28 +02:00
|
|
|
Hidden: false,
|
|
|
|
Private: false,
|
|
|
|
Archived: false,
|
2022-04-15 09:42:07 +02:00
|
|
|
MaxSize: 1024 * MegaByte,
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectedFiles finds files based on the given selection form, e.g. for downloading or sharing.
|
|
|
|
func SelectedFiles(f form.Selection, o FileSelection) (results entity.Files, err error) {
|
|
|
|
if f.Empty() {
|
|
|
|
return results, errors.New("no items selected")
|
|
|
|
}
|
|
|
|
|
2022-05-16 23:59:28 +02:00
|
|
|
// Resolve photos in smart albums.
|
|
|
|
if photoIds, err := AlbumsPhotoUIDs(f.Albums, false, o.Private); err != nil {
|
|
|
|
log.Warnf("query: %s", err.Error())
|
|
|
|
} else if len(photoIds) > 0 {
|
|
|
|
f.Photos = append(f.Photos, photoIds...)
|
|
|
|
}
|
|
|
|
|
2022-02-27 17:32:54 +01:00
|
|
|
var concat string
|
|
|
|
switch DbDialect() {
|
|
|
|
case MySQL:
|
|
|
|
concat = "CONCAT(a.path, '/%')"
|
|
|
|
case SQLite3:
|
|
|
|
concat = "a.path || '/%'"
|
|
|
|
default:
|
|
|
|
return results, fmt.Errorf("unknown sql dialect: %s", DbDialect())
|
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Search condition.
|
2022-02-27 17:32:54 +01:00
|
|
|
where := fmt.Sprintf(`photos.photo_uid IN (?)
|
|
|
|
OR photos.place_id IN (?)
|
|
|
|
OR photos.photo_uid IN (SELECT photo_uid FROM files WHERE file_uid IN (?))
|
|
|
|
OR photos.photo_path IN (
|
|
|
|
SELECT a.path FROM folders a WHERE a.folder_uid IN (?) UNION
|
|
|
|
SELECT b.path FROM folders a JOIN folders b ON b.path LIKE %s WHERE a.folder_uid IN (?))
|
|
|
|
OR photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND album_uid IN (?))
|
|
|
|
OR files.file_uid IN (SELECT file_uid FROM %s m WHERE m.subj_uid IN (?))
|
2022-10-31 09:58:39 +01:00
|
|
|
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN labels l ON pl.label_id = l.id AND pl.uncertainty < 100 AND l.deleted_at IS NULL WHERE l.label_uid IN (?))
|
|
|
|
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN categories c ON c.label_id = pl.label_id AND pl.uncertainty < 100 JOIN labels lc ON lc.id = c.category_id AND lc.deleted_at IS NULL WHERE lc.label_uid IN (?))`,
|
2022-02-27 17:32:54 +01:00
|
|
|
concat, entity.Marker{}.TableName())
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Build search query.
|
2022-02-27 17:32:54 +01:00
|
|
|
s := UnscopedDb().Table("files").
|
|
|
|
Select("files.*").
|
|
|
|
Joins("JOIN photos ON photos.id = files.photo_id").
|
2022-04-15 09:42:07 +02:00
|
|
|
Where("files.file_missing = 0 AND files.file_name <> '' AND files.file_hash <> ''").
|
2022-02-27 17:32:54 +01:00
|
|
|
Where(where, f.Photos, f.Places, f.Files, f.Files, f.Files, f.Albums, f.Subjects, f.Labels, f.Labels).
|
|
|
|
Group("files.id")
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// File size limit?
|
|
|
|
if o.MaxSize > 0 {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.file_size < ?", o.MaxSize)
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Specific media types only?
|
|
|
|
if len(o.Media) > 0 {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.media_type IN (?)", o.Media)
|
2022-04-15 09:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Exclude media types?
|
|
|
|
if len(o.OmitMedia) > 0 {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.media_type NOT IN (?)", o.OmitMedia)
|
2022-04-15 09:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Specific file types only?
|
|
|
|
if len(o.Types) > 0 {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.file_type IN (?)", o.Types)
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Exclude file types?
|
|
|
|
if len(o.OmitTypes) > 0 {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.file_type NOT IN (?)", o.OmitTypes)
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2023-02-11 20:18:04 +01:00
|
|
|
// Previews files only?
|
2022-04-15 09:42:07 +02:00
|
|
|
if o.Primary {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.file_primary = 1")
|
2022-04-15 09:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Files in originals only?
|
|
|
|
if o.Originals {
|
2022-05-16 23:59:28 +02:00
|
|
|
s = s.Where("files.file_root = '/'")
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Exclude private?
|
|
|
|
if !o.Private {
|
|
|
|
s = s.Where("photos.photo_private <> 1")
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Exclude hidden photos?
|
|
|
|
if !o.Hidden {
|
|
|
|
s = s.Where("photos.photo_quality > -1")
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Exclude archived photos?
|
|
|
|
if !o.Archived {
|
|
|
|
s = s.Where("photos.deleted_at IS NULL")
|
2022-02-27 17:32:54 +01:00
|
|
|
}
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
// Find and return.
|
2022-02-27 17:32:54 +01:00
|
|
|
if result := s.Scan(&results); result.Error != nil {
|
|
|
|
return results, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|