diff --git a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index b7d361a..5a1eecb 100644 --- a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -52,17 +52,22 @@ export class ThumbnailGeneratorMWs { return next(); } - const cw: ContentWrapper = req.resultPipe; - if (cw.notModified === true) { - return next(); - } - if (cw.directory) { - ThumbnailGeneratorMWs.addThInfoTODir(cw.directory); - } - if (cw.searchResult) { - ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.media); - } + try { + const cw: ContentWrapper = req.resultPipe; + if (cw.notModified === true) { + return next(); + } + if (cw.directory) { + ThumbnailGeneratorMWs.addThInfoTODir(cw.directory); + } + if (cw.searchResult && cw.searchResult.media) { + ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.media); + } + } catch (error) { + return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result', error.toString())); + + } return next(); @@ -102,18 +107,14 @@ export class ThumbnailGeneratorMWs { } private static addThInfoTODir(directory: DirectoryDTO) { - if (typeof directory.media === 'undefined') { - directory.media = []; + if (typeof directory.media !== 'undefined') { + ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); } - if (typeof directory.directories === 'undefined') { - directory.directories = []; + if (typeof directory.directories !== 'undefined') { + for (let i = 0; i < directory.directories.length; i++) { + ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]); + } } - ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); - - for (let i = 0; i < directory.directories.length; i++) { - ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]); - } - } private static addThInfoToPhotos(photos: MediaDTO[]) { @@ -135,7 +136,6 @@ export class ThumbnailGeneratorMWs { if (fs.existsSync(iconPath) === true) { photos[i].readyIcon = true; } - } } diff --git a/backend/model/sql/GalleryManager.ts b/backend/model/sql/GalleryManager.ts index b4b4c11..f2a6840 100644 --- a/backend/model/sql/GalleryManager.ts +++ b/backend/model/sql/GalleryManager.ts @@ -13,7 +13,7 @@ import {ISQLGalleryManager} from './IGalleryManager'; import {DatabaseType, ReIndexingSensitivity} from '../../../common/config/private/IPrivateConfig'; import {PhotoDTO} from '../../../common/entities/PhotoDTO'; import {OrientationType} from '../../../common/entities/RandomQueryDTO'; -import {Brackets, Connection} from 'typeorm'; +import {Brackets, Connection, Transaction, TransactionRepository, Repository} from 'typeorm'; import {MediaEntity} from './enitites/MediaEntity'; import {MediaDTO} from '../../../common/entities/MediaDTO'; import {VideoEntity} from './enitites/VideoEntity'; @@ -23,6 +23,9 @@ import {NotificationManager} from '../NotifocationManager'; export class GalleryManager implements IGalleryManager, ISQLGalleryManager { + private savingQueue: DirectoryDTO[] = []; + private isSaving = false; + protected async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise { const query = connection .getRepository(DirectoryEntity) @@ -34,7 +37,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { .leftJoinAndSelect('directory.directories', 'directories') .leftJoinAndSelect('directory.media', 'media'); - if (Config.Client.MetaFile.enabled == true) { + if (Config.Client.MetaFile.enabled === true) { query.leftJoinAndSelect('directory.metaFile', 'metaFile'); } @@ -75,15 +78,17 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { public async listDirectory(relativeDirectoryName: string, knownLastModified?: number, knownLastScanned?: number): Promise { + relativeDirectoryName = path.normalize(path.join('.' + path.sep, relativeDirectoryName)); const directoryName = path.basename(relativeDirectoryName); const directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep); const connection = await SQLConnection.getConnection(); const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); const lastModified = Math.max(stat.ctime.getTime(), stat.mtime.getTime()); - const dir = await this.selectParentDir(connection, directoryName, directoryParent); + const dir = await this.selectParentDir(connection, directoryName, directoryParent); + if (dir && dir.lastScanned != null) { // If it seems that the content did not changed, do not work on it if (knownLastModified && knownLastScanned @@ -135,7 +140,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { scannedDirectory.media.forEach(p => p.readyThumbnails = []); resolve(scannedDirectory); - await this.saveToDB(scannedDirectory); + this.queueForSave(scannedDirectory).catch(console.error); } catch (error) { NotificationManager.warning('Unknown indexing error for: ' + relativeDirectoryName, error.toString()); @@ -204,139 +209,151 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { } + // Todo fix it, once typeorm support connection pools ofr sqlite + protected async queueForSave(scannedDirectory: DirectoryDTO) { + if (this.savingQueue.findIndex(dir => dir.name === scannedDirectory.name && + dir.path === scannedDirectory.path && + dir.lastModified === scannedDirectory.lastModified && + dir.lastScanned === scannedDirectory.lastScanned && + (dir.media || dir.media.length) === (scannedDirectory.media || scannedDirectory.media.length) && + (dir.metaFile || dir.metaFile.length) === (scannedDirectory.metaFile || scannedDirectory.metaFile.length)) !== -1) { + return; + } + this.savingQueue.push(scannedDirectory); + while (this.isSaving === false && this.savingQueue.length > 0) { + await this.saveToDB(this.savingQueue[0]); + this.savingQueue.shift(); + } + + } protected async saveToDB(scannedDirectory: DirectoryDTO) { - const connection = await SQLConnection.getConnection(); + this.isSaving = true; + try { + const connection = await SQLConnection.getConnection(); - // saving to db - const directoryRepository = connection.getRepository(DirectoryEntity); - const mediaRepository = connection.getRepository(MediaEntity); - const fileRepository = connection.getRepository(FileEntity); + // saving to db + const directoryRepository = connection.getRepository(DirectoryEntity); + const mediaRepository = connection.getRepository(MediaEntity); + const fileRepository = connection.getRepository(FileEntity); - let currentDir: DirectoryEntity = await directoryRepository.createQueryBuilder('directory') - .where('directory.name = :name AND directory.path = :path', { - name: scannedDirectory.name, - path: scannedDirectory.path - }).getOne(); - - if (!!currentDir) {// Updated parent dir (if it was in the DB previously) - currentDir.lastModified = scannedDirectory.lastModified; - currentDir.lastScanned = scannedDirectory.lastScanned; - // const media: MediaEntity[] = currentDir.media; - // delete currentDir.media; - currentDir = await directoryRepository.save(currentDir); - /*if (media) { - media.forEach(m => m.directory = currentDir); - currentDir.media = await this.saveMedia(connection, media); - }*/ - } else { - // const media = scannedDirectory.media; - // delete scannedDirectory.media; - (scannedDirectory).lastScanned = scannedDirectory.lastScanned; - currentDir = await directoryRepository.save(scannedDirectory); - /* if (media) { - media.forEach(m => m.directory = currentDir); - currentDir.media = await this.saveMedia(connection, media); - }*/ - } - - // save subdirectories - const childDirectories = await directoryRepository.createQueryBuilder('directory') - .where('directory.parent = :dir', { - dir: currentDir.id - }).getMany(); - - for (let i = 0; i < scannedDirectory.directories.length; i++) { - // Was this child Dir already indexed before? - let directory: DirectoryEntity = null; - for (let j = 0; j < childDirectories.length; j++) { - if (childDirectories[j].name === scannedDirectory.directories[i].name) { - directory = childDirectories[j]; - childDirectories.splice(j, 1); - break; - } - } - - if (directory != null) { // update existing directory - if (!directory.parent || !directory.parent.id) { // set parent if not set yet - directory.parent = currentDir; - delete directory.media; - await directoryRepository.save(directory); - } + let currentDir: DirectoryEntity = await directoryRepository.createQueryBuilder('directory') + .where('directory.name = :name AND directory.path = :path', { + name: scannedDirectory.name, + path: scannedDirectory.path + }).getOne(); + if (!!currentDir) {// Updated parent dir (if it was in the DB previously) + currentDir.lastModified = scannedDirectory.lastModified; + currentDir.lastScanned = scannedDirectory.lastScanned; + currentDir = await directoryRepository.save(currentDir); } else { - scannedDirectory.directories[i].parent = currentDir; - (scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet - const d = await directoryRepository.save(scannedDirectory.directories[i]); - for (let j = 0; j < scannedDirectory.directories[i].media.length; j++) { - scannedDirectory.directories[i].media[j].directory = d; + (scannedDirectory).lastScanned = scannedDirectory.lastScanned; + currentDir = await directoryRepository.save(scannedDirectory); + } + + + // save subdirectories + const childDirectories = await directoryRepository.createQueryBuilder('directory') + .where('directory.parent = :dir', { + dir: currentDir.id + }).getMany(); + + for (let i = 0; i < scannedDirectory.directories.length; i++) { + // Was this child Dir already indexed before? + let directory: DirectoryEntity = null; + for (let j = 0; j < childDirectories.length; j++) { + if (childDirectories[j].name === scannedDirectory.directories[i].name) { + directory = childDirectories[j]; + childDirectories.splice(j, 1); + break; + } } - await this.saveMedia(connection, scannedDirectory.directories[i].media); - } - } + if (directory != null) { // update existing directory + if (!directory.parent || !directory.parent.id) { // set parent if not set yet + directory.parent = currentDir; + delete directory.media; + await directoryRepository.save(directory); + } + } else { + scannedDirectory.directories[i].parent = currentDir; + (scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet + const d = await directoryRepository.save(scannedDirectory.directories[i]); + for (let j = 0; j < scannedDirectory.directories[i].media.length; j++) { + scannedDirectory.directories[i].media[j].directory = d; + } - // Remove child Dirs that are not anymore in the parent dir - await directoryRepository.remove(childDirectories); - - // save media - const indexedMedia = await mediaRepository.createQueryBuilder('media') - .where('media.directory = :dir', { - dir: currentDir.id - }).getMany(); - - - const mediaToSave = []; - for (let i = 0; i < scannedDirectory.media.length; i++) { - let media: MediaDTO = null; - for (let j = 0; j < indexedMedia.length; j++) { - if (indexedMedia[j].name === scannedDirectory.media[i].name) { - media = indexedMedia[j]; - indexedMedia.splice(j, 1); - break; + await this.saveMedia(connection, scannedDirectory.directories[i].media); } } - if (media == null) { //not in DB yet - scannedDirectory.media[i].directory = null; - media = Utils.clone(scannedDirectory.media[i]); - scannedDirectory.media[i].directory = scannedDirectory; - media.directory = currentDir; - mediaToSave.push(media); - } else if (!Utils.equalsFilter(media.metadata, scannedDirectory.media[i].metadata)) { - media.metadata = scannedDirectory.media[i].metadata; - mediaToSave.push(media); - } - } - await this.saveMedia(connection, mediaToSave); - await mediaRepository.remove(indexedMedia); - // save files - const indexedMetaFiles = await fileRepository.createQueryBuilder('file') - .where('file.directory = :dir', { - dir: currentDir.id - }).getMany(); + // Remove child Dirs that are not anymore in the parent dir + await directoryRepository.remove(childDirectories); + + // save media + const indexedMedia = await mediaRepository.createQueryBuilder('media') + .where('media.directory = :dir', { + dir: currentDir.id + }).getMany(); - const metaFilesToSave = []; - for (let i = 0; i < scannedDirectory.metaFile.length; i++) { - let metaFile: FileDTO = null; - for (let j = 0; j < indexedMetaFiles.length; j++) { - if (indexedMetaFiles[j].name === scannedDirectory.metaFile[i].name) { - metaFile = indexedMetaFiles[j]; - indexedMetaFiles.splice(j, 1); - break; + const mediaToSave = []; + for (let i = 0; i < scannedDirectory.media.length; i++) { + let media: MediaDTO = null; + for (let j = 0; j < indexedMedia.length; j++) { + if (indexedMedia[j].name === scannedDirectory.media[i].name) { + media = indexedMedia[j]; + indexedMedia.splice(j, 1); + break; + } + } + if (media == null) { // not in DB yet + scannedDirectory.media[i].directory = null; + media = Utils.clone(scannedDirectory.media[i]); + scannedDirectory.media[i].directory = scannedDirectory; + media.directory = currentDir; + mediaToSave.push(media); + } else if (!Utils.equalsFilter(media.metadata, scannedDirectory.media[i].metadata)) { + media.metadata = scannedDirectory.media[i].metadata; + mediaToSave.push(media); } } - if (metaFile == null) { //not in DB yet - scannedDirectory.metaFile[i].directory = null; - metaFile = Utils.clone(scannedDirectory.metaFile[i]); - scannedDirectory.metaFile[i].directory = scannedDirectory; - metaFile.directory = currentDir; - metaFilesToSave.push(metaFile); + await this.saveMedia(connection, mediaToSave); + await mediaRepository.remove(indexedMedia); + + // save files + const indexedMetaFiles = await fileRepository.createQueryBuilder('file') + .where('file.directory = :dir', { + dir: currentDir.id + }).getMany(); + + + const metaFilesToSave = []; + for (let i = 0; i < scannedDirectory.metaFile.length; i++) { + let metaFile: FileDTO = null; + for (let j = 0; j < indexedMetaFiles.length; j++) { + if (indexedMetaFiles[j].name === scannedDirectory.metaFile[i].name) { + metaFile = indexedMetaFiles[j]; + indexedMetaFiles.splice(j, 1); + break; + } + } + if (metaFile == null) { // not in DB yet + scannedDirectory.metaFile[i].directory = null; + metaFile = Utils.clone(scannedDirectory.metaFile[i]); + scannedDirectory.metaFile[i].directory = scannedDirectory; + metaFile.directory = currentDir; + metaFilesToSave.push(metaFile); + } } + await fileRepository.save(metaFilesToSave); + await fileRepository.remove(indexedMetaFiles); + }catch (e){ + throw e; + }finally { + this.isSaving = false; } - await fileRepository.save(metaFilesToSave); - await fileRepository.remove(indexedMetaFiles); } protected async saveMedia(connection: Connection, mediaList: MediaDTO[]): Promise { diff --git a/backend/model/sql/SQLConnection.ts b/backend/model/sql/SQLConnection.ts index 8808ac0..31b8aab 100644 --- a/backend/model/sql/SQLConnection.ts +++ b/backend/model/sql/SQLConnection.ts @@ -26,11 +26,9 @@ export class SQLConnection { private static connection: Connection = null; public static async getConnection(): Promise { - if (this.connection == null) { - const options: any = this.getDriver(Config.Server.database); - options.name = 'main'; + // options.name = 'main'; options.entities = [ UserEntity, FileEntity, @@ -47,7 +45,6 @@ export class SQLConnection { await SQLConnection.schemeSync(this.connection); } return this.connection; - } public static async tryConnection(config: DataBaseConfig) { diff --git a/backend/model/sql/enitites/DirectoryEntity.ts b/backend/model/sql/enitites/DirectoryEntity.ts index 70bd20d..e0a8b80 100644 --- a/backend/model/sql/enitites/DirectoryEntity.ts +++ b/backend/model/sql/enitites/DirectoryEntity.ts @@ -1,9 +1,10 @@ -import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn} from 'typeorm'; +import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {MediaEntity} from './MediaEntity'; import {FileEntity} from './FileEntity'; @Entity() +@Unique(['name', 'path']) export class DirectoryEntity implements DirectoryDTO { @PrimaryGeneratedColumn() diff --git a/backend/model/sql/enitites/MediaEntity.ts b/backend/model/sql/enitites/MediaEntity.ts index 2446be0..2ba0a9d 100644 --- a/backend/model/sql/enitites/MediaEntity.ts +++ b/backend/model/sql/enitites/MediaEntity.ts @@ -1,4 +1,4 @@ -import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance} from 'typeorm'; +import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm'; import {DirectoryEntity} from './DirectoryEntity'; import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO'; import {OrientationTypes} from 'ts-exif-parser'; @@ -51,6 +51,7 @@ export class MediaMetadataEntity implements MediaMetadata { // TODO: fix inheritance once its working in typeorm @Entity() +@Unique(['name', 'directory']) @TableInheritance({column: {type: 'varchar', name: 'type'}}) export abstract class MediaEntity implements MediaDTO { diff --git a/backend/model/sql/enitites/PhotoEntity.ts b/backend/model/sql/enitites/PhotoEntity.ts index 7d4643e..ede2fc3 100644 --- a/backend/model/sql/enitites/PhotoEntity.ts +++ b/backend/model/sql/enitites/PhotoEntity.ts @@ -1,4 +1,4 @@ -import {Column, Entity, ChildEntity} from 'typeorm'; +import {Column, Entity, ChildEntity, Unique} from 'typeorm'; import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO'; import {OrientationTypes} from 'ts-exif-parser'; import {MediaEntity, MediaMetadataEntity} from './MediaEntity'; diff --git a/frontend/app/gallery/lightbox/lightbox.gallery.component.ts b/frontend/app/gallery/lightbox/lightbox.gallery.component.ts index 9924d29..f67c69d 100644 --- a/frontend/app/gallery/lightbox/lightbox.gallery.component.ts +++ b/frontend/app/gallery/lightbox/lightbox.gallery.component.ts @@ -162,10 +162,6 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { public nextImage() { if (this.activePhotoId + 1 < this.gridPhotoQL.length) { this.navigateToPhoto(this.activePhotoId + 1); - /*if (this.activePhotoId + 3 >= this.gridPhotoQL.length) { - this.onLastElement.emit({}); // trigger to render more photos if there are - }*/ - return; } } @@ -173,14 +169,13 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.pause(); if (this.activePhotoId > 0) { this.navigateToPhoto(this.activePhotoId - 1); - return; } } private navigateToPhoto(photoIndex: number) { this.router.navigate([], - {queryParams: this.queryService.getParams(this.gridPhotoQL.toArray()[photoIndex].gridPhoto.media)}); + {queryParams: this.queryService.getParams(this.gridPhotoQL.toArray()[photoIndex].gridPhoto.media)}).catch(console.error); } private showPhoto(photoIndex: number, resize: boolean = true) { @@ -235,6 +230,11 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.prevImage(); } break; + case 'ArrowRight': + if (this.activePhotoId < this.gridPhotoQL.length - 1) { + this.nextImage(); + } + break; case 'i': case 'I': if (this.isInfoPanelAnimating()) { @@ -258,11 +258,6 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { case 'C': this.controllersAlwaysOn = !this.controllersAlwaysOn; break; - case 'ArrowRight': - if (this.activePhotoId < this.gridPhotoQL.length - 1) { - this.nextImage(); - } - break; case 'Escape': // escape this.hide(); break; @@ -276,7 +271,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { public hide() { this.router.navigate([], - {queryParams: this.queryService.getParams()}); + {queryParams: this.queryService.getParams()}).catch(console.error); } private hideLigthbox() { diff --git a/package.json b/package.json index eff5101..492f587 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pigallery2", - "version": "1.5.0", + "version": "1.5.5", "description": "This is a photo gallery optimised for running low resource servers (especially on raspberry pi)", "author": "Patrik J. Braun", "homepage": "https://github.com/bpatrik/PiGallery2", @@ -46,20 +46,20 @@ "winston": "2.4.2" }, "devDependencies": { - "@angular-devkit/build-angular": "0.11.0", - "@angular-devkit/build-optimizer": "0.11.0", - "@angular/animations": "7.1.1", - "@angular/cli": "7.1.0", - "@angular/common": "7.1.1", - "@angular/compiler": "7.1.1", - "@angular/compiler-cli": "7.1.1", - "@angular/core": "7.1.1", - "@angular/forms": "7.1.1", - "@angular/http": "7.1.1", - "@angular/language-service": "7.1.1", - "@angular/platform-browser": "7.1.1", - "@angular/platform-browser-dynamic": "7.1.1", - "@angular/router": "7.1.1", + "@angular-devkit/build-angular": "0.11.2", + "@angular-devkit/build-optimizer": "0.11.2", + "@angular/animations": "7.1.2", + "@angular/cli": "7.1.2", + "@angular/common": "7.1.2", + "@angular/compiler": "7.1.2", + "@angular/compiler-cli": "7.1.2", + "@angular/core": "7.1.2", + "@angular/forms": "7.1.2", + "@angular/http": "7.1.2", + "@angular/language-service": "7.1.2", + "@angular/platform-browser": "7.1.2", + "@angular/platform-browser-dynamic": "7.1.2", + "@angular/router": "7.1.2", "@ngx-translate/i18n-polyfill": "1.0.0", "@types/bcryptjs": "2.4.2", "@types/chai": "4.1.7", @@ -70,18 +70,18 @@ "@types/fluent-ffmpeg": "2.1.8", "@types/gm": "1.18.2", "@types/jasmine": "3.3.0", - "@types/node": "10.12.11", + "@types/node": "10.12.12", "@types/sharp": "0.21.0", "@types/winston": "2.3.9", "@yaga/leaflet-ng2": "^1.0.0", "bootstrap": "4.1.3", "chai": "4.2.0", "codelyzer": "4.5.0", - "core-js": "2.5.7", + "core-js": "2.6.0", "ejs-loader": "0.3.1", "gulp": "3.9.1", "gulp-json-modify": "1.0.2", - "gulp-typescript": "5.0.0-alpha.3", + "gulp-typescript": "5.0.0", "gulp-zip": "4.2.0", "hammerjs": "2.0.8", "intl": "1.2.5", @@ -126,7 +126,7 @@ "bcrypt": "3.0.2", "gm": "1.23.1", "mysql": "2.16.0", - "sharp": "0.21.0" + "sharp": "0.21.1" }, "engines": { "node": ">= 6.9 <11.0" diff --git a/test/backend/unit/model/sql/GalleryManager.ts b/test/backend/unit/model/sql/GalleryManager.ts index ae93756..a7198fe 100644 --- a/test/backend/unit/model/sql/GalleryManager.ts +++ b/test/backend/unit/model/sql/GalleryManager.ts @@ -12,6 +12,8 @@ import {DirectoryEntity} from '../../../../../backend/model/sql/enitites/Directo import {Utils} from '../../../../../common/Utils'; import {MediaDTO} from '../../../../../common/entities/MediaDTO'; import {FileDTO} from '../../../../../common/entities/FileDTO'; +import {PhotoEntity} from '../../../../../backend/model/sql/enitites/PhotoEntity'; +import {FileEntity} from '../../../../../backend/model/sql/enitites/FileEntity'; class GalleryManagerTest extends GalleryManager { @@ -28,6 +30,10 @@ class GalleryManagerTest extends GalleryManager { public async saveToDB(scannedDirectory: DirectoryDTO) { return super.saveToDB(scannedDirectory); } + + public async queueForSave(scannedDirectory: DirectoryDTO): Promise { + return super.queueForSave(scannedDirectory); + } } describe('GalleryManager', () => { @@ -171,7 +177,41 @@ describe('GalleryManager', () => { // selected.directories[0].parent = selected; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(subDir))); + }); + it('should avoid race condition', async () => { + const conn = await SQLConnection.getConnection(); + const gm = new GalleryManagerTest(); + Config.Client.MetaFile.enabled = true; + const parent = TestHelper.getRandomizedDirectoryEntry(); + const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1'); + const p2 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo2'); + const gpx = TestHelper.getRandomizedGPXEntry(parent, 'GPX1'); + const subDir = TestHelper.getRandomizedDirectoryEntry(parent, 'subDir'); + const sp1 = TestHelper.getRandomizedPhotoEntry(subDir, 'subPhoto1'); + const sp2 = TestHelper.getRandomizedPhotoEntry(subDir, 'subPhoto2'); + + + DirectoryDTO.removeReferences(parent); + const s1 = gm.queueForSave(Utils.clone(parent)); + const s2 = gm.queueForSave(Utils.clone(parent)); + const s3 = gm.queueForSave(Utils.clone(parent)); + + await Promise.all([s1, s2, s3]); + + const selected = await gm.selectParentDir(conn, parent.name, parent.path); + await gm.fillParentDir(conn, selected); + + const query = conn.getRepository(FileEntity).createQueryBuilder('photo'); + query.innerJoinAndSelect('photo.directory', 'directory'); + console.log((await query.getMany())); + DirectoryDTO.removeReferences(selected); + removeIds(selected); + subDir.isPartial = true; + delete subDir.directories; + delete subDir.metaFile; + expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) + .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); });