From 8623ff2842a170f151f9d21c645a753cb7181535 Mon Sep 17 00:00:00 2001 From: AlexisMora Date: Tue, 2 Jun 2026 11:04:13 +0200 Subject: [PATCH 1/3] chore: remove legacy code --- src/apps/drive/fuse/FuseApp.ts | 179 ------------------ .../drive/fuse/callbacks/CreateCallback.ts | 15 -- src/apps/drive/fuse/callbacks/FuseCallback.ts | 162 ---------------- src/apps/drive/fuse/callbacks/FuseErrors.ts | 6 - .../fuse/callbacks/GetAttributesCallback.ts | 96 ---------- .../fuse/callbacks/GetXAttributeCallback.ts | 37 ---- .../fuse/callbacks/MakeDirectoryCallback.ts | 35 ---- src/apps/drive/fuse/callbacks/OpenCallback.ts | 42 ---- .../drive/fuse/callbacks/ReadCallback.test.ts | 58 ------ src/apps/drive/fuse/callbacks/ReadCallback.ts | 73 ------- .../drive/fuse/callbacks/ReaddirCallback.ts | 49 ----- .../fuse/callbacks/ReleaseCallback.test.ts | 41 ---- .../drive/fuse/callbacks/ReleaseCallback.ts | 46 ----- .../fuse/callbacks/RenameMoveOrTrashFile.ts | 90 --------- .../fuse/callbacks/RenameMoveOrTrashFolder.ts | 89 --------- .../fuse/callbacks/RenameOrMoveCallback.ts | 54 ------ .../drive/fuse/callbacks/TrashFileCallback.ts | 41 ---- .../callbacks/TrashFolderCallback.test.ts | 103 ---------- .../fuse/callbacks/TrashFolderCallback.ts | 79 -------- .../fuse/callbacks/UploadOnRename.test.ts | 171 ----------------- .../drive/fuse/callbacks/UploadOnRename.ts | 79 -------- .../drive/fuse/callbacks/WriteCallback.ts | 12 -- src/apps/drive/index.ts | 69 ------- .../drive/virtual-drive/VirtualDrive.test.ts | 68 ------- src/apps/drive/virtual-drive/VirtualDrive.ts | 57 ------ .../fuse/on-open/open-flags-tracker.test.ts | 114 ----------- .../fuse/on-open/open-flags-tracker.ts | 77 -------- .../handle-release-callback.test.ts | 107 ----------- .../on-release/handle-release-callback.ts | 44 ----- 29 files changed, 2093 deletions(-) delete mode 100644 src/apps/drive/fuse/FuseApp.ts delete mode 100644 src/apps/drive/fuse/callbacks/CreateCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/FuseCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/GetAttributesCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/GetXAttributeCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/MakeDirectoryCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/OpenCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/ReadCallback.test.ts delete mode 100644 src/apps/drive/fuse/callbacks/ReadCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/ReaddirCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/ReleaseCallback.test.ts delete mode 100644 src/apps/drive/fuse/callbacks/ReleaseCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/RenameMoveOrTrashFile.ts delete mode 100644 src/apps/drive/fuse/callbacks/RenameMoveOrTrashFolder.ts delete mode 100644 src/apps/drive/fuse/callbacks/RenameOrMoveCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/TrashFileCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/TrashFolderCallback.test.ts delete mode 100644 src/apps/drive/fuse/callbacks/TrashFolderCallback.ts delete mode 100644 src/apps/drive/fuse/callbacks/UploadOnRename.test.ts delete mode 100644 src/apps/drive/fuse/callbacks/UploadOnRename.ts delete mode 100644 src/apps/drive/fuse/callbacks/WriteCallback.ts delete mode 100644 src/apps/drive/index.ts delete mode 100644 src/apps/drive/virtual-drive/VirtualDrive.test.ts delete mode 100644 src/apps/drive/virtual-drive/VirtualDrive.ts delete mode 100644 src/backend/features/fuse/on-open/open-flags-tracker.test.ts delete mode 100644 src/backend/features/fuse/on-open/open-flags-tracker.ts delete mode 100644 src/backend/features/fuse/on-release/handle-release-callback.test.ts delete mode 100644 src/backend/features/fuse/on-release/handle-release-callback.ts diff --git a/src/apps/drive/fuse/FuseApp.ts b/src/apps/drive/fuse/FuseApp.ts deleted file mode 100644 index ab9e9eb0da..0000000000 --- a/src/apps/drive/fuse/FuseApp.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Container } from 'diod'; -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { VirtualDrive } from '../virtual-drive/VirtualDrive'; -import { FuseDriveStatus } from './FuseDriveStatus'; -import { CreateCallback } from './callbacks/CreateCallback'; -import { GetAttributesCallback } from './callbacks/GetAttributesCallback'; -import { GetXAttributeCallback } from './callbacks/GetXAttributeCallback'; -import { MakeDirectoryCallback } from './callbacks/MakeDirectoryCallback'; -import { OpenCallback } from './callbacks/OpenCallback'; -// import { ReadCallback } from './callbacks/ReadCallback'; -import { ReaddirCallback } from './callbacks/ReaddirCallback'; -import { ReleaseCallback } from './callbacks/ReleaseCallback'; -import { RenameMoveOrTrashCallback } from './callbacks/RenameOrMoveCallback'; -import { TrashFileCallback } from './callbacks/TrashFileCallback'; -import { TrashFolderCallback } from './callbacks/TrashFolderCallback'; -import { WriteCallback } from './callbacks/WriteCallback'; -// import { mountPromise } from './helpers'; -import { execFile } from 'node:child_process'; -import { EventEmitter } from 'stream'; - -export class FuseApp extends EventEmitter { - private status: FuseDriveStatus = 'UNMOUNTED'; - private static readonly MAX_INT_32 = 2147483647; - private static readonly MAX_RETRIES = 5; - // private _fuse: Fuse | undefined; - - constructor( - private readonly virtualDrive: VirtualDrive, - private readonly container: Container, - private readonly localRoot: string, - // private readonly remoteRoot: number, - // private readonly remoteRootUuid: string, - ) { - super(); - } - - private getOpt() { - const readdir = new ReaddirCallback(this.container); - const getattr = new GetAttributesCallback(this.container); - const open = new OpenCallback(this.virtualDrive, this.container); - // const read = new ReadCallback(this.container); - const renameOrMove = new RenameMoveOrTrashCallback(this.container); - const create = new CreateCallback(this.container); - const makeDirectory = new MakeDirectoryCallback(this.container); - const trashFile = new TrashFileCallback(this.container); - const trashFolder = new TrashFolderCallback(this.container); - const write = new WriteCallback(this.container); - const release = new ReleaseCallback(this.container); - const getXAttributes = new GetXAttributeCallback(this.virtualDrive); - - return { - getattr: getattr.handle.bind(getattr), - readdir: readdir.handle.bind(readdir), - open: open.handle.bind(open), - // read: read.execute.bind(read), - rename: renameOrMove.handle.bind(renameOrMove), - create: create.handle.bind(create), - write: write.execute.bind(write), - mkdir: makeDirectory.handle.bind(makeDirectory), - release: release.handle.bind(release), - unlink: trashFile.handle.bind(trashFile), - rmdir: trashFolder.handle.bind(trashFolder), - getxattr: getXAttributes.handle.bind(getXAttributes), - }; - } - - // async start() { - // const ops = this.getOpt(); - - // this._fuse = new Fuse(this.localRoot, ops, { - // debug: false, - // force: true, - // autoUnmount: true, - // maxRead: FuseApp.MAX_INT_32, - // }); - - // const mountSuccessful = await this.mountWithRetries(); - // if (!mountSuccessful) { - // logger.error({ msg: '[FUSE] mount error after max retries' }); - // this.emit('mount-error'); - // return; - // } - - // await this.update(); - // } - - async stop() { - // if (!this._fuse) { - // return; - // } - - await this.unmountFuse(); - // this._fuse = undefined; - this.status = 'UNMOUNTED'; - } - - private unmountFuse(): Promise { - const fusermount = '/usr/bin/fusermount'; - return new Promise((resolve) => { - execFile(fusermount, ['-u', this.localRoot], (err) => { - if (!err) { - resolve(); - return; - } - logger.debug({ msg: '[FUSE] non-lazy unmount failed, trying lazy unmount', error: err }); - execFile(fusermount, ['-uz', this.localRoot], (err2) => { - if (err2) { - logger.error({ msg: '[FUSE] lazy unmount failed:', error: err2 }); - } - resolve(); - }); - }); - }); - } - - // async clearCache(): Promise { - // clearHydrationState(); - // await this.container.get(StorageClearer).run(); - // } - - // async update() { - // try { - // const tree = await this.container.get(RemoteTreeBuilder) - // .run(this.remoteRoot, this.remoteRootUuid); - - // Promise.all([ - // this.container.get(FileRepositorySynchronizer).run(tree.files), - // this.container.get(FolderRepositorySynchronizer).run(tree.folders), - // this.container.get(StorageRemoteChangesSyncher).run(), - // ]); - - // logger.debug({ msg: '[FUSE] Tree updated successfully' }); - // } catch (err) { - // logger.error({ msg: '[FUSE] Error Updating the tree:', error: err }); - // } - // } - - getStatus() { - return this.status; - } - - async mount() { - if (this.status === 'MOUNTED') { - logger.debug({ msg: '[FUSE] Already mounted' }); - return this.status; - } - - // if (!this._fuse) { - // logger.error({ msg: '[FUSE] Cannot mount: FUSE instance not initialized' }); - // return this.status; - // } - - try { - // await mountPromise(this._fuse); - this.status = 'MOUNTED'; - this.emit('mounted'); - } catch (err) { - this.status = 'ERROR'; - logger.error({ msg: '[FUSE] mount error:', error: err }); - } - - return this.status; - } - - // private async mountWithRetries(): Promise { - // for (let attempt = 1; attempt <= FuseApp.MAX_RETRIES; attempt++) { - // const status = await this.mount(); - - // if (status === 'MOUNTED') return true; - - // if (attempt < FuseApp.MAX_RETRIES) { - // const delay = Math.min(1000 * attempt, 3000); - // await new Promise((resolve) => setTimeout(resolve, delay)); - // } - // } - - // return false; - // } -} diff --git a/src/apps/drive/fuse/callbacks/CreateCallback.ts b/src/apps/drive/fuse/callbacks/CreateCallback.ts deleted file mode 100644 index 272342884e..0000000000 --- a/src/apps/drive/fuse/callbacks/CreateCallback.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Container } from 'diod'; -import { NotifyFuseCallback } from './FuseCallback'; -import { TemporalFileCreator } from '../../../../context/storage/TemporalFiles/application/creation/TemporalFileCreator'; - -export class CreateCallback extends NotifyFuseCallback { - constructor(private readonly container: Container) { - super('Create'); - } - - async execute(path: string) { - await this.container.get(TemporalFileCreator).run(path); - - return this.right(); - } -} diff --git a/src/apps/drive/fuse/callbacks/FuseCallback.ts b/src/apps/drive/fuse/callbacks/FuseCallback.ts deleted file mode 100644 index 9c762411df..0000000000 --- a/src/apps/drive/fuse/callbacks/FuseCallback.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { Either, right, left } from '../../../../context/shared/domain/Either'; -import { Stopwatch } from '../../../shared/types/Stopwatch'; -import { FuseError, FuseUnknownError } from './FuseErrors'; -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { PathsToIgnore } from './PathsToIgnore'; -import { FuseCodes } from './FuseCodes'; - -export type Callback = (code: number) => void; - -export type CallbackWithData = (code: number, params?: T) => void; - -type DebugOptions = { - input: boolean; - output: boolean; - debug: boolean; - elapsedTime: boolean; - detectSlowCallbacks: boolean; -}; - -export abstract class FuseCallback { - protected static readonly OK = 0; - - constructor( - protected readonly name: string, - protected readonly debug: Partial = { - input: false, - output: false, - debug: false, - elapsedTime: false, - detectSlowCallbacks: true, - }, - ) {} - - protected async executeAndCatch(params: unknown[]): Promise> { - // Ensure that an Either is always returned - - const stopwatch = new Stopwatch(); - let interval: NodeJS.Timeout | undefined = undefined; - - if (this.debug.detectSlowCallbacks) { - interval = setInterval(() => { - const et = stopwatch.elapsedTime(); - - if (et > 10) { - logger.debug({ msg: `${this.name} ${params} is running for ${et}ms` }); - } - }, 2_000); - } - - try { - stopwatch.start(); - - const result = await this.execute(...params); - - return result; - } catch (throwed: unknown) { - logger.error({ msg: 'Error in FUSE callback', error: throwed }); - if (throwed instanceof FuseError) { - return this.left(throwed); - } - - return this.left(new FuseUnknownError()); - } finally { - if (this.debug.elapsedTime) { - logger.debug({ - msg: `Elapsed time for ${this.name}: ${params[0]}`, - elapsedTime: stopwatch.elapsedTime(), - }); - } - - if (interval) { - clearInterval(interval); - } - } - } - - protected right(value: T): Either { - if (this.debug.output) { - logger.debug({ - msg: `${this.name} Result: ${JSON.stringify({ value }, null, 2)}`, - }); - } - - return right(value); - } - - protected left(error: FuseError): Either; - protected left(error: unknown): Either; - - protected left(error: FuseError | unknown): Either { - if (error instanceof FuseError) { - logger.error({ msg: `${this.name}`, error }); - return left(error); - } - - logger.error({ msg: `${this.name} Error:`, error }); - return left(new FuseUnknownError()); - } - - protected logDebugMessage(...message: Array): void { - if (!this.debug) return; - - logger.debug({ msg: `${this.name}: `, message }); - } - - async handle(...params: unknown[]): Promise { - const callback = params.pop() as CallbackWithData; - - if (typeof params[0] === 'string' && PathsToIgnore.some((regex) => regex.test(params[0] as string))) { - return callback(FuseCodes.EINVAL); - } - - if (this.debug.input) { - logger.debug({ msg: `${this.name}: `, params }); - } - - const result = await this.executeAndCatch(params); - - if (result.isLeft()) { - const error = result.getLeft(); - return callback(error.code); - } - - const data = result.getRight(); - - callback(FuseCallback.OK, data); - } - - abstract execute(...params: unknown[]): Promise>; -} - -export abstract class NotifyFuseCallback extends FuseCallback { - protected right(): Either { - return right(undefined); - } - - async handle(...params: unknown[]): Promise { - const callback = params.pop() as Callback; - - if (this.debug.input) { - logger.debug({ msg: `${this.name}: `, params }); - } - - const result = await this.executeAndCatch(params); - - if (result.isLeft()) { - const error = result.getLeft(); - - if (this.debug.output) { - logger.debug({ msg: `${this.name}`, error }); - } - - return callback(error.code); - } - - if (this.debug.output) { - logger.debug({ msg: `${this.name} completed successfully ${params[0]}` }); - } - - callback(NotifyFuseCallback.OK); - } -} diff --git a/src/apps/drive/fuse/callbacks/FuseErrors.ts b/src/apps/drive/fuse/callbacks/FuseErrors.ts index 1ff7dbf469..94a24cdf2f 100644 --- a/src/apps/drive/fuse/callbacks/FuseErrors.ts +++ b/src/apps/drive/fuse/callbacks/FuseErrors.ts @@ -17,12 +17,6 @@ export class FuseNoSuchFileOrDirectoryError extends FuseError { } } -export class FuseFileOrDirectoryAlreadyExistsError extends FuseError { - constructor() { - super(FuseCodes.EEXIST, 'File or directory already exists.'); - } -} - export class FuseIOError extends FuseError { constructor(details?: string) { super(FuseCodes.EIO, `Input/output error${details ? `: ${details}` : ''}.`); diff --git a/src/apps/drive/fuse/callbacks/GetAttributesCallback.ts b/src/apps/drive/fuse/callbacks/GetAttributesCallback.ts deleted file mode 100644 index 535ac6c11d..0000000000 --- a/src/apps/drive/fuse/callbacks/GetAttributesCallback.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Container } from 'diod'; -import { Either, left } from '../../../../context/shared/domain/Either'; -import { FileStatuses } from '../../../../context/virtual-drive/files/domain/FileStatus'; -import { FuseCallback } from './FuseCallback'; -import { FuseError, FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { SingleFolderMatchingSearcher } from '../../../../context/virtual-drive/folders/application/SingleFolderMatchingSearcher'; -import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; - -type GetAttributesCallbackData = { - mode: number; - size: number; - mtime: Date; - ctime: Date; - atime?: Date; - uid: number; - gid: number; -}; - -export class GetAttributesCallback extends FuseCallback { - private static readonly FILE = 33188; - private static readonly FOLDER = 16877; - - constructor(private readonly container: Container) { - super('Get Attributes'); - } - - protected left(error: FuseNoSuchFileOrDirectoryError): Either { - // When the OS wants to check if a node exists will try to get the attributes of it - // so not founding them is not an error - return left(error); - } - - async execute(path: string) { - if (path === '/') { - return this.right({ - mode: GetAttributesCallback.FOLDER, - size: 0, - mtime: new Date(), - ctime: new Date(), - atime: undefined, - uid: process.getuid?.() || 0, - gid: process.getgid?.() || 0, - }); - } - - const file = await this.container.get(FirstsFileSearcher).run({ - path, - status: FileStatuses.EXISTS, - }); - - if (file) { - return this.right({ - mode: GetAttributesCallback.FILE, - size: file.size, - ctime: file.createdAt, - mtime: file.updatedAt, - atime: new Date(), - uid: process.getuid?.() || 0, - gid: process.getgid?.() || 0, - }); - } - - const folder = await this.container.get(SingleFolderMatchingSearcher).run({ - path, - }); - - if (folder) { - return this.right({ - mode: GetAttributesCallback.FOLDER, - size: 0, - ctime: folder.createdAt, - mtime: folder.updatedAt, - atime: folder.createdAt, - uid: process.getuid?.() || 0, - gid: process.getgid?.() || 0, - }); - } - - const document = await this.container.get(TemporalFileByPathFinder).run(path); - - if (document) { - return this.right({ - mode: GetAttributesCallback.FILE, - size: document.size.value, - mtime: new Date(), - ctime: document.createdAt, - atime: document.createdAt, - uid: process.getuid?.() || 0, - gid: process.getgid?.() || 0, - }); - } - - return this.left(new FuseNoSuchFileOrDirectoryError(path)); - } -} diff --git a/src/apps/drive/fuse/callbacks/GetXAttributeCallback.ts b/src/apps/drive/fuse/callbacks/GetXAttributeCallback.ts deleted file mode 100644 index 2e6095365e..0000000000 --- a/src/apps/drive/fuse/callbacks/GetXAttributeCallback.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FuseCallback } from './FuseCallback'; -import { VirtualDrive } from '../../virtual-drive/VirtualDrive'; -import { FuseError, FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -import { FuseCodes } from './FuseCodes'; - -export class GetXAttributeCallback extends FuseCallback { - constructor(private readonly drive: VirtualDrive) { - super('Get X Attribute', { - input: true, - elapsedTime: false, - output: false, - }); - } - - private isRootFolder(path: string): boolean { - return path === '/'; - } - - async execute(path: string) { - if (this.isRootFolder(path)) { - return this.left(new FuseError(FuseCodes.ENOSYS, 'Cannot get the status of root folder')); - } - - try { - const isAvailableLocally = await this.drive.isLocallyAvailable(path); - - if (isAvailableLocally) { - return this.right(Buffer.from('on_local')); - } - - const buff = Buffer.from('on_remote'); - return this.right(buff); - } catch (err) { - return this.left(new FuseNoSuchFileOrDirectoryError(path)); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/MakeDirectoryCallback.ts b/src/apps/drive/fuse/callbacks/MakeDirectoryCallback.ts deleted file mode 100644 index a7e365ac8a..0000000000 --- a/src/apps/drive/fuse/callbacks/MakeDirectoryCallback.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Container } from 'diod'; -import { basename } from 'path'; -import { FolderCreator } from '../../../../context/virtual-drive/folders/application/create/FolderCreator'; -import { SyncFolderMessenger } from '../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; -import { NotifyFuseCallback } from './FuseCallback'; - -export class MakeDirectoryCallback extends NotifyFuseCallback { - constructor(private readonly container: Container) { - super('Make Directory'); - } - - async execute(path: string) { - if (path.startsWith('/.Trash')) { - return this.right(); - } - - try { - await this.container.get(SyncFolderMessenger).creating(path); - - await this.container.get(FolderCreator).run(path); - - await this.container.get(SyncFolderMessenger).created(path); - - return this.right(); - } catch (throwed: unknown) { - await this.container.get(SyncFolderMessenger).issue({ - error: 'FOLDER_CREATE_ERROR', - cause: 'UNKNOWN', - name: basename(path), - }); - - return this.left(throwed); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/OpenCallback.ts b/src/apps/drive/fuse/callbacks/OpenCallback.ts deleted file mode 100644 index 652f0e2029..0000000000 --- a/src/apps/drive/fuse/callbacks/OpenCallback.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { VirtualDrive } from '../../virtual-drive/VirtualDrive'; -import { FuseCallback } from './FuseCallback'; -import { FuseFileOrDirectoryAlreadyExistsError, FuseIOError, FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { Container } from 'diod'; -import { trackOpen } from '../../../../backend/features/fuse/on-open/open-flags-tracker'; -import { TemporalFile } from '../../../../context/storage/TemporalFiles/domain/TemporalFile'; - -export class OpenCallback extends FuseCallback { - constructor( - private readonly virtualDrive: VirtualDrive, - private readonly container: Container, - ) { - super('Open'); - } - - async execute(path: string, flag: number) { - trackOpen(path, flag); - - try { - const virtualFile = await this.container.get(FirstsFileSearcher).run({ path }); - - if (!virtualFile) { - const temporalFileExists = await this.virtualDrive.temporalFileExists(path); - - if (temporalFileExists.isLeft() || temporalFileExists.getLeft()) { - return this.left(new FuseNoSuchFileOrDirectoryError(path)); - } - } - - return this.right(0); - } catch (err) { - if (TemporalFile.isTemporaryPath(path)) { - return this.left(new FuseFileOrDirectoryAlreadyExistsError()); - } - - logger.error({ msg: '[OpenCallback] Error:', error: err, path }); - return this.left(new FuseIOError()); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/ReadCallback.test.ts b/src/apps/drive/fuse/callbacks/ReadCallback.test.ts deleted file mode 100644 index ae35711c93..0000000000 --- a/src/apps/drive/fuse/callbacks/ReadCallback.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -// import Fuse from '@gcas/fuse'; -// import { ReadCallback } from './ReadCallback'; -// import * as handleReadModule from '../../../../backend/features/fuse/on-read/handle-read-callback'; -// import { partialSpyOn } from '../../../../../tests/vitest/utils.helper'; -// import { left, right } from '../../../../context/shared/domain/Either'; -// import { FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -// import { type Container } from 'diod'; - -// const handleReadCallbackMock = partialSpyOn(handleReadModule, 'handleReadCallback'); - -// function createMockContainer() { -// return { -// get: vi.fn().mockReturnValue({ -// run: vi.fn(), -// exists: vi.fn(), -// register: vi.fn(), -// downloadStarted: vi.fn(), -// downloadUpdate: vi.fn(), -// downloadFinished: vi.fn(), -// elapsedTime: vi.fn(), -// }), -// } as Partial as Container; -// } - -describe.skip('ReadCallback', () => { - // it('should copy chunk into buf and call cb with chunk length on success', async () => { - // const chunk = Buffer.from('hello'); - // handleReadCallbackMock.mockResolvedValue(right(chunk)); - // const buf = Buffer.alloc(10); - // const cb = vi.fn(); - // const callback = new ReadCallback(createMockContainer()); - // await callback.execute('/file.txt', 0, buf, 5, 0, cb); - // expect(buf.subarray(0, 5).toString()).toBe('hello'); - // expect(cb).toHaveBeenCalledWith(5); - // }); - // it('should call cb with error code when result is left', async () => { - // const error = new FuseNoSuchFileOrDirectoryError('/file.txt'); - // handleReadCallbackMock.mockResolvedValue(left(error)); - // const cb = vi.fn(); - // const callback = new ReadCallback(createMockContainer()); - // await callback.execute('/file.txt', 0, Buffer.alloc(10), 10, 0, cb); - // expect(cb).toHaveBeenCalledWith(error.code); - // }); - // it('should call cb with Fuse.EIO when an exception is thrown', async () => { - // handleReadCallbackMock.mockRejectedValue(new Error('unexpected')); - // const cb = vi.fn(); - // const callback = new ReadCallback(createMockContainer()); - // await callback.execute('/file.txt', 0, Buffer.alloc(10), 10, 0, cb); - // expect(cb).toHaveBeenCalledWith(Fuse.EIO); - // }); - // it('should call cb with 0 when result is an empty buffer', async () => { - // handleReadCallbackMock.mockResolvedValue(right(Buffer.alloc(0))); - // const cb = vi.fn(); - // const callback = new ReadCallback(createMockContainer()); - // await callback.execute('/file.txt', 0, Buffer.alloc(10), 10, 0, cb); - // expect(cb).toHaveBeenCalledWith(0); - // }); -}); diff --git a/src/apps/drive/fuse/callbacks/ReadCallback.ts b/src/apps/drive/fuse/callbacks/ReadCallback.ts deleted file mode 100644 index 055810e5f9..0000000000 --- a/src/apps/drive/fuse/callbacks/ReadCallback.ts +++ /dev/null @@ -1,73 +0,0 @@ -// import { Container } from 'diod'; -// import { logger } from '@internxt/drive-desktop-core/build/backend'; -// import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -// import { TemporalFileChunkReader } from '../../../../context/storage/TemporalFiles/application/read/TemporalFileChunkReader'; -// import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -// import { StorageFilesRepository } from '../../../../context/storage/StorageFiles/domain/StorageFilesRepository'; -// import { StorageFile } from '../../../../context/storage/StorageFiles/domain/StorageFile'; -// import { DownloadProgressTracker } from '../../../../context/shared/domain/DownloadProgressTracker'; -// import { -// handleReadCallback, -// type HandleReadCallbackDeps, -// } from '../../../../backend/features/fuse/on-read/handle-read-callback'; -// import { buildNetworkClient } from '../../../../infra/environment/download-file/build-network-client'; -// import { getCredentials } from '../../../main/auth/get-credentials'; -// import { DependencyInjectionUserProvider } from '../../../shared/dependency-injection/DependencyInjectionUserProvider'; - -// export class ReadCallback { -// constructor(private readonly container: Container) {} - -// async execute( -// path: string, -// _fd: unknown, -// buf: Buffer, -// len: number, -// pos: number, -// cb: (code: number, params?: unknown) => void, -// ) { -// try { -// const { mnemonic } = getCredentials(); -// const user = DependencyInjectionUserProvider.get(); -// const network = buildNetworkClient({ bridgeUser: user.bridgeUser, userId: user.userId }); -// const repo = this.container.get(StorageFilesRepository); -// const tracker = this.container.get(DownloadProgressTracker); - -// const deps: HandleReadCallbackDeps = { -// findVirtualFile: (p: string) => this.container.get(FirstsFileSearcher).run({ path: p }), -// findTemporalFile: (p: string) => this.container.get(TemporalFileByPathFinder).run(p), -// readTemporalFileChunk: async (p: string, length: number, position: number) => { -// const result = await this.container.get(TemporalFileChunkReader).run(p, length, position); -// return result.isPresent() ? result.get() : undefined; -// }, -// onDownloadProgress: (name, extension, bytesDownloaded, fileSize, elapsedTime) => { -// tracker.downloadUpdate(name, extension, { -// percentage: Math.min(bytesDownloaded / fileSize, 1), -// elapsedTime, -// }); -// }, -// saveToRepository: async (contentsId, size, uuid, name, extension) => { -// const storage = StorageFile.from({ id: contentsId, virtualId: uuid, size }); -// await repo.register(storage); -// tracker.downloadFinished(name, extension); -// }, -// bucketId: user.bucket, -// mnemonic, -// network, -// }; - -// const result = await handleReadCallback(deps, path, len, pos); - -// if (result.isLeft()) { -// cb(result.getLeft().code); -// return; -// } - -// const chunk = result.getRight(); -// chunk.copy(buf as unknown as Uint8Array); -// cb(chunk.length); -// } catch (err) { -// logger.error({ msg: '[ReadCallback] Error reading file:', error: err, path }); -// cb(Fuse.EIO); -// } -// } -// } diff --git a/src/apps/drive/fuse/callbacks/ReaddirCallback.ts b/src/apps/drive/fuse/callbacks/ReaddirCallback.ts deleted file mode 100644 index 37bcbc043f..0000000000 --- a/src/apps/drive/fuse/callbacks/ReaddirCallback.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Container } from 'diod'; -import { FuseCallback } from './FuseCallback'; -import { FilesByFolderPathSearcher } from '../../../../context/virtual-drive/files/application/search/FilesByFolderPathSearcher'; -import { FoldersByParentPathLister } from '../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; -import { TemporalFileByFolderFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByFolderFinder'; -import { FuseIOError } from './FuseErrors'; -import { logger } from '@internxt/drive-desktop-core/build/backend'; - -export class ReaddirCallback extends FuseCallback> { - constructor(private readonly container: Container) { - super('Read Directory'); - } - - async execute(path: string) { - try { - const start = performance.now(); - const [filesNames, foldersNames, temporalFiles] = await Promise.all([ - this.container.get(FilesByFolderPathSearcher).run(path), - this.container.get(FoldersByParentPathLister).run(path), - this.container.get(TemporalFileByFolderFinder).run(path), - ]); - - const endPromises = performance.now(); - logger.debug({ - msg: `[ReaddirCallback] Time taken on Promises: ${endPromises - start}ms`, - }); - - const auxiliaryFileName = temporalFiles.reduce((acc, file) => { - if (file.isAuxiliary()) { - acc.push(file.name); - } - return acc; - }, [] as string[]); - - const endReduce = performance.now(); - logger.debug({ - msg: `[ReaddirCallback] Time taken on Reduce: ${endReduce - endPromises}ms`, - }); - - const end = performance.now(); - logger.debug({ msg: `[ReaddirCallback] Time taken on Total: ${end - start}ms` }); - - return this.right(['.', '..', ...filesNames, ...foldersNames, ...auxiliaryFileName]); - } catch (error) { - logger.error({ msg: '[ReaddirCallback] Error reading directory:', error }); - return this.left(new FuseIOError()); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/ReleaseCallback.test.ts b/src/apps/drive/fuse/callbacks/ReleaseCallback.test.ts deleted file mode 100644 index c28d0bf1a2..0000000000 --- a/src/apps/drive/fuse/callbacks/ReleaseCallback.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, vi } from 'vitest'; -import { call } from '../../../../../tests/vitest/utils.helper'; -import { ReleaseCallback } from './ReleaseCallback'; -import { right } from '../../../../context/shared/domain/Either'; -import * as openFlagsTracker from './../../../../backend/features/fuse/on-open/open-flags-tracker'; -import * as handleReleaseModule from '../../../../backend/features/fuse/on-release/handle-release-callback'; -import { partialSpyOn } from '../../../../../tests/vitest/utils.helper'; -import { Container } from 'diod'; - -vi.mock(import('@internxt/drive-desktop-core/build/backend')); - -describe('ReleaseCallback', () => { - const onReleaseSpy = partialSpyOn(openFlagsTracker, 'onRelease'); - const handleReleaseSpy = partialSpyOn(handleReleaseModule, 'handleReleaseCallback'); - - const container = { get: vi.fn() } as unknown as Container; - const releaseCallback = new ReleaseCallback(container); - it('should call onRelease to clean up open flags tracker', async () => { - handleReleaseSpy.mockResolvedValue(right(undefined)); - - await releaseCallback.execute('/Documents/file.pdf', 3); - - call(onReleaseSpy).toBe('/Documents/file.pdf'); - }); - - it('should delegate to handleReleaseCallback', async () => { - handleReleaseSpy.mockResolvedValue(right(undefined)); - - await releaseCallback.execute('/Documents/file.pdf', 3); - - call(handleReleaseSpy).toMatchObject({ path: '/Documents/file.pdf' }); - }); - - it('should return the result from handleReleaseCallback', async () => { - handleReleaseSpy.mockResolvedValue(right(undefined)); - - const result = await releaseCallback.execute('/Documents/file.pdf', 3); - - expect(result.isRight()).toBe(true); - }); -}); diff --git a/src/apps/drive/fuse/callbacks/ReleaseCallback.ts b/src/apps/drive/fuse/callbacks/ReleaseCallback.ts deleted file mode 100644 index 377648d402..0000000000 --- a/src/apps/drive/fuse/callbacks/ReleaseCallback.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Container } from 'diod'; -import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -import { TemporalFileUploader } from '../../../../context/storage/TemporalFiles/application/upload/TemporalFileUploader'; -import { NotifyFuseCallback } from './FuseCallback'; -import { FuseError } from './FuseErrors'; -import { TemporalFileDeleter } from '../../../../context/storage/TemporalFiles/application/deletion/TemporalFileDeleter'; -import { onRelease } from '../../../../backend/features/fuse/on-open/open-flags-tracker'; -import { Either } from '../../../../context/shared/domain/Either'; -import { handleReleaseCallback } from '../../../../backend/features/fuse/on-release/handle-release-callback'; - -/** - * FUSE release callback: - * called when the last file descriptor for an open file is closed. - * This is the counterpart to OpenCallback. For every open() there will eventually be a release(). - * - * If the user accesses a file on the virtual drive triggers this lifecycle: - * open (OpenCallback) → read/write (ReadCallback) → release (ReleaseCallback) - * - * to read more about this: - * https://libfuse.github.io/doxygen/structfuse__operations.html#abac8718cdfc1ee273a44831a27393419 - * - * @example `md5sum file.mp4`, `vlc video.mp4`, or Nautilus previewing a file - * will all trigger a release once the program finishes reading and closes the file descriptor. - */ -export class ReleaseCallback extends NotifyFuseCallback { - constructor(private readonly container: Container) { - super('Release', { debug: false }); - } - - /** - * @param path - the virtual drive path of the file being released - * @param _fileDescriptor - a number assigned by the OS kernel when the file was opened, - * used to track which open file instance this release corresponds to (e.g. fd = 3). - * The same fd flows through open → read → release. Currently unused — we identify files by path instead. - */ - async execute(path: string, _fileDescriptor: number): Promise> { - onRelease(path); - - return await handleReleaseCallback({ - path, - findTemporalFile: (p) => this.container.get(TemporalFileByPathFinder).run(p), - uploadTemporalFile: (p) => this.container.get(TemporalFileUploader).run(p), - deleteTemporalFile: (p) => this.container.get(TemporalFileDeleter).run(p), - }); - } -} diff --git a/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFile.ts b/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFile.ts deleted file mode 100644 index 2e819096f5..0000000000 --- a/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFile.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Container } from 'diod'; -import path from 'path'; -import { Either, left, right } from '../../../../context/shared/domain/Either'; -import { DriveDesktopError } from '../../../../context/shared/domain/errors/DriveDesktopError'; -import { FileTrasher } from '../../../../context/virtual-drive/files/application/trash/FileTrasher'; -import { FilePathUpdater } from '../../../../context/virtual-drive/files/application/move/FilePathUpdater'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { File } from '../../../../context/virtual-drive/files/domain/File'; -import { FilePath } from '../../../../context/virtual-drive/files/domain/FilePath'; -import { FileStatuses } from '../../../../context/virtual-drive/files/domain/FileStatus'; -import { SyncFileMessenger } from '../../../../context/virtual-drive/files/domain/SyncFileMessenger'; -import { SyncError } from '../../../../shared/issues/SyncErrorCause'; -import { FuseError, FuseUnknownError } from './FuseErrors'; - -type RenameOrMoveRight = 'no-op' | 'success'; - -export class RenameMoveOrTrashFile { - private static readonly NO_OP: RenameOrMoveRight = 'no-op'; - private static readonly SUCCESS: RenameOrMoveRight = 'success'; - - constructor(private readonly container: Container) {} - - private async trash(file: File): Promise { - try { - await this.container.get(FileTrasher).run(file.contentsId); - return undefined; - } catch (trowed: unknown) { - const cause: SyncError = trowed instanceof DriveDesktopError ? trowed.cause : 'UNKNOWN'; - - await this.container.get(SyncFileMessenger).issues({ - error: 'DELETE_ERROR', - cause, - name: file.name, - }); - - if (trowed instanceof FuseError) { - return trowed; - } - - return new FuseUnknownError(); - } - } - - async execute(src: string, dest: string): Promise> { - const file = await this.container.get(FirstsFileSearcher).run({ - path: src, - status: FileStatuses.EXISTS, - }); - - if (!file) { - return right(RenameMoveOrTrashFile.NO_OP); - } - - if (dest.startsWith('/.Trash')) { - const error = await this.trash(file); - - if (!error) { - return right(RenameMoveOrTrashFile.SUCCESS); - } - - return left(error); - } - - try { - const desiredPath = new FilePath(dest); - - await this.container.get(SyncFileMessenger).renaming(file.nameWithExtension, desiredPath.nameWithExtension()); - - await this.container.get(FilePathUpdater).run(file.contentsId, dest); - - await this.container.get(SyncFileMessenger).renamed(file.nameWithExtension, desiredPath.nameWithExtension()); - - return right(RenameMoveOrTrashFile.SUCCESS); - } catch (trowed: unknown) { - const cause: SyncError = trowed instanceof DriveDesktopError ? trowed.cause : 'UNKNOWN'; - - await this.container.get(SyncFileMessenger).issues({ - error: 'RENAME_ERROR', - cause, - name: path.basename(src), - }); - - if (trowed instanceof FuseError) { - return left(trowed); - } - - return left(new FuseUnknownError()); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFolder.ts b/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFolder.ts deleted file mode 100644 index d00a45ca9d..0000000000 --- a/src/apps/drive/fuse/callbacks/RenameMoveOrTrashFolder.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { basename } from 'path'; -import { FolderPath } from '../../../../context/virtual-drive/folders/domain/FolderPath'; -import { FuseError, FuseUnknownError } from './FuseErrors'; -import { Either, left, right } from '../../../../context/shared/domain/Either'; -import { FolderStatuses } from '../../../../context/virtual-drive/folders/domain/FolderStatus'; -import { Folder } from '../../../../context/virtual-drive/folders/domain/Folder'; -import { DriveDesktopError } from '../../../../context/shared/domain/errors/DriveDesktopError'; -import { SyncError } from '../../../../shared/issues/SyncErrorCause'; -import { Container } from 'diod'; -import { SyncFileMessenger } from '../../../../context/virtual-drive/files/domain/SyncFileMessenger'; -import { FolderDeleter } from '../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { FolderPathUpdater } from '../../../../context/virtual-drive/folders/application/FolderPathUpdater'; -import { SingleFolderMatchingSearcher } from '../../../../context/virtual-drive/folders/application/SingleFolderMatchingSearcher'; -import { SyncFolderMessenger } from '../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; - -type RenameOrMoveRight = 'no-op' | 'success'; - -export class RenameMoveOrTrashFolder { - private static readonly NO_OP: RenameOrMoveRight = 'no-op'; - private static readonly SUCCESS: RenameOrMoveRight = 'success'; - - constructor(private readonly container: Container) {} - - private async trash(folder: Folder): Promise { - try { - await this.container.get(FolderDeleter).run(folder.uuid); - return undefined; - } catch (trowed: unknown) { - const cause: SyncError = trowed instanceof DriveDesktopError ? trowed.cause : 'UNKNOWN'; - - await this.container.get(SyncFileMessenger).issues({ - error: 'DELETE_ERROR', - cause, - name: folder.name, - }); - - if (trowed instanceof FuseError) { - return trowed; - } - - return new FuseUnknownError(); - } - } - - async execute(src: string, dest: string): Promise> { - const folder = await this.container.get(SingleFolderMatchingSearcher).run({ - path: src, - status: FolderStatuses.EXISTS, - }); - - if (!folder) { - return right(RenameMoveOrTrashFolder.NO_OP); - } - - if (dest.startsWith('/.Trash')) { - const error = await this.trash(folder); - - if (!error) { - return right(RenameMoveOrTrashFolder.SUCCESS); - } - - return left(error); - } - - try { - const desiredPath = new FolderPath(dest); - - await this.container.get(SyncFolderMessenger).rename(folder.name, desiredPath.name()); - - await this.container.get(FolderPathUpdater).run(folder.uuid, dest); - - await this.container.get(SyncFolderMessenger).renamed(folder.name, desiredPath.name()); - - return right(RenameMoveOrTrashFolder.SUCCESS); - } catch (throwed: unknown) { - await this.container.get(SyncFolderMessenger).issue({ - error: 'FOLDER_RENAME_ERROR', - cause: 'UNKNOWN', - name: basename(src), - }); - - if (throwed instanceof FuseError) { - return left(throwed); - } - - return left(new FuseUnknownError()); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/RenameOrMoveCallback.ts b/src/apps/drive/fuse/callbacks/RenameOrMoveCallback.ts deleted file mode 100644 index cb1a2ac202..0000000000 --- a/src/apps/drive/fuse/callbacks/RenameOrMoveCallback.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Container } from 'diod'; -import { NotifyFuseCallback } from './FuseCallback'; -import { FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -import { RenameMoveOrTrashFile } from './RenameMoveOrTrashFile'; -import { RenameMoveOrTrashFolder } from './RenameMoveOrTrashFolder'; -import { UploadOnRename } from './UploadOnRename'; - -export class RenameMoveOrTrashCallback extends NotifyFuseCallback { - private readonly updateFile: RenameMoveOrTrashFile; - private readonly updateFolder: RenameMoveOrTrashFolder; - private readonly uploadOnRename: UploadOnRename; - - constructor(container: Container) { - super('Rename Move or Trash', { input: true, output: true }); - - this.updateFile = new RenameMoveOrTrashFile(container); - this.updateFolder = new RenameMoveOrTrashFolder(container); - this.uploadOnRename = new UploadOnRename(container); - } - - async execute(src: string, dest: string) { - const fileEither = await this.updateFile.execute(src, dest); - - if (fileEither.isLeft()) { - return this.left(fileEither.getLeft()); - } - - if (fileEither.getRight() === 'success') { - return this.right(); - } - - const folderEither = await this.updateFolder.execute(src, dest); - - if (folderEither.isLeft()) { - return this.left(folderEither.getLeft()); - } - - if (folderEither.getRight() === 'success') { - return this.right(); - } - - const offlineUploadEither = await this.uploadOnRename.run(src, dest); - - if (offlineUploadEither.isLeft()) { - return this.left(offlineUploadEither.getLeft()); - } - - if (offlineUploadEither.getRight() === 'success') { - return this.right(); - } - - return this.left(new FuseNoSuchFileOrDirectoryError(src)); - } -} diff --git a/src/apps/drive/fuse/callbacks/TrashFileCallback.ts b/src/apps/drive/fuse/callbacks/TrashFileCallback.ts deleted file mode 100644 index d2a1625e12..0000000000 --- a/src/apps/drive/fuse/callbacks/TrashFileCallback.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Container } from 'diod'; -import { FileTrasher } from '../../../../context/virtual-drive/files/application/trash/FileTrasher'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { FileStatuses } from '../../../../context/virtual-drive/files/domain/FileStatus'; -import { NotifyFuseCallback } from './FuseCallback'; -import { FuseIOError, FuseNoSuchFileOrDirectoryError } from './FuseErrors'; -import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -import { TemporalFileDeleter } from '../../../../context/storage/TemporalFiles/application/deletion/TemporalFileDeleter'; - -export class TrashFileCallback extends NotifyFuseCallback { - constructor(private readonly container: Container) { - super('Trash file'); - } - - async execute(path: string) { - const file = await this.container.get(FirstsFileSearcher).run({ - path, - status: FileStatuses.EXISTS, - }); - - if (!file) { - const document = await this.container.get(TemporalFileByPathFinder).run(path); - - if (!document) { - return this.left(new FuseNoSuchFileOrDirectoryError(path)); - } - - await this.container.get(TemporalFileDeleter).run(path); - - return this.right(); - } - - try { - await this.container.get(FileTrasher).run(file.contentsId); - - return this.right(); - } catch { - return this.left(new FuseIOError()); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/TrashFolderCallback.test.ts b/src/apps/drive/fuse/callbacks/TrashFolderCallback.test.ts deleted file mode 100644 index ab64869527..0000000000 --- a/src/apps/drive/fuse/callbacks/TrashFolderCallback.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { FolderDeleter } from '../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { SingleFolderMatchingFinder } from '../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; -import { FolderMother } from '../../../../context/virtual-drive/folders/domain/__test-helpers__/FolderMother'; -import { FolderStatuses } from '../../../../context/virtual-drive/folders/domain/FolderStatus'; -import { SyncFolderMessenger } from '../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; -import { ContainerMock } from '../../__mocks__/ContainerMock'; -import { TrashFolderCallback } from './TrashFolderCallback'; - -describe('TrashFolderCallback', () => { - it('returns success even when folder deletion exceeds callback timeout', async () => { - vi.useFakeTimers(); - - try { - const container = new ContainerMock(); - const folder = FolderMother.any(); - - const folderFinder = { - run: vi.fn(async () => { - return folder; - }), - } as unknown as SingleFolderMatchingFinder; - - const folderDeleter = { - run: vi.fn(() => { - return new Promise((resolve) => { - setTimeout(resolve, 5_000); - }); - }), - } as unknown as FolderDeleter; - - container.set(SingleFolderMatchingFinder, folderFinder); - container.set(FolderDeleter, folderDeleter); - - const callback = new TrashFolderCallback(container as never); - const resultPromise = callback.execute('/Files/SlowFolder'); - - await vi.advanceTimersByTimeAsync(1_600); - - const result = await resultPromise; - - expect(result.isRight()).toBe(true); - expect(folderFinder.run).toHaveBeenCalledWith({ - path: '/Files/SlowFolder', - status: FolderStatuses.EXISTS, - }); - expect(folderDeleter.run).toHaveBeenCalledWith(folder.uuid); - } finally { - vi.useRealTimers(); - } - }); - - it('reports issue when background deletion fails after timeout', async () => { - vi.useFakeTimers(); - - try { - const container = new ContainerMock(); - const folder = FolderMother.any(); - - const folderFinder = { - run: vi.fn(async () => { - return folder; - }), - } as unknown as SingleFolderMatchingFinder; - - const folderDeleter = { - run: vi.fn(() => { - return new Promise((_resolve, reject) => { - setTimeout(() => { - reject(new Error('slow-delete-failed')); - }, 5_000); - }); - }), - } as unknown as FolderDeleter; - - const syncFolderMessenger = { - issue: vi.fn(async () => undefined), - } as unknown as SyncFolderMessenger; - - container.set(SingleFolderMatchingFinder, folderFinder); - container.set(FolderDeleter, folderDeleter); - container.set(SyncFolderMessenger, syncFolderMessenger); - - const callback = new TrashFolderCallback(container as never); - const resultPromise = callback.execute('/Files/SlowFolder'); - - await vi.advanceTimersByTimeAsync(1_600); - - const result = await resultPromise; - expect(result.isRight()).toBe(true); - - await vi.advanceTimersByTimeAsync(3_500); - await Promise.resolve(); - - expect(syncFolderMessenger.issue).toHaveBeenCalledWith({ - error: 'FOLDER_TRASH_ERROR', - cause: 'UNKNOWN', - name: 'SlowFolder', - }); - } finally { - vi.useRealTimers(); - } - }); -}); diff --git a/src/apps/drive/fuse/callbacks/TrashFolderCallback.ts b/src/apps/drive/fuse/callbacks/TrashFolderCallback.ts deleted file mode 100644 index a93c7beb46..0000000000 --- a/src/apps/drive/fuse/callbacks/TrashFolderCallback.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Container } from 'diod'; -import { basename } from 'path'; -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { FolderDeleter } from '../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { SingleFolderMatchingFinder } from '../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; -import { FolderStatuses } from '../../../../context/virtual-drive/folders/domain/FolderStatus'; -import { SyncFolderMessenger } from '../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; -import { NotifyFuseCallback } from './FuseCallback'; - -const FOLDER_TRASH_CALLBACK_TIMEOUT_MS = 1_500; - -type WaitWithTimeoutPops = { - promise: Promise; - timeoutMs: number; -}; - -async function waitWithTimeout({ promise, timeoutMs }: WaitWithTimeoutPops) { - const completion = promise.then(() => true); - const timeout = new Promise((resolve) => { - setTimeout(() => { - resolve(false); - }, timeoutMs); - }); - - return Promise.race([completion, timeout]); -} - -export class TrashFolderCallback extends NotifyFuseCallback { - constructor(private readonly container: Container) { - super('Trash Folder'); - } - - async execute(path: string) { - try { - const folder = await this.container.get(SingleFolderMatchingFinder).run({ - path, - status: FolderStatuses.EXISTS, - }); - - const deletionPromise = this.container.get(FolderDeleter).run(folder.uuid); - const deletionCompletedInTime = await waitWithTimeout({ - promise: deletionPromise, - timeoutMs: FOLDER_TRASH_CALLBACK_TIMEOUT_MS, - }); - - if (!deletionCompletedInTime) { - logger.warn({ - msg: 'Folder deletion exceeded callback timeout. Continuing deletion in background.', - path, - timeoutMs: FOLDER_TRASH_CALLBACK_TIMEOUT_MS, - }); - - void deletionPromise.catch(async (error) => { - logger.error({ - msg: 'Background folder deletion failed after callback timeout', - path, - error, - }); - - await this.container.get(SyncFolderMessenger).issue({ - error: 'FOLDER_TRASH_ERROR', - cause: 'UNKNOWN', - name: basename(path), - }); - }); - } - - return this.right(); - } catch (throwed: unknown) { - await this.container.get(SyncFolderMessenger).issue({ - error: 'FOLDER_TRASH_ERROR', - cause: 'UNKNOWN', - name: basename(path), - }); - - return this.left(throwed); - } - } -} diff --git a/src/apps/drive/fuse/callbacks/UploadOnRename.test.ts b/src/apps/drive/fuse/callbacks/UploadOnRename.test.ts deleted file mode 100644 index d23fab095e..0000000000 --- a/src/apps/drive/fuse/callbacks/UploadOnRename.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { UploadOnRename } from './UploadOnRename'; -import { ContainerMock } from '../../__mocks__/ContainerMock'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -import { TemporalFileUploader } from '../../../../context/storage/TemporalFiles/application/upload/TemporalFileUploader'; -import { TemporalFileDeleter } from '../../../../context/storage/TemporalFiles/application/deletion/TemporalFileDeleter'; -import { RelativePathToAbsoluteConverter } from '../../../../context/virtual-drive/shared/application/RelativePathToAbsoluteConverter'; -import { TemporalFileByteByByteComparator } from '../../../../context/storage/TemporalFiles/application/comparation/TemporalFileByteByByteComparator'; -import { FileMother } from '../../../../context/virtual-drive/files/domain/__test-helpers__/FileMother'; -import { BucketEntryIdMother } from '../../../../context/virtual-drive/shared/domain/__test-helpers__/BucketEntryIdMother'; -import { TemporalFile } from '../../../../context/storage/TemporalFiles/domain/TemporalFile'; -import { TemporalFilePath } from '../../../../context/storage/TemporalFiles/domain/TemporalFilePath'; -import { TemporalFileSize } from '../../../../context/storage/TemporalFiles/domain/TemporalFileSize'; -import { calls, call } from 'tests/vitest/utils.helper'; -import { FileStatuses } from '../../../../context/virtual-drive/files/domain/FileStatus'; - -describe('UploadOnRename', () => { - let container: ContainerMock; - let uploadOnRename: UploadOnRename; - - const firstsFileSearcherMock = { run: vi.fn() }; - const temporalFileByPathFinderMock = { run: vi.fn() }; - const temporalFileUploaderMock = { run: vi.fn() }; - const temporalFileDeleterMock = { run: vi.fn() }; - const relativePathToAbsoluteConverterMock = { run: vi.fn() }; - const temporalFileByteByByteComparatorMock = { run: vi.fn() }; - - const src = '/tmp/offline-file.txt'; - const dest = '/virtual/file.txt'; - - beforeEach(() => { - container = new ContainerMock(); - uploadOnRename = new UploadOnRename(container); - - container.set(FirstsFileSearcher, firstsFileSearcherMock); - container.set(TemporalFileByPathFinder, temporalFileByPathFinderMock); - container.set(TemporalFileUploader, temporalFileUploaderMock); - container.set(TemporalFileDeleter, temporalFileDeleterMock); - container.set(RelativePathToAbsoluteConverter, relativePathToAbsoluteConverterMock); - container.set(TemporalFileByteByByteComparator, temporalFileByteByByteComparatorMock); - }); - - it('should return no-op when file to override is not found', async () => { - firstsFileSearcherMock.run.mockResolvedValue(null); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('no-op'); - call(firstsFileSearcherMock.run).toMatchObject({ - path: dest, - status: FileStatuses.EXISTS, - }); - calls(temporalFileByPathFinderMock.run).toHaveLength(0); - }); - - it('should return no-op when temporal file is not found', async () => { - const virtualFile = FileMother.fromPartial({ path: dest }); - firstsFileSearcherMock.run.mockResolvedValue(virtualFile); - temporalFileByPathFinderMock.run.mockResolvedValue(null); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('no-op'); - call(firstsFileSearcherMock.run).toMatchObject({ - path: dest, - status: FileStatuses.EXISTS, - }); - call(temporalFileByPathFinderMock.run).toBe(src); - calls(temporalFileUploaderMock.run).toHaveLength(0); - }); - - it('should delete temporal file and return success when files do not differ', async () => { - const contentsId = BucketEntryIdMother.primitive(); - const virtualFile = FileMother.fromPartial({ - path: dest, - size: 1024, - contentsId, - }); - const temporalFile = TemporalFile.create(new TemporalFilePath(src), new TemporalFileSize(1024)); - - firstsFileSearcherMock.run.mockResolvedValue(virtualFile); - temporalFileByPathFinderMock.run.mockResolvedValue(temporalFile); - relativePathToAbsoluteConverterMock.run.mockReturnValue('/absolute/path/to/file'); - temporalFileByteByByteComparatorMock.run.mockResolvedValue(true); - temporalFileDeleterMock.run.mockResolvedValue(undefined); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('success'); - call(relativePathToAbsoluteConverterMock.run).toBe(contentsId); - calls(temporalFileByteByByteComparatorMock.run).toHaveLength(1); - call(temporalFileDeleterMock.run).toBe(src); - calls(temporalFileUploaderMock.run).toHaveLength(0); - }); - - it('should upload and delete temporal file when files differ in size', async () => { - const contentsId = BucketEntryIdMother.primitive(); - const virtualFile = FileMother.fromPartial({ - path: dest, - size: 1024, - contentsId, - }); - const temporalFile = TemporalFile.create(new TemporalFilePath(src), new TemporalFileSize(2048)); - - firstsFileSearcherMock.run.mockResolvedValue(virtualFile); - temporalFileByPathFinderMock.run.mockResolvedValue(temporalFile); - temporalFileUploaderMock.run.mockResolvedValue(undefined); - temporalFileDeleterMock.run.mockResolvedValue(undefined); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('success'); - call(temporalFileUploaderMock.run).toMatchObject([temporalFile, { contentsId }]); - call(temporalFileDeleterMock.run).toBe(src); - }); - - it('should upload and delete temporal file when files differ in content', async () => { - const contentsId = BucketEntryIdMother.primitive(); - const virtualFile = FileMother.fromPartial({ - path: dest, - size: 1024, - contentsId, - }); - const temporalFile = TemporalFile.create(new TemporalFilePath(src), new TemporalFileSize(1024)); - - firstsFileSearcherMock.run.mockResolvedValue(virtualFile); - temporalFileByPathFinderMock.run.mockResolvedValue(temporalFile); - relativePathToAbsoluteConverterMock.run.mockReturnValue('/absolute/path/to/file'); - temporalFileByteByByteComparatorMock.run.mockResolvedValue(false); - temporalFileUploaderMock.run.mockResolvedValue(undefined); - temporalFileDeleterMock.run.mockResolvedValue(undefined); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('success'); - call(relativePathToAbsoluteConverterMock.run).toBe(contentsId); - calls(temporalFileByteByByteComparatorMock.run).toHaveLength(1); - call(temporalFileUploaderMock.run).toMatchObject([temporalFile, { contentsId }]); - call(temporalFileDeleterMock.run).toBe(src); - }); - - it('should handle comparison errors and proceed with upload', async () => { - const contentsId = BucketEntryIdMother.primitive(); - const virtualFile = FileMother.fromPartial({ - path: dest, - size: 1024, - contentsId, - }); - const temporalFile = TemporalFile.create(new TemporalFilePath(src), new TemporalFileSize(1024)); - - firstsFileSearcherMock.run.mockResolvedValue(virtualFile); - temporalFileByPathFinderMock.run.mockResolvedValue(temporalFile); - relativePathToAbsoluteConverterMock.run.mockReturnValue('/absolute/path/to/file'); - temporalFileByteByByteComparatorMock.run.mockRejectedValue(new Error('Comparison failed')); - temporalFileUploaderMock.run.mockResolvedValue(undefined); - temporalFileDeleterMock.run.mockResolvedValue(undefined); - - const result = await uploadOnRename.run(src, dest); - - expect(result.isRight()).toBe(true); - expect(result.getRight()).toBe('success'); - calls(temporalFileByteByByteComparatorMock.run).toHaveLength(1); - calls(temporalFileUploaderMock.run).toHaveLength(0); - call(temporalFileDeleterMock.run).toBe(src); - }); -}); diff --git a/src/apps/drive/fuse/callbacks/UploadOnRename.ts b/src/apps/drive/fuse/callbacks/UploadOnRename.ts deleted file mode 100644 index 820f291310..0000000000 --- a/src/apps/drive/fuse/callbacks/UploadOnRename.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { FileStatuses } from '../../../../context/virtual-drive/files/domain/FileStatus'; -import { Either, right } from '../../../../context/shared/domain/Either'; -import { FuseError } from './FuseErrors'; -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { File } from '../../../../context/virtual-drive/files/domain/File'; -import { Container } from 'diod'; -import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/search/FirstsFileSearcher'; -import { RelativePathToAbsoluteConverter } from '../../../../context/virtual-drive/shared/application/RelativePathToAbsoluteConverter'; -import { TemporalFileUploader } from '../../../../context/storage/TemporalFiles/application/upload/TemporalFileUploader'; -import { TemporalFileByPathFinder } from '../../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -import { TemporalFile } from '../../../../context/storage/TemporalFiles/domain/TemporalFile'; -import { TemporalFileByteByByteComparator } from '../../../../context/storage/TemporalFiles/application/comparation/TemporalFileByteByByteComparator'; -import { TemporalFilePath } from '../../../../context/storage/TemporalFiles/domain/TemporalFilePath'; -import { TemporalFileDeleter } from '../../../../context/storage/TemporalFiles/application/deletion/TemporalFileDeleter'; - -type Result = 'no-op' | 'success'; - -export class UploadOnRename { - private static readonly NO_OP: Result = 'no-op'; - private static readonly SUCCESS: Result = 'success'; - constructor(private readonly container: Container) {} - - private async differs(virtual: File, document: TemporalFile): Promise { - if (virtual.size !== document.size.value) { - return true; - } - - try { - const filePath = this.container.get(RelativePathToAbsoluteConverter).run(virtual.contentsId); - - const areEqual = await this.container - .get(TemporalFileByteByByteComparator) - .run(new TemporalFilePath(filePath), document.path); - - logger.debug({ msg: `Contents of <${virtual.path}> did not change` }); - - return !areEqual; - } catch (err) { - logger.error({ msg: 'Error comparing file contents', error: err }); - } - - return false; - } - - async run(src: string, dest: string): Promise> { - const fileToOverride = await this.container.get(FirstsFileSearcher).run({ - path: dest, - status: FileStatuses.EXISTS, - }); - - if (!fileToOverride) { - logger.debug({ msg: '[UPLOAD ON RENAME] file to override not found', dest }); - return right(UploadOnRename.NO_OP); - } - - const document = await this.container.get(TemporalFileByPathFinder).run(src); - - if (!document) { - logger.debug({ msg: '[UPLOAD ON RENAME] offline file not found', src }); - return right(UploadOnRename.NO_OP); - } - - const differs = await this.differs(fileToOverride, document); - - if (!differs) { - await this.container.get(TemporalFileDeleter).run(src); - return right(UploadOnRename.SUCCESS); - } - - await this.container.get(TemporalFileUploader).run(document, { - contentsId: fileToOverride.contentsId, - name: fileToOverride.name, - extension: fileToOverride.type, - }); - - await this.container.get(TemporalFileDeleter).run(src); - return right(UploadOnRename.SUCCESS); - } -} diff --git a/src/apps/drive/fuse/callbacks/WriteCallback.ts b/src/apps/drive/fuse/callbacks/WriteCallback.ts deleted file mode 100644 index d59f4b4939..0000000000 --- a/src/apps/drive/fuse/callbacks/WriteCallback.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Container } from 'diod'; -import { TemporalFileWriter } from '../../../../context/storage/TemporalFiles/application/write/TemporalFileWriter'; - -export class WriteCallback { - constructor(private readonly container: Container) {} - - async execute(path: string, _fd: number, buffer: Buffer, len: number, pos: number, cb: (a: number) => void) { - await this.container.get(TemporalFileWriter).run(path, buffer, len, pos); - - return cb(len); - } -} diff --git a/src/apps/drive/index.ts b/src/apps/drive/index.ts deleted file mode 100644 index 6ac0f286c1..0000000000 --- a/src/apps/drive/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -// import { getRootVirtualDrive } from '../main/virtual-root-folder/service'; -// import { broadcastToWindows } from '../main/windows'; -// import { DependencyInjectionUserProvider } from '../shared/dependency-injection/DependencyInjectionUserProvider'; -// import { VirtualDrive } from './virtual-drive/VirtualDrive'; -// import { DriveDependencyContainerFactory } from './dependency-injection/DriveDependencyContainerFactory'; -// import { FuseApp } from './fuse/FuseApp'; -// import { HydrationApi } from './hydration-api/HydrationApi'; -// import { logger } from '@internxt/drive-desktop-core/build/backend'; -// import { -// startFuseDaemonServer, -// stopFuseDaemonServer, -// startDaemon, -// stopDaemon, -// } from '../../backend/features/virtual-drive'; - -// let fuseApp: FuseApp; -// let hydrationApi: HydrationApi; - -// export async function startVirtualDrive() { -// const localRoot = getRootVirtualDrive(); - -// const container = await DriveDependencyContainerFactory.build(); -// const user = DependencyInjectionUserProvider.get(); -// const virtualDrive = new VirtualDrive(container); -// hydrationApi = new HydrationApi(container); -// fuseApp = new FuseApp(virtualDrive, container, localRoot, user.root_folder_id, user.rootFolderId); -// fuseApp.on('mounted', () => broadcastToWindows('virtual-drive-status-change', 'MOUNTED')); -// fuseApp.on('mount-error', () => broadcastToWindows('virtual-drive-status-change', 'ERROR')); -// await hydrationApi.start({ debug: false, timeElapsed: false }); -// await fuseApp.start(); - -// await startFuseDaemonServer(container); -// await startDaemon(localRoot); - -// broadcastToWindows('virtual-drive-status-change', 'MOUNTED'); -// logger.debug({ msg: '[FUSE DAEMON] virtual drive mounted and ready' }); -// } - -// export async function stopAndClearFuseApp() { -// await stopHydrationApi(); -// await stopDaemon(); -// await stopFuseDaemonServer(); -// } - -// export async function updateFuseApp() { -// await fuseApp.update(); -// } - -// export function getFuseDriveState() { -// if (!fuseApp) { -// return 'UNMOUNTED'; -// } -// return fuseApp.getStatus(); -// return 'MOUNTED'; -// } - -// export async function stopHydrationApi() { -// if (!hydrationApi) { -// logger.debug({ msg: 'HydrationApi not initialized, skipping stop.' }); -// return; -// } - -// try { -// logger.debug({ msg: 'Stopping HydrationApi...' }); -// await hydrationApi.stop(); -// } catch (error) { -// logger.error({ msg: 'Error stopping HydrationApi:', error }); -// } -// } diff --git a/src/apps/drive/virtual-drive/VirtualDrive.test.ts b/src/apps/drive/virtual-drive/VirtualDrive.test.ts deleted file mode 100644 index 850d83256f..0000000000 --- a/src/apps/drive/virtual-drive/VirtualDrive.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { VirtualDrive } from './VirtualDrive'; - -import { ContainerMock } from '../__mocks__/ContainerMock'; -import { StorageFileIsAvailableOffline } from '../../../context/storage/StorageFiles/application/offline/StorageFileIsAvailableOffline'; -import { AllFilesInFolderAreAvailableOffline } from '../../../context/storage/StorageFolders/application/offline/AllFilesInFolderAreAvailableOffline'; -import { TemporalFileByPathFinder } from '../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; - -describe('VirtualDrive', () => { - let container: ContainerMock; - let virtualDrive: VirtualDrive; - let path = '/path/to/file.txt'; - - beforeEach(() => { - container = new ContainerMock(); - virtualDrive = new VirtualDrive(container); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('isLocallyAvailable', () => { - it('should verify if a file is locally available', async () => { - const storageFileIsAvailableOfflineMock = { run: vi.fn().mockResolvedValue(true) }; - container.set(StorageFileIsAvailableOffline, storageFileIsAvailableOfflineMock); - - const result = await virtualDrive.isLocallyAvailable(path); - - expect(container.get).toHaveBeenCalledWith(StorageFileIsAvailableOffline); - expect(storageFileIsAvailableOfflineMock.run).toHaveBeenCalledWith(path); - expect(result).toBe(true); - }); - - it('Should verify if a folder is available locally', async () => { - path = '/path/to/folder'; - const allFilesInFolderAreAvailableOfflineMock = { run: vi.fn().mockResolvedValue(true) }; - container.set(AllFilesInFolderAreAvailableOffline, allFilesInFolderAreAvailableOfflineMock); - - const result = await virtualDrive.isLocallyAvailable(path); - - expect(container.get).toHaveBeenCalledWith(AllFilesInFolderAreAvailableOffline); - expect(allFilesInFolderAreAvailableOfflineMock.run).toHaveBeenCalledWith(path); - expect(result).toBe(true); - }); - }); - - it('should verify if a temporal file exists', async () => { - const temporalFileMock = { run: vi.fn().mockResolvedValue(true) }; - container.set(TemporalFileByPathFinder, temporalFileMock); - - const result = await virtualDrive.temporalFileExists(path); - - expect(container.get).toHaveBeenCalledWith(TemporalFileByPathFinder); - expect(temporalFileMock.run).toHaveBeenCalledWith(path); - expect(result.getRight()).toEqual(true); - }); - - it('should return false if a temporal file doesnt exist', async () => { - const temporalFileMock = { run: vi.fn().mockResolvedValue(null) }; - container.set(TemporalFileByPathFinder, temporalFileMock); - - const result = await virtualDrive.temporalFileExists(path); - - expect(container.get).toHaveBeenCalledWith(TemporalFileByPathFinder); - expect(temporalFileMock.run).toHaveBeenCalledWith(path); - expect(result.getRight()).toEqual(false); - }); -}); diff --git a/src/apps/drive/virtual-drive/VirtualDrive.ts b/src/apps/drive/virtual-drive/VirtualDrive.ts deleted file mode 100644 index 6dc20ffb6f..0000000000 --- a/src/apps/drive/virtual-drive/VirtualDrive.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Container } from 'diod'; -import { extname } from 'path'; -import { Either, right } from '../../../context/shared/domain/Either'; -import { AllFilesInFolderAreAvailableOffline } from '../../../context/storage/StorageFolders/application/offline/AllFilesInFolderAreAvailableOffline'; -import { StorageFileIsAvailableOffline } from '../../../context/storage/StorageFiles/application/offline/StorageFileIsAvailableOffline'; -import { TemporalFileByPathFinder } from '../../../context/storage/TemporalFiles/application/find/TemporalFileByPathFinder'; -import { VirtualDriveError } from '../errors/VirtualDriveError'; - -export class VirtualDrive { - constructor(private readonly container: Container) {} - - private async isFileLocallyAvailable(path: string): Promise { - try { - return await this.container.get(StorageFileIsAvailableOffline).run(path); - } catch (error) { - // If the path is from a folder it will not find it as a file - return false; - } - } - - private async isFolderLocallyAvailable(path: string): Promise { - try { - return await this.container.get(AllFilesInFolderAreAvailableOffline).run(path); - } catch (error) { - // If the path is from a file it will not find it as a folder - return false; - } - } - - private seemsToBeFromAFile(path: string): boolean { - return extname(path) !== ''; - } - - async isLocallyAvailable(path: string): Promise { - if (this.seemsToBeFromAFile(path)) { - const fileIsAvaliable = await this.isFileLocallyAvailable(path); - - if (fileIsAvaliable) return true; - - return this.isFolderLocallyAvailable(path); - } - - const allFilesInFolderAreAvaliable = await this.isFolderLocallyAvailable(path); - if (allFilesInFolderAreAvaliable) return true; - - return await this.isFileLocallyAvailable(path); - } - async temporalFileExists(path: string): Promise> { - const file = await this.container.get(TemporalFileByPathFinder).run(path); - - if (!file) { - return right(false); - } - - return right(true); - } -} diff --git a/src/backend/features/fuse/on-open/open-flags-tracker.test.ts b/src/backend/features/fuse/on-open/open-flags-tracker.test.ts deleted file mode 100644 index 805f2b4a93..0000000000 --- a/src/backend/features/fuse/on-open/open-flags-tracker.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - isSystemOpen, - isUserOpen, - onRelease, - shouldDownload, - SYSTEM_OPEN_FLAG, - trackOpen, - USER_OPEN_FLAG, -} from './open-flags-tracker'; - -describe('open-flags-tracker', () => { - describe('isSystemOpen', () => { - it('should return true when given the system open flag', () => { - const result = isSystemOpen(SYSTEM_OPEN_FLAG); - - expect(result).toBe(true); - }); - - it('should return false when given a non-system flag', () => { - const result = isSystemOpen(USER_OPEN_FLAG); - - expect(result).toBe(false); - }); - }); - - describe('isUserOpen', () => { - it('should return true when given the user open flag', () => { - const result = isUserOpen(USER_OPEN_FLAG); - - expect(result).toBe(true); - }); - - it('should return false when given a non-user flag', () => { - const result = isUserOpen(SYSTEM_OPEN_FLAG); - - expect(result).toBe(false); - }); - }); - - describe('trackOpen', () => { - afterEach(() => { - onRelease('/test.mp4'); - }); - - it('should store the flag so shouldDownload can use it', () => { - trackOpen('/test.mp4', SYSTEM_OPEN_FLAG); - - const result = shouldDownload('/test.mp4'); - - expect(result).toBe(false); - - trackOpen('/test.png', SYSTEM_OPEN_FLAG); - - const result2 = shouldDownload('/test.png'); - expect(result2).toBe(true); - }); - }); - - describe('shouldDownload', () => { - afterEach(() => { - onRelease('/test.mp4'); - onRelease('/test.png'); - }); - - it('should return true when no flag found for a given path', () => { - const result = shouldDownload('/test.mp4'); - - expect(result).toBe(true); - }); - - it('should return true when given a path extension is in whitelist', () => { - trackOpen('/test.png', SYSTEM_OPEN_FLAG); - - const result = shouldDownload('/test.png'); - - expect(result).toBe(true); - }); - - it('should return false when given a flag is systemOpen', () => { - trackOpen('/test.mp4', SYSTEM_OPEN_FLAG); - - const result = shouldDownload('/test.mp4'); - - expect(result).toBe(false); - }); - - it('should return true when given a flag is not systemOpen', () => { - trackOpen('/test.mp4', 34816); - - const result = shouldDownload('/test.mp4'); - - expect(result).toBe(true); - }); - - it('should return true when given a path extension is not in whitelist and is not systemOpen', () => { - trackOpen('/test.mp4', USER_OPEN_FLAG); - - const result = shouldDownload('/test.mp4'); - - expect(result).toBe(true); - }); - }); - - describe('onRelease', () => { - it('should clear the flag so shouldDownload returns true', () => { - trackOpen('/test.mp4', SYSTEM_OPEN_FLAG); - - onRelease('/test.mp4'); - - const result = shouldDownload('/test.mp4'); - expect(result).toBe(true); - }); - }); -}); diff --git a/src/backend/features/fuse/on-open/open-flags-tracker.ts b/src/backend/features/fuse/on-open/open-flags-tracker.ts deleted file mode 100644 index 8930f1950a..0000000000 --- a/src/backend/features/fuse/on-open/open-flags-tracker.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { logger } from '@internxt/drive-desktop-core/build/backend'; - -/** - * v.2.5.1 - * Esteban Galvis Triana - * Flag patterns observed: - * - 294912 (0x48000): System opening file (thumbnails, directory browsing) - * - 32768 (0x8000): User opening file (actual file access) - */ - -export const SYSTEM_OPEN_FLAG = 294912; -export const USER_OPEN_FLAG = 32768; - -/** - * v.2.5.1 - * Esteban Galvis Triana - * Files in set trigger system verification to select the default application, - * causing a system-level open flag that crashes the file open. - * Added as an exception since user-initiated opens cannot be reliably - * distinguished from system checks. - */ -const DOWNLOAD_WHITELIST = new Set(['.png', '.json']); - -const fileFlags = new Map(); - -export function isSystemOpen(flag: number): boolean { - return flag === SYSTEM_OPEN_FLAG; -} - -export function isUserOpen(flag: number): boolean { - return flag === USER_OPEN_FLAG; -} -export function trackOpen(path: string, flag: number): void { - fileFlags.set(path, flag); - logger.debug({ - msg: '[OpenFlagsTracker] File opened:', - path, - flag, - isSystemOpen: isSystemOpen(flag), - isUserOpen: isUserOpen(flag), - }); -} - -export function shouldDownload(path: string): boolean { - const flag = fileFlags.get(path); - - if (!flag) { - logger.debug({ msg: '[OpenFlagsTracker] No flag found, allowing download:', path }); - return true; - } - - const extension = path.substring(path.lastIndexOf('.')).toLowerCase(); - if (DOWNLOAD_WHITELIST.has(extension)) { - logger.debug({ - msg: '[OpenFlagsTracker] Extension whitelisted, allowing download:', - path, - extension, - flag, - }); - return true; - } - - if (isSystemOpen(flag)) { - logger.debug({ - msg: '[OpenFlagsTracker] Download blocked - system open detected:', - path, - flag, - }); - return false; - } - - return true; -} - -export function onRelease(path: string): void { - fileFlags.delete(path); -} diff --git a/src/backend/features/fuse/on-release/handle-release-callback.test.ts b/src/backend/features/fuse/on-release/handle-release-callback.test.ts deleted file mode 100644 index e7e9f6c3db..0000000000 --- a/src/backend/features/fuse/on-release/handle-release-callback.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, vi } from 'vitest'; -import { call, calls } from '../../../../../tests/vitest/utils.helper'; -import { handleReleaseCallback } from './handle-release-callback'; -import { TemporalFile } from '../../../../context/storage/TemporalFiles/domain/TemporalFile'; - -vi.mock(import('@internxt/drive-desktop-core/build/backend')); - -function createTemporalFile(path: string): TemporalFile { - return TemporalFile.from({ - path, - size: 100, - createdAt: new Date(), - modifiedAt: new Date(), - }); -} - -function createAuxiliaryFile(path: string): TemporalFile { - return TemporalFile.from({ - path, - size: 0, - createdAt: new Date(), - modifiedAt: new Date(), - }); -} - -describe('handle-release-callback', () => { - const findTemporalFile = vi.fn<(path: string) => Promise>(); - const uploadTemporalFile = vi.fn<(temporalFile: TemporalFile) => Promise>(); - const deleteTemporalFile = vi.fn<(path: string) => Promise>(); - - it('should return right when no temporal file is found', async () => { - findTemporalFile.mockResolvedValue(undefined); - - const result = await handleReleaseCallback({ - path: '/Documents/file.pdf', - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, - }); - - expect(result.isRight()).toBe(true); - calls(findTemporalFile).toHaveLength(1); - calls(uploadTemporalFile).toHaveLength(0); - }); - - it('should skip upload for auxiliary files', async () => { - findTemporalFile.mockResolvedValue(createAuxiliaryFile('/Documents/.~lock.file.odt#')); - - const result = await handleReleaseCallback({ - path: '/Documents/.~lock.file.odt#', - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, - }); - - expect(result.isRight()).toBe(true); - calls(findTemporalFile).toHaveLength(1); - calls(uploadTemporalFile).toHaveLength(0); - }); - - it('should upload temporal file and returns right on success', async () => { - const temporalFile = createTemporalFile('/Documents/report.pdf'); - findTemporalFile.mockResolvedValue(temporalFile); - uploadTemporalFile.mockResolvedValue('contents-id-123'); - - const result = await handleReleaseCallback({ - path: '/Documents/report.pdf', - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, - }); - - expect(result.isRight()).toBe(true); - calls(findTemporalFile).toHaveLength(1); - call(uploadTemporalFile).toBe(temporalFile); - }); - - it('should delete temporal file and return left when upload fails', async () => { - findTemporalFile.mockResolvedValue(createTemporalFile('/Documents/report.pdf')); - uploadTemporalFile.mockRejectedValue(new Error('Network error')); - - const result = await handleReleaseCallback({ - path: '/Documents/report.pdf', - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, - }); - - expect(result.isLeft()).toBe(true); - call(deleteTemporalFile).toBe('/Documents/report.pdf'); - }); - - it('should return left when findTemporalFile throws an error', async () => { - findTemporalFile.mockRejectedValue(new Error('DB error')); - - const result = await handleReleaseCallback({ - path: '/Documents/report.pdf', - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, - }); - - expect(result.isLeft()).toBe(true); - calls(uploadTemporalFile).toHaveLength(0); - calls(deleteTemporalFile).toHaveLength(0); - }); -}); diff --git a/src/backend/features/fuse/on-release/handle-release-callback.ts b/src/backend/features/fuse/on-release/handle-release-callback.ts deleted file mode 100644 index 935bb0404c..0000000000 --- a/src/backend/features/fuse/on-release/handle-release-callback.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { logger } from '@internxt/drive-desktop-core/build/backend'; -import { left, right, type Either } from '../../../../context/shared/domain/Either'; -import { type TemporalFile } from '../../../../context/storage/TemporalFiles/domain/TemporalFile'; -import { FuseError, FuseIOError } from '../../../../apps/drive/fuse/callbacks/FuseErrors'; - -type Props = { - path: string; - findTemporalFile: (path: string) => Promise; - uploadTemporalFile: (temporalFile: TemporalFile) => Promise; - deleteTemporalFile: (path: string) => Promise; -}; -export async function handleReleaseCallback({ - path, - findTemporalFile, - uploadTemporalFile, - deleteTemporalFile, -}: Props): Promise> { - try { - const temporalFile = await findTemporalFile(path); - - if (!temporalFile) { - logger.debug({ msg: '[Release] No temporal file found, nothing to upload', path }); - return right(undefined); - } - - if (temporalFile.isAuxiliary()) { - logger.debug({ msg: '[Release] Auxiliary file detected, skipping upload', path }); - return right(undefined); - } - - try { - await uploadTemporalFile(temporalFile); - logger.debug({ msg: '[Release] Temporal file uploaded', path }); - return right(undefined); - } catch (uploadError) { - logger.error({ msg: '[Release] Upload failed, deleting temporal file', error: uploadError, path }); - await deleteTemporalFile(path); - return left(new FuseIOError('Upload failed due to insufficient storage or network issues.')); - } - } catch (err: unknown) { - logger.error({ msg: '[Release] Unexpected error', error: err, path }); - return left(new FuseIOError('An unexpected error occurred during file release.')); - } -} From 029e4c08a791509339b2d423d89c7327a3e697bb Mon Sep 17 00:00:00 2001 From: AlexisMora Date: Tue, 2 Jun 2026 11:21:55 +0200 Subject: [PATCH 2/3] chore: move FuseDriveStatus to virtual-drive module --- src/apps/main/interface.d.ts | 4 ++-- src/apps/main/preload.d.ts | 4 ++-- src/apps/renderer/hooks/useVirtualDriveStatus.tsx | 2 +- .../features/virtual-drive}/FuseDriveStatus.ts | 0 src/backend/features/virtual-drive/index.ts | 1 + src/backend/features/virtual-drive/services/daemon.service.ts | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) rename src/{apps/drive/fuse => backend/features/virtual-drive}/FuseDriveStatus.ts (100%) diff --git a/src/apps/main/interface.d.ts b/src/apps/main/interface.d.ts index 6b62365cd0..26a8d5cc9b 100644 --- a/src/apps/main/interface.d.ts +++ b/src/apps/main/interface.d.ts @@ -159,9 +159,9 @@ export interface IElectronAPI { onUpdateAvailable(callback: (info: { version: string }) => void): () => void; getRemoteSyncStatus(): Promise; onRemoteSyncStatusChange(callback: (status: import('./remote-sync/helpers').RemoteSyncStatus) => void): () => void; - getVirtualDriveStatus(): Promise; + getVirtualDriveStatus(): Promise; onVirtualDriveStatusChange( - callback: (event: { status: import('../drive/fuse/FuseDriveStatus').FuseDriveStatus }) => void, + callback: (event: { status: import('../../backend/features/virtual-drive').FuseDriveStatus }) => void, ): () => void; } diff --git a/src/apps/main/preload.d.ts b/src/apps/main/preload.d.ts index aca484dcf5..2a57746063 100644 --- a/src/apps/main/preload.d.ts +++ b/src/apps/main/preload.d.ts @@ -151,9 +151,9 @@ declare interface Window { onRemoteSyncStatusChange(callback: (status: import('./remote-sync/helpers').RemoteSyncStatus) => void): () => void; getRemoteSyncStatus(): Promise; - getVirtualDriveStatus(): Promise; + getVirtualDriveStatus(): Promise; onVirtualDriveStatusChange( - callback: (event: { status: import('../drive/fuse/FuseDriveStatus').FuseDriveStatus }) => void, + callback: (event: { status: import('../../backend/features/virtual-drive').FuseDriveStatus }) => void, ): () => void; startRemoteSync: () => Promise; openUrl: (url: string) => Promise; diff --git a/src/apps/renderer/hooks/useVirtualDriveStatus.tsx b/src/apps/renderer/hooks/useVirtualDriveStatus.tsx index 47f266bc8e..35043aaec2 100644 --- a/src/apps/renderer/hooks/useVirtualDriveStatus.tsx +++ b/src/apps/renderer/hooks/useVirtualDriveStatus.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { FuseDriveStatus } from '../../drive/fuse/FuseDriveStatus'; +import { FuseDriveStatus } from '../../../backend/features/virtual-drive'; export default function useVirtualDriveStatus() { const [virtualDriveStatus, setVirtualDriveStatus] = useState(); diff --git a/src/apps/drive/fuse/FuseDriveStatus.ts b/src/backend/features/virtual-drive/FuseDriveStatus.ts similarity index 100% rename from src/apps/drive/fuse/FuseDriveStatus.ts rename to src/backend/features/virtual-drive/FuseDriveStatus.ts diff --git a/src/backend/features/virtual-drive/index.ts b/src/backend/features/virtual-drive/index.ts index e6b0f884aa..7cff0b0975 100644 --- a/src/backend/features/virtual-drive/index.ts +++ b/src/backend/features/virtual-drive/index.ts @@ -1,2 +1,3 @@ export { startFuseDaemonServer, stopFuseDaemonServer } from './services/server.service'; export { startDaemon, stopDaemon } from './services/daemon.service'; +export { FuseDriveStatus } from './FuseDriveStatus'; diff --git a/src/backend/features/virtual-drive/services/daemon.service.ts b/src/backend/features/virtual-drive/services/daemon.service.ts index 501b5d0e2d..d551f9c076 100644 --- a/src/backend/features/virtual-drive/services/daemon.service.ts +++ b/src/backend/features/virtual-drive/services/daemon.service.ts @@ -1,7 +1,7 @@ import { spawn, ChildProcess } from 'node:child_process'; import { logger } from '@internxt/drive-desktop-core/build/backend'; import { PATHS } from '../../../../core/electron/paths'; -import { FuseDriveStatus } from '../../../../apps/drive/fuse/FuseDriveStatus'; +import { FuseDriveStatus } from '../FuseDriveStatus'; import { broadcastToWindows } from '../../../../apps/main/windows'; let resolveReady: () => void; From 4311912b30181c3bd24be2e51bf376bab1cfa164 Mon Sep 17 00:00:00 2001 From: AlexisMora Date: Tue, 2 Jun 2026 11:26:57 +0200 Subject: [PATCH 3/3] chore: delete unused PathsToIgnore files --- src/apps/drive/fuse/callbacks/PathsToIgnore.ts | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/apps/drive/fuse/callbacks/PathsToIgnore.ts diff --git a/src/apps/drive/fuse/callbacks/PathsToIgnore.ts b/src/apps/drive/fuse/callbacks/PathsToIgnore.ts deleted file mode 100644 index b184cbda1a..0000000000 --- a/src/apps/drive/fuse/callbacks/PathsToIgnore.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PathsToIgnore: Array = [ - /^\/BDMV$/, - /^\/autorun\.inf$/, - /^\/.Trash$/, - /^\/.Trash-1001\/files$/, - /\/.xdg-volume-info$/, - /\/.hidden$/, -];