diff --git a/package-lock.json b/package-lock.json
index 17cf570a..e1cfb183 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14296,7 +14296,8 @@
},
"lodash": {
"version": "4.17.20",
- "resolved": "",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
},
"minimist": {
@@ -21616,7 +21617,8 @@
},
"y18n": {
"version": "3.2.1",
- "resolved": "",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yallist": {
diff --git a/src/backend/model/database/sql/SearchManager.ts b/src/backend/model/database/sql/SearchManager.ts
index 8af49aa5..a4c96f3a 100644
--- a/src/backend/model/database/sql/SearchManager.ts
+++ b/src/backend/model/database/sql/SearchManager.ts
@@ -33,6 +33,7 @@ import {ISQLGalleryManager} from './IGalleryManager';
import {ISQLSearchManager} from './ISearchManager';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {Utils} from '../../../../common/Utils';
+import {FileEntity} from './enitites/FileEntity';
export class SearchManager implements ISQLSearchManager {
@@ -156,13 +157,15 @@ export class SearchManager implements ISQLSearchManager {
const facesQuery = Config.Server.Database.type === DatabaseType.mysql ?
'CONCAT(\'[\' , GROUP_CONCAT( \'{"name": "\' , person.name , \'", "box": {"top":\' , faces.box.top , \', "left":\' , faces.box.left , \', "height":\' , faces.box.height ,\', "width":\' , faces.box.width , \'}}\' ) ,\']\') as media_metadataFaces' :
'\'[\' || GROUP_CONCAT( \'{"name": "\' || person.name || \'", "box": {"top":\' || faces.box.top || \', "left":\' || faces.box.left || \', "height":\' || faces.box.height ||\', "width":\' || faces.box.width || \'}}\' ) ||\']\' as media_metadataFaces';
+ const directorySelect = ['directory.id', 'directory.name', 'directory.path'];
+
const rawAndEntries = await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
- .select(['media', facesQuery])
+ .select(['media', ...directorySelect, facesQuery])
.where(this.buildWhereQuery(query))
- .leftJoinAndSelect('media.directory', 'directory')
+ .leftJoin('media.directory', 'directory')
.leftJoin('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.limit(Config.Client.Search.maxMediaResult + 1)
@@ -181,6 +184,20 @@ export class SearchManager implements ISQLSearchManager {
result.resultOverflow = true;
}
+ if (Config.Client.Search.listMetafiles === true) {
+ result.metaFile = await connection.getRepository(FileEntity)
+ .createQueryBuilder('file')
+ .select(['file', ...directorySelect])
+ .innerJoin(q => q.from(MediaEntity, 'media')
+ .select('distinct directory.id')
+ .where(this.buildWhereQuery(query))
+ .leftJoin('media.directory', 'directory'),
+ 'dir',
+ 'file.directory=dir.id')
+ .leftJoin('file.directory', 'directory')
+ .getMany();
+ }
+
if (Config.Client.Search.listDirectories === true) {
const dirQuery = this.filterDirectoryQuery(query);
if (dirQuery !== null) {
diff --git a/src/backend/model/database/sql/enitites/MediaEntity.ts b/src/backend/model/database/sql/enitites/MediaEntity.ts
index 51295297..1c125a9c 100644
--- a/src/backend/model/database/sql/enitites/MediaEntity.ts
+++ b/src/backend/model/database/sql/enitites/MediaEntity.ts
@@ -1,10 +1,10 @@
-import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique, ValueTransformer} from 'typeorm';
+import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm';
import {DirectoryEntity} from './DirectoryEntity';
import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../../common/entities/MediaDTO';
import {OrientationTypes} from 'ts-exif-parser';
-import {CameraMetadataEntity, PositionMetaDataEntity} from './PhotoEntity';
import {FaceRegionEntry} from './FaceRegionEntry';
import {columnCharsetCS} from './EntityUtils';
+import {CameraMetadata, GPSMetadata, PositionMetaData} from '../../../../../common/entities/PhotoDTO';
export class MediaDimensionEntity implements MediaDimension {
@@ -16,6 +16,80 @@ export class MediaDimensionEntity implements MediaDimension {
}
+export class CameraMetadataEntity implements CameraMetadata {
+
+ @Column('int', {nullable: true, unsigned: true})
+ ISO: number;
+
+
+ @Column({
+ type: 'text', nullable: true,
+ charset: columnCharsetCS.charset,
+ collation: columnCharsetCS.collation
+ })
+ model: string;
+
+
+ @Column({
+ type: 'text', nullable: true,
+ charset: columnCharsetCS.charset,
+ collation: columnCharsetCS.collation
+ })
+ make: string;
+
+ @Column('float', {nullable: true})
+ fStop: number;
+
+ @Column('float', {nullable: true})
+ exposure: number;
+
+ @Column('float', {nullable: true})
+ focalLength: number;
+
+ @Column('text', {nullable: true})
+ lens: string;
+}
+
+
+export class GPSMetadataEntity implements GPSMetadata {
+
+ @Column('float', {nullable: true})
+ latitude: number;
+ @Column('float', {nullable: true})
+ longitude: number;
+ @Column('int', {nullable: true})
+ altitude: number;
+}
+
+
+export class PositionMetaDataEntity implements PositionMetaData {
+
+ @Column(type => GPSMetadataEntity)
+ GPSData: GPSMetadataEntity;
+
+ @Column({
+ type: 'text', nullable: true,
+ charset: columnCharsetCS.charset,
+ collation: columnCharsetCS.collation
+ })
+ country: string;
+
+ @Column({
+ type: 'text', nullable: true,
+ charset: columnCharsetCS.charset,
+ collation: columnCharsetCS.collation
+ })
+ state: string;
+
+ @Column({
+ type: 'text', nullable: true,
+ charset: columnCharsetCS.charset,
+ collation: columnCharsetCS.collation
+ })
+ city: string;
+}
+
+
export class MediaMetadataEntity implements MediaMetadata {
@Column('text')
caption: string;
diff --git a/src/backend/model/database/sql/enitites/PhotoEntity.ts b/src/backend/model/database/sql/enitites/PhotoEntity.ts
index 794e3490..dae4a539 100644
--- a/src/backend/model/database/sql/enitites/PhotoEntity.ts
+++ b/src/backend/model/database/sql/enitites/PhotoEntity.ts
@@ -3,78 +3,7 @@ import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData}
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
import {columnCharsetCS} from './EntityUtils';
-export class CameraMetadataEntity implements CameraMetadata {
- @Column('int', {nullable: true, unsigned: true})
- ISO: number;
-
-
- @Column({
- type: 'text', nullable: true,
- charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation
- })
- model: string;
-
-
- @Column({
- type: 'text', nullable: true,
- charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation
- })
- make: string;
-
- @Column('float', {nullable: true})
- fStop: number;
-
- @Column('float', {nullable: true})
- exposure: number;
-
- @Column('float', {nullable: true})
- focalLength: number;
-
- @Column('text', {nullable: true})
- lens: string;
-}
-
-
-export class GPSMetadataEntity implements GPSMetadata {
-
- @Column('float', {nullable: true})
- latitude: number;
- @Column('float', {nullable: true})
- longitude: number;
- @Column('int', {nullable: true})
- altitude: number;
-}
-
-
-export class PositionMetaDataEntity implements PositionMetaData {
-
- @Column(type => GPSMetadataEntity)
- GPSData: GPSMetadataEntity;
-
- @Column({
- type: 'text', nullable: true,
- charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation
- })
- country: string;
-
- @Column({
- type: 'text', nullable: true,
- charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation
- })
- state: string;
-
- @Column({
- type: 'text', nullable: true,
- charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation
- })
- city: string;
-}
export class PhotoMetadataEntity extends MediaMetadataEntity implements PhotoMetadata {
diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts
index 57bf3f0d..3db97eaf 100644
--- a/src/common/config/public/ClientConfig.ts
+++ b/src/common/config/public/ClientConfig.ts
@@ -31,6 +31,10 @@ export class ClientSearchConfig {
maxMediaResult: number = 10000;
@ConfigProperty({description: 'Search returns also with directories, not just media'})
listDirectories: boolean = false;
+ @ConfigProperty({
+ description: 'Search also returns with metafiles from directories that contain a media file of the matched search result'
+ })
+ listMetafiles: boolean = false;
@ConfigProperty({type: 'unsignedInt'})
maxDirectoryResult: number = 200;
}
diff --git a/src/frontend/app/ui/settings/search/search.settings.component.html b/src/frontend/app/ui/settings/search/search.settings.component.html
index 4202e699..31c83665 100644
--- a/src/frontend/app/ui/settings/search/search.settings.component.html
+++ b/src/frontend/app/ui/settings/search/search.settings.component.html
@@ -52,6 +52,14 @@
[simplifiedMode]="simplifiedMode">
+
+
+
diff --git a/src/frontend/translate/messages.en.xlf b/src/frontend/translate/messages.en.xlf
index ab8914c1..f1419dd1 100644
--- a/src/frontend/translate/messages.en.xlf
+++ b/src/frontend/translate/messages.en.xlf
@@ -2828,6 +2828,14 @@
Shows albums tab in the top bar and enables creating saved searches.
+
+
+ List metafiles
+
+
+
+ Search also returns with metafiles from directories that contain a media file of the matched search result
+