@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
299 lines (298 loc) • 13.4 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, "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