UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

328 lines (327 loc) 15.8 kB
/* * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com> * This file is part of Sync-in | The open source file sync and share solution * See the LICENSE file for licensing details */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "SpacesBrowser", { enumerable: true, get: function() { return SpacesBrowser; } }); const _common = require("@nestjs/common"); const _promises = /*#__PURE__*/ _interop_require_default(require("node:fs/promises")); const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path")); const _configenvironment = require("../../../configuration/config.environment"); const _fileslockmanagerservice = require("../../files/services/files-lock-manager.service"); const _filesqueriesservice = require("../../files/services/files-queries.service"); const _filesrecentsservice = require("../../files/services/files-recents.service"); const _files = require("../../files/utils/files"); const _sharesqueriesservice = require("../../shares/services/shares-queries.service"); const _user = require("../../users/constants/user"); const _paths = require("../utils/paths"); const _spacesmanagerservice = require("./spaces-manager.service"); const _spacesqueriesservice = require("./spaces-queries.service"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function _ts_metadata(k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); } let SpacesBrowser = class SpacesBrowser { async browse(user, space, options = {}) { // check sync permission options.withSyncs = options.withSyncs && user.havePermission(_user.USER_PERMISSION.DESKTOP_APP) && user.havePermission(_user.USER_PERMISSION.DESKTOP_APP_SYNC); const spaceFiles = { files: [], hasRoots: false, permissions: space.browsePermissions() }; const [fsFiles, dbFiles, rootFiles] = await Promise.all([ this.parseFS(space), this.parseDB(user.id, space, options), this.parseRootFiles(user, space, { withShares: options.withSpacesAndShares, withHasComments: options.withHasComments, withSyncs: options.withSyncs, withLocks: options.withLocks }) ]); this.updateDBFiles(user, space, dbFiles, fsFiles, options); if (space.inSharesList) { // the share space includes shares as root files spaceFiles.files = [ ...rootFiles, ...fsFiles ]; spaceFiles.hasRoots = true; } else { await this.mergeSpaceRootFiles(space, rootFiles, fsFiles, spaceFiles); } if (options.withLocks && !space.inTrashRepository) { // locks were removed when files were moved to the trash, no need to parse locks await this.enrichWithLocks(space, spaceFiles.files); } // update recents files this.filesRecents.updateRecents(user, space, spaceFiles.files).catch((e)=>this.logger.error(`${this.browse.name} - ${e}`)); return spaceFiles; } async parseRootFiles(user, space, options) { if (space.inFilesRepository && space.id && !space.root.alias) { // list roots in the space return Promise.all((await this.spacesQueries.spaceRootFiles(user.id, space.id, options)).map((f)=>this.updateRootFile(f, options))); } else if (space.inSharesList) { // list shares as roots return Promise.all((await this.sharesQueries.shareRootFiles(user, options)).map((f)=>this.updateRootFile(f, options))); } return []; } async parseDB(userId, space, options) { if (space.inSharesList) return []; const dbOptions = { withSpaces: options.withSpacesAndShares && space.inPersonalSpace, withShares: options.withSpacesAndShares, withSyncs: options.withSyncs, withHasComments: options.withHasComments, ignoreChildShares: !space.inSharesRepository }; return this.filesQueries.browseFiles(userId, space.dbFile, dbOptions); } async parseFS(space) { if (space.inSharesList) return []; const fsFiles = []; try { await (0, _paths.IsRealPathIsDirAndExists)(space.realPath); } catch (e) { this.logger.warn(`${this.parseFS.name} - ${space.realPath} : ${e.message}`); throw new _common.HttpException(e.message, e.httpCode); } for await (const f of this.parsePath(space)){ fsFiles.push(f); } return fsFiles; } async *parsePath(space) { try { for (const element of (await _promises.default.readdir(space.realPath, { withFileTypes: true }))){ const isDir = element.isDirectory(); if (!isDir && !element.isFile()) { this.logger.log(`${this.parsePath.name} - ignore special file : ${element.name}`); continue; } if (!_configenvironment.configuration.applications.files.showHiddenFiles && element.name[0] === '.') { this.logger.verbose(`${this.parsePath.name} - ignore filtered file : ${element.name}`); continue; } const realPath = _nodepath.default.join(space.realPath, element.name); const filePath = _nodepath.default.join(space.relativeUrl, element.name); try { yield await (0, _files.getProps)(realPath, filePath, isDir); } catch (e) { this.logger.warn(`${this.parsePath.name} - unable get stats from ${realPath} : ${e}`); } } } catch (e) { this.logger.error(`${this.parsePath.name} - unable to parse ${space.realPath} : ${e}`); } } async updateRootFile(f, options) { const realPath = (0, _paths.realPathFromRootFile)(f); const originalPath = f.path; f.path = f.root.name; try { const fileProps = await (0, _files.getProps)(realPath, f.path); if (options.withShares) { fileProps.shares = f.shares; } if (options.withHasComments) { fileProps.hasComments = f.hasComments; } if (options.withSyncs) { fileProps.syncs = f.syncs; } if (options.withLocks && (f.origin || f.root?.owner)) { // `f.origin` is used for shares // `f.root.owner` is used for anchored files in spaces // all other files are handled in the `enrichWithLocks` function const dbFile = { ...f.origin?.spaceId ? { spaceId: f.origin.spaceId, ...f.origin.spaceExternalRootId ? { spaceExternalRootId: f.origin.spaceExternalRootId } : {} } : f.origin?.shareExternalId ? { shareExternalId: f.origin.shareExternalId } : { ownerId: f.origin?.ownerId ?? f.root.owner.id }, path: originalPath, inTrash: f.inTrash }; const locks = await this.filesLockManager.getLocksByPath(dbFile); if (locks.length > 0) { fileProps.lock = this.filesLockManager.convertLockToFileLockProps(locks[0]); } } // `owner.id` is only used in the `withLocks` condition delete f.root.owner?.id; // check `f.id`; it can be null for external roots if (f.id) { // todo: check if a db file referenced under external roots have an id and correctly parsed here this.filesQueries.compareAndUpdateFileProps(f, fileProps).catch((e)=>this.logger.error(`${this.updateRootFile.name} - ${e}`)); fileProps.id = f.id; } fileProps.root = { id: f.root.id, alias: f.root.alias, description: f.root.description, enabled: typeof f.root.enabled === 'undefined' ? true : f.root.enabled, permissions: f.root.permissions, owner: f.root.owner }; return fileProps; } catch (e) { this.logger.error(`${this.updateRootFile.name} - ${JSON.stringify(f)} - ${e}`); return { ...f, name: (0, _files.fileName)(f.path), path: (0, _files.dirName)(f.path), ...{ root: { ...f.root, enabled: false } } }; } } updateDBFiles(user, space, dbFiles, fsFiles, options) { for (const dbFile of dbFiles){ const fsFile = fsFiles.find((f)=>dbFile.name === f.name); if (fsFile) { /* important: inherits from the file id in database */ fsFile.id = dbFile.id; if (options.withSpacesAndShares) { fsFile.spaces = dbFile.spaces; fsFile.shares = dbFile.shares; } if (options.withSyncs) { fsFile.syncs = dbFile.syncs; } if (options.withHasComments) { fsFile.hasComments = dbFile.hasComments; } this.filesQueries.compareAndUpdateFileProps(dbFile, fsFile).catch((e)=>this.logger.error(`${this.updateDBFiles.name} - ${e}`)); } else { this.logger.warn(`${this.updateDBFiles.name} - missing ${dbFile.path}/${dbFile.name} (${dbFile.id}) from fs, delete it from db`); if (options.withSpacesAndShares) { if (dbFile.spaces) { for (const space of dbFile.spaces){ this.logger.warn(`${this.updateDBFiles.name} - ${dbFile.path}/${dbFile.name} (${dbFile.id}) will be removed from space : *${space.alias}* (${space.id})`); } } if (dbFile.shares) { for (const share of dbFile.shares){ this.logger.warn(`${this.updateDBFiles.name} - ${dbFile.path}/${dbFile.name} (${dbFile.id}) will be removed from share : *${share.alias}* (${share.id})`); } } } this.deleteDBFile(user, space, dbFile).catch((e)=>this.logger.error(`${this.updateDBFiles.name} - ${e}`)); } } } async deleteDBFile(user, space, dbFile) { const spaceEnv = await this.spacesManager.spaceEnv(user, _nodepath.default.join(space.url, dbFile.name).split('/')); this.filesQueries.deleteFiles(spaceEnv.dbFile, dbFile.isDir, true).catch((e)=>this.logger.error(`${this.deleteDBFile.name} - ${e}`)); } async mergeSpaceRootFiles(space, rootFiles, fsFiles, spaceFiles) { // merges root files in space files taking care of alias and name (file names must be unique) if (!rootFiles.length) { spaceFiles.files = fsFiles; return; } spaceFiles.hasRoots = true; for (const f of rootFiles){ // check root alias (must be unique in the space) const newAlias = await this.spacesManager.uniqueRootAlias(space.id, f.root.alias, fsFiles.map((f)=>f.name), true); if (newAlias) { this.logger.log(`${this.mergeSpaceRootFiles.name} - update space root alias (${f.root.id}) : ${f.root.alias} -> ${newAlias}`); // update in db this.spacesQueries.updateRoot({ alias: newAlias }, { id: f.root.id }).catch((e)=>this.logger.error(`${this.mergeSpaceRootFiles.name} - ${e}`)); // cleanup cache this.spacesQueries.clearCachePermissions(space.alias, [ f.root.alias, newAlias ]).catch((e)=>this.logger.error(`${this.mergeSpaceRootFiles.name} - ${e}`)); // assign f.root.alias = newAlias; } // check root name (must be unique in the space) // f.name is equal to root name const newName = this.spacesManager.uniqueRootName(f.name, fsFiles.map((f)=>f.name)); if (newName) { this.logger.log(`${this.mergeSpaceRootFiles.name} - update space root name (${f.root.id}) : ${f.name} -> ${newName}`); // update in db this.spacesQueries.updateRoot({ name: newName }, { id: f.root.id }).catch((e)=>this.logger.error(`${this.mergeSpaceRootFiles.name} - ${e}`)); // assign f.name = newName; } } spaceFiles.files = [ ...fsFiles, ...rootFiles ]; } async enrichWithLocks(space, files) { if (space.inSharesList) { return; } const locks = await this.filesLockManager.browseParentChildLocks(space.dbFile, false); if (!Object.keys(locks).length) return; for (const f of files.filter((f)=>!f.root && !f.origin && f.name in locks)){ f.lock = this.filesLockManager.convertLockToFileLockProps(locks[f.name]); } } constructor(spacesManager, spacesQueries, sharesQueries, filesQueries, filesLockManager, filesRecents){ this.spacesManager = spacesManager; this.spacesQueries = spacesQueries; this.sharesQueries = sharesQueries; this.filesQueries = filesQueries; this.filesLockManager = filesLockManager; this.filesRecents = filesRecents; this.logger = new _common.Logger(SpacesBrowser.name); } }; SpacesBrowser = _ts_decorate([ (0, _common.Injectable)(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ typeof _spacesmanagerservice.SpacesManager === "undefined" ? Object : _spacesmanagerservice.SpacesManager, typeof _spacesqueriesservice.SpacesQueries === "undefined" ? Object : _spacesqueriesservice.SpacesQueries, typeof _sharesqueriesservice.SharesQueries === "undefined" ? Object : _sharesqueriesservice.SharesQueries, typeof _filesqueriesservice.FilesQueries === "undefined" ? Object : _filesqueriesservice.FilesQueries, typeof _fileslockmanagerservice.FilesLockManager === "undefined" ? Object : _fileslockmanagerservice.FilesLockManager, typeof _filesrecentsservice.FilesRecents === "undefined" ? Object : _filesrecentsservice.FilesRecents ]) ], SpacesBrowser); //# sourceMappingURL=spaces-browser.service.js.map