import {promises as fsp, Stats} from 'fs'; import * as path from 'path'; import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; import {PhotoDTO} from '../../../common/entities/PhotoDTO'; import {ProjectPath} from '../../ProjectPath'; import {Config} from '../../../common/config/private/Config'; import {VideoDTO} from '../../../common/entities/VideoDTO'; import {FileDTO} from '../../../common/entities/FileDTO'; import {MetadataLoader} from './MetadataLoader'; import {Logger} from '../../Logger'; import {SupportedFormats} from '../../../common/SupportedFormats'; import {VideoProcessing} from '../fileprocessing/VideoProcessing'; import {PhotoProcessing} from '../fileprocessing/PhotoProcessing'; const LOG_TAG = '[DiskMangerWorker]'; export class DiskMangerWorker { public static calcLastModified(stat: Stats) { return Math.max(stat.ctime.getTime(), stat.mtime.getTime()); } public static normalizeDirPath(dirPath: string): string { return path.normalize(path.join('.' + path.sep, dirPath)); } public static pathFromRelativeDirName(relativeDirectoryName: string): string { return path.join(path.dirname(this.normalizeDirPath(relativeDirectoryName)), path.sep); } public static pathFromParent(parent: { path: string, name: string }): string { return path.join(this.normalizeDirPath(path.join(parent.path, parent.name)), path.sep); } public static dirName(name: string) { if (name.trim().length === 0) { return '.'; } return path.basename(name); } public static async excludeDir(name: string, relativeDirectoryName: string, absoluteDirectoryName: string) { if (Config.Server.Indexing.excludeFolderList.length === 0 || Config.Server.Indexing.excludeFileList.length === 0) { return false; } const absoluteName = path.normalize(path.join(absoluteDirectoryName, name)); const relativeName = path.normalize(path.join(relativeDirectoryName, name)); for (let j = 0; j < Config.Server.Indexing.excludeFolderList.length; j++) { const exclude = Config.Server.Indexing.excludeFolderList[j]; if (exclude.startsWith('/')) { if (exclude === absoluteName) { return true; } } else if (exclude.includes('/')) { if (path.normalize(exclude) === relativeName) { return true; } } else { if (exclude === name) { return true; } } } // exclude dirs that have the given files (like .ignore) for (let j = 0; j < Config.Server.Indexing.excludeFileList.length; j++) { const exclude = Config.Server.Indexing.excludeFileList[j]; try { await fsp.access(path.join(absoluteName, exclude)); return true; } catch (e) { } } return false; } public static async scanDirectory(relativeDirectoryName: string, settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise { relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName); const directoryName = DiskMangerWorker.dirName(relativeDirectoryName); const directoryParent = this.pathFromRelativeDirName(relativeDirectoryName); const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName); const stat = await fsp.stat(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); const directory: DirectoryDTO = { id: null, parent: null, name: directoryName, path: directoryParent, lastModified: this.calcLastModified(stat), lastScanned: Date.now(), directories: [], isPartial: false, mediaCount: 0, media: [], metaFile: [] }; const list = await fsp.readdir(absoluteDirectoryName); for (let i = 0; i < list.length; i++) { const file = list[i]; const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file)); if ((await fsp.stat(fullFilePath)).isDirectory()) { if (settings.noDirectory === true || await DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) { continue; } // create preview directory const d = await DiskMangerWorker.scanDirectory(path.join(relativeDirectoryName, file), { maxPhotos: Config.Server.Indexing.folderPreviewSize, noMetaFile: true, noVideo: true, noDirectory: true } ); d.lastScanned = 0; // it was not a fully scan d.isPartial = true; directory.directories.push(d); } else if (PhotoProcessing.isPhoto(fullFilePath)) { if (settings.noPhoto) { continue; } directory.media.push({ name: file, directory: null, metadata: await MetadataLoader.loadPhotoMetadata(fullFilePath) }); if (settings.maxPhotos && directory.media.length > settings.maxPhotos) { break; } } else if (VideoProcessing.isVideo(fullFilePath)) { if (Config.Client.Media.Video.enabled === false || settings.noVideo) { continue; } try { directory.media.push({ name: file, directory: null, metadata: await MetadataLoader.loadVideoMetadata(fullFilePath) }); } catch (e) { Logger.warn('Media loading error, skipping: ' + file + ', reason: ' + e.toString()); } } else if (DiskMangerWorker.isMetaFile(fullFilePath)) { if (Config.Client.MetaFile.enabled === false || settings.noMetaFile) { continue; } directory.metaFile.push({ name: file, directory: null, }); } } directory.mediaCount = directory.media.length; return directory; } private static isMetaFile(fullPath: string) { const extension = path.extname(fullPath).toLowerCase(); return SupportedFormats.WithDots.MetaFiles.indexOf(extension) !== -1; } } export namespace DiskMangerWorker { export interface DirectoryScanSettings { maxPhotos?: number; noMetaFile?: boolean; noVideo?: boolean; noPhoto?: boolean; noDirectory?: boolean; } }