@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
328 lines (327 loc) • 15.8 kB
JavaScript
/*
* 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