diff --git a/.travis.yml b/.travis.yml index dc17f52..9743c1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,8 @@ language: node_js node_js: - '8' - '10' +- '11' -deploy: - provider: heroku - api_key: - secure: kv4RwF5ZGYUbgmaX9jESGAasxKPMSkAD8NUShwIE+H//Z/xPtnyJ4zbIYb+NTPljS2lZGXGxxhtxVzC7bX+q/Y+k7aKjY1gcZHEXWKHF2RIIB/KTESonuootvvetbKQVYx5bqAakCXkXmbq+/3yRD89q6PJMGsw5rCx7fEpOil3yITfneRGul/8ZDhtgGLSQtsa0iqVHVYnYFnEI1B2EsYHrCmyGWFen0wKKZkqE2ryxw2KevsOEm7dlz4xtjIQP/zTdFDwCL1IqkXYpvGMMBnnntGkPQBjGoRiJfYRQVQi+XC3qhPg+XG/SdoExiHoEc+uOlf8VqwTn3Z1uPEvMzZP+02r5EhupeOZ9rMXTgb6EYXN6q8i5agkXF8QujUYFz5NZs451YF3PFxyq7KKTrtuKd0KujGOkVzA0KpIjl2tRztxvej3Q2IPblAMXH+Rq9pem/HAH2Oxr+stT7dIOawHh5bk3yTMuLDvsEFbneELEHStzWzWFIhyBIXtTEEdSJY9moh76lkZY83ireE6U3zmLftJ7+TWBdFTiUe0mJxPoI8MWAr1rjcXNVjE7iUXx8q4rNPhVlJ3uzKk+qZ+P5VjNQLUAT1QE/IdF6h7V7nVcn5XeVPvIIcIa5b1tBTqmYBO42S4CkQ+plXsfVbiKACgPEmkeGU9bIqomQaFlcbQ= - app: pigallery2 - on: - repo: bpatrik/pigallery2 - node: '8' -cache: - directories: - - node_modules addons: chrome: stable diff --git a/backend/middlewares/GalleryMWs.ts b/backend/middlewares/GalleryMWs.ts index 1834925..c06ab11 100644 --- a/backend/middlewares/GalleryMWs.ts +++ b/backend/middlewares/GalleryMWs.ts @@ -13,6 +13,7 @@ import {UserDTO} from '../../common/entities/UserDTO'; import {RandomQuery} from '../model/interfaces/IGalleryManager'; import {MediaDTO} from '../../common/entities/MediaDTO'; import {VideoDTO} from '../../common/entities/VideoDTO'; +import {Utils} from '../../common/Utils'; const LOG_TAG = '[GalleryMWs]'; @@ -68,12 +69,16 @@ export class GalleryMWs { delete (m).metadata.bitRate; delete (m).metadata.duration; } else if (MediaDTO.isVideo(m)) { + delete (m).metadata.caption; delete (m).metadata.cameraData; delete (m).metadata.orientation; delete (m).metadata.orientation; delete (m).metadata.keywords; delete (m).metadata.positionData; } + Utils.removeNullOrEmptyObj(m); + console.log(m); + console.log(Utils.removeNullOrEmptyObj(m)); }); }; diff --git a/backend/model/sql/enitites/MediaEntity.ts b/backend/model/sql/enitites/MediaEntity.ts index 6fa85d8..2446be0 100644 --- a/backend/model/sql/enitites/MediaEntity.ts +++ b/backend/model/sql/enitites/MediaEntity.ts @@ -17,6 +17,8 @@ export class MediaDimensionEntity implements MediaDimension { export class MediaMetadataEntity implements MediaMetadata { + @Column('text') + caption: string; @Column(type => MediaDimensionEntity) size: MediaDimensionEntity; @@ -50,7 +52,7 @@ export class MediaMetadataEntity implements MediaMetadata { // TODO: fix inheritance once its working in typeorm @Entity() @TableInheritance({column: {type: 'varchar', name: 'type'}}) -export abstract class MediaEntity implements MediaDTO { +export abstract class MediaEntity implements MediaDTO { @PrimaryGeneratedColumn() id: number; diff --git a/backend/model/threading/DiskMangerWorker.ts b/backend/model/threading/DiskMangerWorker.ts index 52fa8d7..e0c8ad8 100644 --- a/backend/model/threading/DiskMangerWorker.ts +++ b/backend/model/threading/DiskMangerWorker.ts @@ -18,8 +18,9 @@ const LOG_TAG = '[DiskManagerTask]'; const ffmpeg = FFmpegFactory.get(); export class DiskMangerWorker { - private static isImage(fullPath: string) { - const extensions = [ + + private static readonly SupportedEXT = { + photo: [ '.bmp', '.gif', '.jpeg', '.jpg', '.jpe', @@ -28,31 +29,31 @@ export class DiskMangerWorker { '.webp', '.ico', '.tga' - ]; - - const extension = path.extname(fullPath).toLowerCase(); - return extensions.indexOf(extension) !== -1; - } - - private static isVideo(fullPath: string) { - const extensions = [ + ], + video: [ '.mp4', '.webm', '.ogv', '.ogg' - ]; + ], + metaFile: [ + '.gpx' + ] + }; + private static isImage(fullPath: string) { const extension = path.extname(fullPath).toLowerCase(); - return extensions.indexOf(extension) !== -1; + return this.SupportedEXT.photo.indexOf(extension) !== -1; + } + + private static isVideo(fullPath: string) { + const extension = path.extname(fullPath).toLowerCase(); + return this.SupportedEXT.video.indexOf(extension) !== -1; } private static isMetaFile(fullPath: string) { - const extensions = [ - '.gpx' - ]; - const extension = path.extname(fullPath).toLowerCase(); - return extensions.indexOf(extension) !== -1; + return this.SupportedEXT.metaFile.indexOf(extension) !== -1; } public static scanDirectory(relativeDirectoryName: string, maxPhotos: number = null, photosOnly: boolean = false): Promise { @@ -133,7 +134,7 @@ export class DiskMangerWorker { public static loadVideoMetadata(fullPath: string): Promise { return new Promise((resolve, reject) => { - const metadata: VideoMetadata = { + const metadata: VideoMetadata = { size: { width: 1, height: 1 @@ -188,11 +189,12 @@ export class DiskMangerWorker { fs.closeSync(fd); return reject({file: fullPath, error: err}); } - const metadata: PhotoMetadata = { + const metadata: PhotoMetadata = { keywords: [], cameraData: {}, positionData: null, - size: {}, + size: {width: 1, height: 1}, + caption: null, orientation: OrientationTypes.TOP_LEFT, creationDate: 0, fileSize: 0 @@ -253,7 +255,8 @@ export class DiskMangerWorker { metadata.positionData.state = iptcData.province_or_state; metadata.positionData.city = iptcData.city; } - metadata.keywords = (iptcData.keywords || []); + metadata.caption = iptcData.caption; + metadata.keywords = iptcData.keywords || []; metadata.creationDate = (iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate); } catch (err) { diff --git a/common/DataStructureVersion.ts b/common/DataStructureVersion.ts index a30c528..92be923 100644 --- a/common/DataStructureVersion.ts +++ b/common/DataStructureVersion.ts @@ -1 +1 @@ -export const DataStructureVersion = 3; +export const DataStructureVersion = 4; diff --git a/common/Utils.ts b/common/Utils.ts index 2f3d6ab..8624129 100644 --- a/common/Utils.ts +++ b/common/Utils.ts @@ -8,6 +8,27 @@ export class Utils { } + static removeNullOrEmptyObj(obj: any) { + if (typeof obj !== 'object' || obj == null) { + return obj; + } + + const keys = Object.keys(obj); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (obj[key] !== null && typeof obj[key] === 'object') { + if (Utils.removeNullOrEmptyObj(obj[key])) { + if (Object.keys(obj[key]).length === 0) { + delete obj[key]; + } + } + } else if (obj[key] === null) { + delete obj[key]; + } + } + return obj; + } + static clone(object: T): T { return JSON.parse(JSON.stringify(object)); } diff --git a/common/entities/MediaDTO.ts b/common/entities/MediaDTO.ts index 67c56f3..13788c2 100644 --- a/common/entities/MediaDTO.ts +++ b/common/entities/MediaDTO.ts @@ -4,7 +4,7 @@ import {OrientationTypes} from 'ts-exif-parser'; import {VideoDTO} from './VideoDTO'; import {FileDTO} from './FileDTO'; -export interface MediaDTO extends FileDTO{ +export interface MediaDTO extends FileDTO { id: number; name: string; directory: DirectoryDTO; @@ -51,11 +51,12 @@ export module MediaDTO { }; export const isPhoto = (media: MediaDTO): boolean => { - return typeof (media).metadata.keywords !== 'undefined' && (media).metadata.keywords !== null; + return !MediaDTO.isVideo(media); }; export const isVideo = (media: MediaDTO): boolean => { - return !MediaDTO.isPhoto(media); + const lower = media.name.toLowerCase(); + return lower.endsWith('.mp4') || lower.endsWith('.webm') || lower.endsWith('.ogg') || lower.endsWith('.ogv'); }; export const getRotatedSize = (photo: MediaDTO): MediaDimension => { diff --git a/common/entities/PhotoDTO.ts b/common/entities/PhotoDTO.ts index 3b2a05b..b7710ea 100644 --- a/common/entities/PhotoDTO.ts +++ b/common/entities/PhotoDTO.ts @@ -12,6 +12,7 @@ export interface PhotoDTO extends MediaDTO { } export interface PhotoMetadata extends MediaMetadata { + caption: string; keywords: Array; cameraData: CameraMetadata; positionData: PositionMetaData; diff --git a/frontend/app/gallery/gallery.service.ts b/frontend/app/gallery/gallery.service.ts index 6ee35a2..f607174 100644 --- a/frontend/app/gallery/gallery.service.ts +++ b/frontend/app/gallery/gallery.service.ts @@ -1,5 +1,4 @@ import {Injectable} from '@angular/core'; -import {Location} from '@angular/common'; import {NetworkService} from '../model/network/network.service'; import {ContentWrapper} from '../../../common/entities/ConentWrapper'; import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; @@ -11,6 +10,7 @@ import {Config} from '../../../common/config/public/Config'; import {ShareService} from './share.service'; import {NavigationService} from '../model/navigation.service'; import {SortingMethods} from '../../../common/entities/SortingMethods'; +import {QueryService} from '../model/query.service'; import {QueryParams} from '../../../common/QueryParams'; @@ -34,8 +34,7 @@ export class GalleryService { constructor(private networkService: NetworkService, private galleryCacheService: GalleryCacheService, private _shareService: ShareService, - private navigationService: NavigationService, - private location: Location) { + private navigationService: NavigationService) { this.content = new BehaviorSubject(new ContentWrapper()); this.sorting = new BehaviorSubject(Config.Client.Other.defaultPhotoSortingMethod); } @@ -48,7 +47,7 @@ export class GalleryService { this.sorting.next(sorting); } - public loadDirectory(directoryName: string): void { + public async loadDirectory(directoryName: string): Promise { const content = new ContentWrapper(); content.directory = this.galleryCacheService.getDirectory(directoryName); @@ -71,8 +70,8 @@ export class GalleryService { params['knownLastScanned'] = content.directory.lastScanned; } - - this.networkService.getJson('/gallery/content/' + directoryName, params).then((cw) => { + try { + const cw = await this.networkService.getJson('/gallery/content/' + directoryName, params); if (!cw || cw.notModified === true) { @@ -85,18 +84,14 @@ export class GalleryService { return; } - DirectoryDTO.addReferences(cw.directory); - this.lastDirectory = cw.directory; this.content.next(cw); - - - }).catch((e) => { + } catch (e) { console.error(e); this.navigationService.toGallery().catch(console.error); - }); + } } public async search(text: string, type?: SearchTypes): Promise { @@ -140,7 +135,7 @@ export class GalleryService { clearTimeout(this.searchId); } if (!this.lastDirectory) { - this.loadDirectory('/'); + this.loadDirectory('/').catch(console.error); } return null; } @@ -158,7 +153,7 @@ export class GalleryService { if (cw.searchResult == null) { // If result is not search cache, try to load more this.searchId = setTimeout(() => { - this.search(text, type); + this.search(text, type).catch(console.error); this.searchId = null; }, Config.Client.Search.InstantSearchTimeout); diff --git a/frontend/app/model/query.service.ts b/frontend/app/model/query.service.ts index bf2b586..ee80678 100644 --- a/frontend/app/model/query.service.ts +++ b/frontend/app/model/query.service.ts @@ -5,6 +5,8 @@ import {MediaDTO} from '../../../common/entities/MediaDTO'; import {QueryParams} from '../../../common/QueryParams'; import {Utils} from '../../../common/Utils'; import {GalleryService} from '../gallery/gallery.service'; +import {Config} from '../../../common/config/public/Config'; +import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; @Injectable() export class QueryService { @@ -27,10 +29,29 @@ export class QueryService { if (media) { query[QueryParams.gallery.photo] = this.getMediaStringId(media); } - if (this.shareService.isSharing()) { - query[QueryParams.gallery.sharingKey_short] = this.shareService.getSharingKey(); + if (Config.Client.Sharing.enabled === true) { + if (this.shareService.isSharing()) { + query[QueryParams.gallery.sharingKey_short] = this.shareService.getSharingKey(); + } } return query; } + getParamsForDirs(directory: DirectoryDTO) { + const params: { [key: string]: any } = {}; + if (Config.Client.Sharing.enabled === true) { + if (this.shareService.isSharing()) { + params[QueryParams.gallery.sharingKey_short] = this.shareService.getSharingKey(); + } + } + if (directory && directory.lastModified && directory.lastScanned && + !directory.isPartial) { + params['knownLastModified'] = directory.lastModified; + params['knownLastScanned'] = directory.lastScanned; + } + + return params; + + } + } diff --git a/test/backend/unit/model/sql/GalleryManager.ts b/test/backend/unit/model/sql/GalleryManager.ts index fabafdc..ae93756 100644 --- a/test/backend/unit/model/sql/GalleryManager.ts +++ b/test/backend/unit/model/sql/GalleryManager.ts @@ -113,8 +113,8 @@ describe('GalleryManager', () => { subDir.isPartial = true; delete subDir.directories; delete subDir.metaFile; - expect(Utils.clone(selected)).to.deep.equal(Utils.clone(parent)); - + expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) + .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); it('should skip meta files', async () => { @@ -135,7 +135,8 @@ describe('GalleryManager', () => { delete parent.metaFile; DirectoryDTO.removeReferences(selected); removeIds(selected); - expect(Utils.clone(selected)).to.deep.equal(Utils.clone(parent)); + expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) + .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); it('should update sub directory', async () => { @@ -168,7 +169,8 @@ describe('GalleryManager', () => { delete subDir.metaFile; removeIds(selected); // selected.directories[0].parent = selected; - expect(Utils.clone(selected)).to.deep.equal(Utils.clone(subDir)); + expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) + .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(subDir))); }); diff --git a/test/backend/unit/model/sql/TestHelper.ts b/test/backend/unit/model/sql/TestHelper.ts index 77cd716..72f317a 100644 --- a/test/backend/unit/model/sql/TestHelper.ts +++ b/test/backend/unit/model/sql/TestHelper.ts @@ -46,6 +46,7 @@ export class TestHelper { cd.focalLength = 1; cd.lens = 'Lens'; const m = new PhotoMetadataEntity(); + m.caption = null; m.keywords = ['apple']; m.cameraData = cd; m.positionData = pd; @@ -72,6 +73,7 @@ export class TestHelper { sd.width = 200; const m = new VideoMetadataEntity(); + m.caption = null; m.keywords = null; m.size = sd; m.creationDate = Date.now();