UNPKG

@sync-in/server

Version:

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

299 lines (298 loc) 13.4 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, "SyncManager", { enumerable: true, get: function() { return SyncManager; } }); 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 _functions = require("../../../common/functions"); const _operations = require("../../files/constants/operations"); const _fileerror = require("../../files/models/file-error"); const _filelockerror = require("../../files/models/file-lock-error"); const _filesmanagerservice = require("../../files/services/files-manager.service"); const _files = require("../../files/utils/files"); const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service"); const _sync = require("../constants/sync"); const _functions1 = require("../utils/functions"); const _routes = require("../utils/routes"); const _syncqueriesservice = require("./sync-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 SyncManager = class SyncManager { async download(req, res) { const sendFile = this.filesManager.sendFileFromSpace(req.space); try { await sendFile.checks(); return await sendFile.stream(req, res); } catch (e) { this.handleError(req.space, req.method, e); } } async upload(req, syncUploadDto) { const tmpPath = (0, _functions1.getTmpFilePath)(req.space.realPath); try { if (syncUploadDto.checksum) { const checksum = await this.filesManager.saveStream(req.user, req.space, req, { tmpPath: tmpPath, checksumAlg: _sync.SYNC_CHECKSUM_ALG }); if (checksum !== syncUploadDto.checksum) { await (0, _files.removeFiles)(tmpPath); this.handleError(req.space, req.method, new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'checksums are not identical')); } } else { await this.filesManager.saveStream(req.user, req.space, req, { tmpPath: tmpPath }); } const fileStats = await _promises.default.stat(req.space.realPath); if (fileStats.size !== syncUploadDto.size) { await (0, _files.removeFiles)(tmpPath); this.handleError(req.space, req.method, new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, `sizes are not identical : ${fileStats.size} != ${syncUploadDto.size}`)); } // update mtime await (0, _files.touchFile)(req.space.realPath, syncUploadDto.mtime); // return inode number return { ino: fileStats.ino }; } catch (e) { this.handleError(req.space, req.method, e); } } async delete(req) { try { return await this.filesManager.delete(req.user, req.space); } catch (e) { this.handleError(req.space, _operations.FILE_OPERATION.DELETE, e); } } async props(req, syncPropsDto) { try { await this.filesManager.touch(req.user, req.space, syncPropsDto.mtime, false); } catch (e) { this.handleError(req.space, _operations.FILE_OPERATION.TOUCH, e); } } async make(req, syncMakeDto) { try { if (syncMakeDto.type === 'directory') { await this.filesManager.mkDir(req.user, req.space, true); } else { await this.filesManager.mkFile(req.user, req.space, true); } await (0, _files.touchFile)(req.space.realPath, syncMakeDto.mtime); return { ino: (await _promises.default.stat(req.space.realPath)).ino }; } catch (e) { this.handleError(req.space, `${_operations.FILE_OPERATION.MAKE} ${syncMakeDto.type}`, e); } } async copyMove(req, syncCopyMoveDto, isMove) { const dstSpace = await this.spacesManager.spaceEnv(req.user, (0, _routes.SYNC_PATH_TO_SPACE_SEGMENTS)(syncCopyMoveDto.destination)); try { await this.filesManager.copyMove(req.user, req.space, dstSpace, isMove, true, true); if (!isMove) { if (syncCopyMoveDto.mtime) { // update mtime await (0, _files.touchFile)(dstSpace.realPath, syncCopyMoveDto.mtime); } // return inode & mtime const stats = await _promises.default.stat(dstSpace.realPath); return { ino: stats.ino, mtime: Math.floor(stats.mtime.getTime() / 1000) }; } } catch (e) { this.handleError(req.space, isMove ? _operations.FILE_OPERATION.MOVE : _operations.FILE_OPERATION.COPY, e, dstSpace); } } async diff(user, pathId, syncDiff, res) { if (!user.clientId) { throw new _common.HttpException('Client id is missing', _common.HttpStatus.BAD_REQUEST); } const syncPathSettings = await this.syncQueries.getPathSettings(user.clientId, pathId); if (!syncPathSettings) { throw new _common.HttpException('Path not found', _common.HttpStatus.NOT_FOUND); } let space; try { space = await this.spacesManager.spaceEnv(user, (0, _routes.SYNC_PATH_TO_SPACE_SEGMENTS)(syncPathSettings.remotePath)); } catch (e) { throw new _common.HttpException(e.message, _common.HttpStatus.BAD_REQUEST); } if (!space) { throw new _common.HttpException('Space not found', _common.HttpStatus.NOT_FOUND); } if (space.quotaIsExceeded) { throw new _common.HttpException('Space quota is exceeded', _common.HttpStatus.INSUFFICIENT_STORAGE); } if (!await (0, _files.isPathExists)(space.realPath)) { throw new _common.HttpException(`Remote path not found : ${syncPathSettings.remotePath}`, _common.HttpStatus.NOT_FOUND); } if (!await (0, _files.isPathIsDir)(space.realPath)) { throw new _common.HttpException('Remote path must be a directory', _common.HttpStatus.BAD_REQUEST); } // start res.raw.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Transfer-Encoding': 'chunked' }); try { for await (const f of this.parseSyncPath(space, syncDiff)){ res.raw.write(`${JSON.stringify(f)}\n`); } res.raw.write(_sync.SYNC_DIFF_DONE); } catch (e) { this.logger.error(`${this.diff.name} : ${e.message}`); res.raw.write(`${e.message}\n`); res.status(_common.HttpStatus.INTERNAL_SERVER_ERROR); } res.raw.end(); } async *parseSyncPath(space, syncDiff) { const context = { regexBasePath: (0, _functions.regExpPathPattern)(space.realPath), syncDiff: syncDiff }; yield* this.parseFiles(space.realPath, context); } async *parseFiles(dir, ctx) { try { for (const entry of (await _promises.default.readdir(dir, { withFileTypes: true }))){ const realPath = _nodepath.default.join(entry.parentPath, entry.name); if (!entry.isDirectory() && !entry.isFile()) { this.logger.log(`${this.parseFiles.name} - ignore special file: ${realPath}`); continue; } if (entry.isDirectory()) { const dirStats = await this.analyzeFile(entry, ctx); if (dirStats !== null) { yield dirStats; } yield* this.parseFiles(realPath, ctx); } else { const fileStats = await this.analyzeFile(entry, ctx); if (fileStats !== null) { yield fileStats; } } } } catch (e) { this.logger.error(`${this.parseFiles.name} - unable to parse directory : ${dir} : ${e}`); throw new Error('Unable to parse path'); } } async analyzeFile(entry, ctx) { if (ctx.syncDiff.defaultFilters.has(entry.name)) { return null; } const realPath = _nodepath.default.join(entry.parentPath, entry.name); const filePath = realPath.replace(ctx.regexBasePath, ''); let stats; try { stats = await _promises.default.stat(realPath); } catch (e) { this.logger.warn(`${this.analyzeFile.name} - unable to get file stats : ${realPath} : ${e}`); return { [filePath]: [ _sync.F_SPECIAL_STAT.ERROR, e.toString() ] }; } if (ctx.syncDiff.pathFilters && ctx.syncDiff.pathFilters.test(filePath)) { this.logger.verbose(`${this.analyzeFile.name} - ignore filtered file : ${realPath}`); return { [filePath]: [ _sync.F_SPECIAL_STAT.FILTERED, stats.isDirectory() ] }; } const fileStats = [ stats.isDirectory(), stats.isDirectory() ? 0 : stats.size, Math.floor(stats.mtime.getTime() / 1000), stats.ino, null ]; if (ctx.syncDiff.secureDiff && !fileStats[_sync.F_STAT.IS_DIR]) { try { await this.checkSumFile(ctx, filePath, realPath, fileStats); } catch (e) { this.logger.error(`${this.analyzeFile.name} - file error : ${realPath} - ${e}`); return { [filePath]: [ _sync.F_SPECIAL_STAT.ERROR, e.toString() ] }; } } return { [filePath]: fileStats }; } async checkSumFile(ctx, filePath, realPath, fileStats) { if (!ctx.syncDiff.firstSync && ctx.syncDiff.snapshot.has(filePath)) { const snapFileStats = ctx.syncDiff.snapshot.get(filePath); if (snapFileStats[_sync.F_STAT.CHECKSUM] && snapFileStats[_sync.F_STAT.MTIME] == fileStats[_sync.F_STAT.MTIME] && snapFileStats[_sync.F_STAT.SIZE] == fileStats[_sync.F_STAT.SIZE] && snapFileStats[_sync.F_STAT.INO] == fileStats[_sync.F_STAT.INO]) { fileStats[_sync.F_STAT.CHECKSUM] = snapFileStats[_sync.F_STAT.CHECKSUM]; return; } } fileStats[_sync.F_STAT.CHECKSUM] = await (0, _files.checksumFile)(realPath, _sync.SYNC_CHECKSUM_ALG); } handleError(space, action, e, dstSpace) { this.logger.error(`unable to ${action} ${space.url}${dstSpace?.url ? ` -> ${dstSpace.url}` : ''} : ${e}`); if (e instanceof _filelockerror.LockConflict) { throw new _common.HttpException('The file is locked', _common.HttpStatus.LOCKED); } else if (e instanceof _fileerror.FileError) { throw new _common.HttpException(e.message, e.httpCode); } throw new _common.HttpException(e.message, _common.HttpStatus.INTERNAL_SERVER_ERROR); } constructor(spacesManager, filesManager, syncQueries){ this.spacesManager = spacesManager; this.filesManager = filesManager; this.syncQueries = syncQueries; this.logger = new _common.Logger(SyncManager.name); } }; SyncManager = _ts_decorate([ (0, _common.Injectable)(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ typeof _spacesmanagerservice.SpacesManager === "undefined" ? Object : _spacesmanagerservice.SpacesManager, typeof _filesmanagerservice.FilesManager === "undefined" ? Object : _filesmanagerservice.FilesManager, typeof _syncqueriesservice.SyncQueries === "undefined" ? Object : _syncqueriesservice.SyncQueries ]) ], SyncManager); //# sourceMappingURL=sync-manager.service.js.map