1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Creating top pick job #683

This commit is contained in:
Patrik J. Braun 2023-07-29 13:18:04 +02:00
parent 041b984dbd
commit 35340b2c04
9 changed files with 162 additions and 33 deletions

14
package-lock.json generated
View File

@ -23,6 +23,7 @@
"image-size": "1.0.2",
"locale": "0.1.0",
"node-geocoder": "4.2.0",
"nodemailer": "^6.9.4",
"reflect-metadata": "0.1.13",
"sharp": "0.31.3",
"ts-exif-parser": "0.2.2",
@ -16598,6 +16599,14 @@
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
"dev": true
},
"node_modules/nodemailer": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz",
"integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/non-layered-tidy-tree-layout": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
@ -35797,6 +35806,11 @@
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
"dev": true
},
"nodemailer": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz",
"integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA=="
},
"non-layered-tidy-tree-layout": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",

View File

@ -46,13 +46,14 @@
"image-size": "1.0.2",
"locale": "0.1.0",
"node-geocoder": "4.2.0",
"nodemailer": "^6.9.4",
"reflect-metadata": "0.1.13",
"sharp": "0.31.3",
"ts-exif-parser": "0.2.2",
"ts-node-iptc": "1.0.11",
"typeconfig": "2.1.0",
"xml2js": "0.4.23",
"typeorm": "0.3.12"
"typeorm": "0.3.12",
"xml2js": "0.4.23"
},
"devDependencies": {
"@angular-builders/custom-webpack": "15.0.0",

View File

@ -3,9 +3,7 @@ import {promises as fsp} from 'fs';
import * as archiver from 'archiver';
import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {
ParentDirectoryDTO,
} from '../../common/entities/DirectoryDTO';
import {ParentDirectoryDTO,} from '../../common/entities/DirectoryDTO';
import {ObjectManagers} from '../model/ObjectManagers';
import {ContentWrapper} from '../../common/entities/ConentWrapper';
import {ProjectPath} from '../ProjectPath';
@ -14,13 +12,11 @@ import {UserDTOUtils} from '../../common/entities/UserDTO';
import {MediaDTO, MediaDTOUtils} from '../../common/entities/MediaDTO';
import {QueryParams} from '../../common/QueryParams';
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
import {
SearchQueryDTO,
SearchQueryTypes,
} from '../../common/entities/SearchQueryDTO';
import {SearchQueryDTO, SearchQueryTypes,} from '../../common/entities/SearchQueryDTO';
import {LocationLookupException} from '../exceptions/LocationLookupException';
import {SupportedFormats} from '../../common/SupportedFormats';
import {ServerTime} from './ServerTimingMWs';
import {SortingMethods} from '../../common/entities/SortingMethods';
export class GalleryMWs {
@ServerTime('1.db', 'List Directory')
@ -325,16 +321,16 @@ export class GalleryMWs {
req.params['searchQueryDTO'] as string
);
const photo =
await ObjectManagers.getInstance().SearchManager.getRandomPhoto(query);
if (!photo) {
const photos =
await ObjectManagers.getInstance().SearchManager.getNMedia(query, [SortingMethods.random], 1, true);
if (!photos || photos.length !== 1) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
}
req.params['mediaPath'] = path.join(
photo.directory.path,
photo.directory.name,
photo.name
photos[0].directory.path,
photos[0].directory.name,
photos[0].name
);
return next();
} catch (e) {

View File

@ -30,11 +30,11 @@ import {
} from '../../../common/entities/SearchQueryDTO';
import {GalleryManager} from './GalleryManager';
import {ObjectManagers} from '../ObjectManagers';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {DatabaseType} from '../../../common/config/private/PrivateConfig';
import {Utils} from '../../../common/Utils';
import {FileEntity} from './enitites/FileEntity';
import {SQL_COLLATE} from './enitites/EntityUtils';
import {SortingMethods} from '../../../common/entities/SortingMethods';
export class SearchManager {
private DIRECTORY_SELECT = [
@ -348,19 +348,61 @@ export class SearchManager {
return result;
}
public async getRandomPhoto(query: SearchQueryDTO): Promise<PhotoDTO> {
private static setSorting<T>(
query: SelectQueryBuilder<T>,
sortings: SortingMethods[]
): SelectQueryBuilder<T> {
if (!sortings || !Array.isArray(sortings)) {
return query;
}
if (sortings.includes(SortingMethods.random) && sortings.length > 0) {
throw new Error('Error during applying sorting: Can\' randomize and also sort the result. Bad input:' + sortings.map(s => SortingMethods[s]).join(', '));
}
for (const sort of sortings) {
switch (sort) {
case SortingMethods.descDate:
query.addOrderBy('media.metadata.creationDate', 'DESC');
break;
case SortingMethods.ascDate:
query.addOrderBy('media.metadata.creationDate', 'ASC');
break;
case SortingMethods.descRating:
query.addOrderBy('media.metadata.rating', 'DESC');
break;
case SortingMethods.ascRating:
query.addOrderBy('media.metadata.rating', 'ASC');
break;
case SortingMethods.descName:
query.addOrderBy('media.name', 'DESC');
break;
case SortingMethods.ascName:
query.addOrderBy('media.name', 'ASC');
break;
case SortingMethods.random:
if (Config.Database.type === DatabaseType.mysql) {
query.groupBy('RAND(), media.id');
}
query.groupBy('RANDOM()');
break;
}
}
return query;
}
public async getNMedia(query: SearchQueryDTO, sortings: SortingMethods[], take: number, photoOnly = false) {
const connection = await SQLConnection.getConnection();
const sqlQuery: SelectQueryBuilder<PhotoEntity> = connection
.getRepository(PhotoEntity)
.getRepository(photoOnly ? PhotoEntity : MediaEntity)
.createQueryBuilder('media')
.select(['media', ...this.DIRECTORY_SELECT])
.innerJoin('media.directory', 'directory')
.where(await this.prepareAndBuildWhereQuery(query));
SearchManager.setSorting(sqlQuery, sortings);
return sqlQuery.limit(take).getMany();
if (Config.Database.type === DatabaseType.mysql) {
return await sqlQuery.groupBy('RAND(), media.id').limit(1).getOne();
}
return await sqlQuery.groupBy('RANDOM()').limit(1).getOne();
}
public async getCount(query: SearchQueryDTO): Promise<number> {

View File

@ -10,6 +10,7 @@ import {PreviewRestJob} from './jobs/PreviewResetJob';
import {GPXCompressionJob} from './jobs/GPXCompressionJob';
import {AlbumRestJob} from './jobs/AlbumResetJob';
import {GPXCompressionResetJob} from './jobs/GPXCompressionResetJob';
import {TopPickSendJob} from './jobs/TopPickSendJob';
export class JobRepository {
private static instance: JobRepository = null;
@ -45,3 +46,4 @@ JobRepository.Instance.register(new GPXCompressionJob());
JobRepository.Instance.register(new TempFolderCleaningJob());
JobRepository.Instance.register(new AlbumRestJob());
JobRepository.Instance.register(new GPXCompressionResetJob());
JobRepository.Instance.register(new TopPickSendJob());

View File

@ -1,11 +1,6 @@
import {ObjectManagers} from '../../ObjectManagers';
import {
ConfigTemplateEntry,
DefaultsJobs,
} from '../../../../common/entities/job/JobDTO';
import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
import {Job} from './Job';
import { Config } from '../../../../common/config/private/Config';
import { DatabaseType } from '../../../../common/config/private/PrivateConfig';
export class PreviewFillingJob extends Job {
public readonly Name = DefaultsJobs[DefaultsJobs['Preview Filling']];
@ -18,7 +13,7 @@ export class PreviewFillingJob extends Job {
}
protected async init(): Promise<void> {
// abstract function
this.status = 'Persons';
}
protected async step(): Promise<boolean> {

View File

@ -0,0 +1,74 @@
import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
import {Job} from './Job';
import {backendTexts} from '../../../../common/BackendTexts';
import {SortingMethods} from '../../../../common/entities/SortingMethods';
import {DatePatternFrequency, DatePatternSearch, SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
import {ObjectManagers} from '../../ObjectManagers';
import {PhotoEntity} from '../../database/enitites/PhotoEntity';
export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: SortingMethods[], pickAmount: number }> {
public readonly Name = DefaultsJobs[DefaultsJobs['Top Pick Sending']];
public readonly Supported: boolean = true;
public readonly ConfigTemplate: ConfigTemplateEntry[] = [
{
id: 'searchQuery',
type: 'SearchQuery',
name: backendTexts.searchQuery.name,
description: backendTexts.searchQuery.description,
defaultValue: {
type: SearchQueryTypes.date_pattern,
daysLength: 7,
frequency: DatePatternFrequency.every_year
} as DatePatternSearch,
}, {
id: 'sortby',
type: 'sort-array',
name: backendTexts.sortBy.name,
description: backendTexts.sortBy.description,
defaultValue: [SortingMethods.descRating],
}, {
id: 'pickAmount',
type: 'number',
name: backendTexts.pickAmount.name,
description: backendTexts.pickAmount.description,
defaultValue: 5,
},
];
private status: 'Listing' | 'Sending' = 'Listing';
private mediaList: PhotoEntity[] = [];
protected async init(): Promise<void> {
this.status = 'Listing';
this.mediaList = [];
this.Progress.Left = 2;
}
protected async step(): Promise<boolean> {
switch (this.status) {
case 'Listing':
if (!await this.stepListing()) {
this.status = 'Sending';
}
return true;
case 'Sending':
await this.stepSending();
}
return false;
}
private async stepListing(): Promise<boolean> {
this.Progress.log('Collecting Photos and videos to Send');
this.Progress.Processed++;
this.mediaList = await ObjectManagers.getInstance().SearchManager.getNMedia(this.config.searchQuery, this.config.sortBy, this.config.pickAmount);
// console.log(this.mediaList);
return false;
}
private async stepSending(): Promise<boolean> {
return false;
}
}

View File

@ -3,4 +3,8 @@ export const backendTexts = {
indexedFilesOnly: { name: 10, description: 12 },
sizeToGenerate: { name: 20, description: 22 },
indexChangesOnly: { name: 30, description: 32 },
searchQuery: { name: 40, description: 42 },
sortBy: { name: 40, description: 42 },
pickAmount: { name: 40, description: 42 },
};

View File

@ -1,6 +1,6 @@
import {backendText} from '../../BackendTexts';
export type fieldType = 'string' | 'number' | 'boolean' | 'number-array';
export type fieldType = 'string' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array';
export enum DefaultsJobs {
Indexing = 1,
@ -13,7 +13,8 @@ export enum DefaultsJobs {
'Preview Reset' = 8,
'GPX Compression' = 9,
'Album Reset' = 10,
'Delete Compressed GPX' = 11
'Delete Compressed GPX' = 11,
'Top Pick Sending' = 12
}
export interface ConfigTemplateEntry {