diff --git a/package-lock.json b/package-lock.json index 59f5a0075..931ec692a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "internxt-drive", - "version": "2.6.5", + "version": "2.6.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "internxt-drive", - "version": "2.6.5", + "version": "2.6.6", "license": "MIT", "dependencies": { "@headlessui/react": "^1.4.2", diff --git a/package.json b/package.json index 7f10f310a..d982f18f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "internxt-drive", - "version": "2.6.5", + "version": "2.6.6", "author": "Internxt ", "description": "Internxt client UI", "main": "./dist/main/main.js", diff --git a/src/apps/backups/folders/create-folders.ts b/src/apps/backups/folders/create-folders.ts index fdbbf1db3..a31cdfd14 100644 --- a/src/apps/backups/folders/create-folders.ts +++ b/src/apps/backups/folders/create-folders.ts @@ -16,7 +16,7 @@ export async function createFolders({ ctx, added, tree }: TProps) { for (const path of sortedAdded) { try { - await scheduleRequest({ ctx, fn: () => createFolder(ctx, path, tree) }); + await scheduleRequest({ ctx, path, fn: () => createFolder(ctx, path, tree) }); } catch (error) { ctx.logger.error({ msg: 'Error creating folder', path, error }); } diff --git a/src/apps/backups/folders/delete-folders.ts b/src/apps/backups/folders/delete-folders.ts index 2cc379483..d6baf36d5 100644 --- a/src/apps/backups/folders/delete-folders.ts +++ b/src/apps/backups/folders/delete-folders.ts @@ -14,7 +14,7 @@ export async function deleteFolders({ ctx, deleted }: TProps) { const path = folder.absolutePath; try { - await scheduleRequest({ ctx, fn: () => deleteFolderByUuid({ ctx, uuid: folder.uuid, path }) }); + await scheduleRequest({ ctx, path, fn: () => deleteFolderByUuid({ ctx, uuid: folder.uuid, path }) }); } catch (error) { ctx.logger.error({ msg: 'Error deleting folder', path, error }); } diff --git a/src/apps/backups/process-files/create-files.ts b/src/apps/backups/process-files/create-files.ts index e614778a5..acf8c296f 100644 --- a/src/apps/backups/process-files/create-files.ts +++ b/src/apps/backups/process-files/create-files.ts @@ -18,7 +18,7 @@ export async function createFiles({ ctx, remoteTree, added }: Props) { const path = local.path; try { - await scheduleRequest({ ctx, fn: () => createFile(ctx, path, remoteTree) }); + await scheduleRequest({ ctx, path, fn: () => createFile(ctx, path, remoteTree) }); } catch (error) { ctx.logger.error({ msg: 'Error creating file', path, error }); } diff --git a/src/apps/backups/process-files/delete-files.ts b/src/apps/backups/process-files/delete-files.ts index 2d4e7e593..c0104f1e7 100644 --- a/src/apps/backups/process-files/delete-files.ts +++ b/src/apps/backups/process-files/delete-files.ts @@ -14,7 +14,7 @@ export async function deleteFiles({ ctx, deleted }: TProps) { const path = file.absolutePath; try { - await scheduleRequest({ ctx, fn: () => deleteFileByUuid({ ctx, uuid: file.uuid, path }) }); + await scheduleRequest({ ctx, path, fn: () => deleteFileByUuid({ ctx, uuid: file.uuid, path }) }); } catch (error) { ctx.logger.error({ msg: 'Error deleting folder', path, error }); } diff --git a/src/apps/backups/process-files/replace-files.ts b/src/apps/backups/process-files/replace-files.ts index 838b99084..0d019658c 100644 --- a/src/apps/backups/process-files/replace-files.ts +++ b/src/apps/backups/process-files/replace-files.ts @@ -14,7 +14,7 @@ export async function replaceFiles({ ctx, modified }: Props) { const path = local.path; try { - await scheduleRequest({ ctx, fn: () => Sync.Actions.replaceFile({ ctx, path, uuid: remote.uuid }) }); + await scheduleRequest({ ctx, path, fn: () => Sync.Actions.replaceFile({ ctx, path, uuid: remote.uuid }) }); } catch (error) { ctx.logger.error({ msg: 'Error replacing file', path, error }); } diff --git a/src/apps/backups/schedule-request.ts b/src/apps/backups/schedule-request.ts index b9e7c801b..e3e564f44 100644 --- a/src/apps/backups/schedule-request.ts +++ b/src/apps/backups/schedule-request.ts @@ -1,13 +1,15 @@ import { isBottleneckStop } from '@/infra/drive-server-wip/in/helpers/error-helpers'; import { tracker } from '../main/background-processes/backups/BackupsProcessTracker/BackupsProcessTracker'; import { BackupsContext } from './BackupInfo'; +import { AbsolutePath } from '@internxt/drive-desktop-core/build/backend'; type Props = { ctx: BackupsContext; + path: AbsolutePath; fn: () => Promise; }; -export async function scheduleRequest({ ctx, fn }: Props) { +export async function scheduleRequest({ ctx, path, fn }: Props) { try { await ctx.backupsBottleneck.schedule(() => fn()); } catch (error) { @@ -15,6 +17,6 @@ export async function scheduleRequest({ ctx, fn }: Props) { throw error; } finally { - tracker.currentProcessed(); + tracker.currentProcessed(path); } } diff --git a/src/apps/main/background-processes/backups/BackupsProcessTracker/BackupsProcessTracker.ts b/src/apps/main/background-processes/backups/BackupsProcessTracker/BackupsProcessTracker.ts index 36e07677a..78b469508 100644 --- a/src/apps/main/background-processes/backups/BackupsProcessTracker/BackupsProcessTracker.ts +++ b/src/apps/main/background-processes/backups/BackupsProcessTracker/BackupsProcessTracker.ts @@ -1,3 +1,4 @@ +import { AbsolutePath } from '@internxt/drive-desktop-core/build/backend'; import { BackupInfo } from '../../../../backups/BackupInfo'; import { broadcastToWindows } from '../../../windows'; import { BackupsStatus } from '../BackupsProcessStatus/BackupsStatus'; @@ -17,10 +18,16 @@ export class BackupsProcessTracker { private abortController: AbortController | undefined; - notify() { + notify(path?: AbsolutePath) { if (this.abortController && !this.abortController.signal.aborted) { - logger.debug({ tag: 'BACKUPS', msg: 'Progress', progress: this.progress() }); broadcastToWindows({ name: 'backup-progress', data: this.progress() }); + logger.debug({ + tag: 'BACKUPS', + msg: 'Progress', + ...(path && { path }), + total: this.current.total, + processed: this.current.processed, + }); } } @@ -43,9 +50,9 @@ export class BackupsProcessTracker { this.notify(); } - currentProcessed() { + currentProcessed(path: AbsolutePath) { this.current.processed++; - this.notify(); + this.notify(path); } setStatus(status: BackupsStatus) { diff --git a/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.test.ts b/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.test.ts new file mode 100644 index 000000000..3506e5a6d --- /dev/null +++ b/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.test.ts @@ -0,0 +1,72 @@ +import { call, calls, mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test'; +import { loadVirtualDrive } from './load-virtual-drive'; +import { VirtualDrive } from '@/node-win/virtual-drive'; +import { NodeWin } from '@/infra/node-win/node-win.module'; +import * as addSyncIssue from '../../issues'; +import { RegisterSyncRootError } from '@/infra/node-win/services/register-sync-root'; +import { loggerMock } from '@/tests/vitest/mocks.helper.test'; +import { Addon } from '@/node-win/addon-wrapper'; + +describe('load-virtual-drive', () => { + partialSpyOn(VirtualDrive, 'createSyncRootFolder'); + const NodeWinRegisterSyncRootMock = partialSpyOn(NodeWin, 'registerSyncRoot'); + const AddonRegisterSyncRootMock = partialSpyOn(Addon, 'registerSyncRoot'); + const getSyncRootFromPathMock = partialSpyOn(Addon, 'getSyncRootFromPath'); + const unregisterSyncRootMock = partialSpyOn(Addon, 'unregisterSyncRoot'); + const connectSyncRootMock = partialSpyOn(Addon, 'connectSyncRoot'); + const addSyncIssueMock = partialSpyOn(addSyncIssue, 'addSyncIssue'); + + const props = mockProps({ ctx: { providerId: 'syncRootId' } }); + + beforeEach(() => { + getSyncRootFromPathMock.mockResolvedValue({ id: 'oldSyncRootId' }); + connectSyncRootMock.mockReturnValue(1n); + }); + + it('should add sync issue if register sync root gives UNKNOWN error', async () => { + // Given + NodeWinRegisterSyncRootMock.mockResolvedValue(new RegisterSyncRootError('UNKNOWN')); + // When + const connectionkey = await loadVirtualDrive(props); + // Then + expect(connectionkey).toBeUndefined(); + call(addSyncIssueMock).toMatchObject({ error: 'CANNOT_REGISTER_VIRTUAL_DRIVE' }); + call(loggerMock.error).toMatchObject({ msg: 'Error loading virtual drive' }); + }); + + it('should add sync issue if there is no old sync root registered when ACCESS_DENIED', async () => { + // Given + NodeWinRegisterSyncRootMock.mockResolvedValue(new RegisterSyncRootError('ACCESS_DENIED')); + getSyncRootFromPathMock.mockRejectedValue(new Error()); + // When + const connectionkey = await loadVirtualDrive(props); + // Then + expect(connectionkey).toBeUndefined(); + call(addSyncIssueMock).toMatchObject({ error: 'CANNOT_REGISTER_VIRTUAL_DRIVE' }); + calls(loggerMock.error).toMatchObject([{ msg: 'Error getting sync root from path' }, { msg: 'Error loading virtual drive' }]); + }); + + it('should unregister and register if there is an old sync root registered when ACCESS_DENIED', async () => { + // Given + NodeWinRegisterSyncRootMock.mockResolvedValue(new RegisterSyncRootError('ACCESS_DENIED')); + // When + const connectionkey = await loadVirtualDrive(props); + // Then + expect(connectionkey).toBe(1n); + call(unregisterSyncRootMock).toStrictEqual({ providerId: 'oldSyncRootId' }); + call(AddonRegisterSyncRootMock).toMatchObject({ providerId: 'syncRootId' }); + calls(loggerMock.error).toHaveLength(0); + }); + + it('should register and connect if no error happens', async () => { + // Given + NodeWinRegisterSyncRootMock.mockResolvedValue(undefined); + // When + const connectionkey = await loadVirtualDrive(props); + // Then + expect(connectionkey).toBe(1n); + calls(unregisterSyncRootMock).toHaveLength(0); + calls(AddonRegisterSyncRootMock).toHaveLength(0); + calls(loggerMock.error).toHaveLength(0); + }); +}); diff --git a/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.ts b/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.ts new file mode 100644 index 000000000..880851b9f --- /dev/null +++ b/src/apps/main/background-processes/sync-engine/services/load-virtual-drive.ts @@ -0,0 +1,45 @@ +import { Addon } from '@/node-win/addon-wrapper'; +import { addSyncIssue } from '../../issues'; +import { VirtualDrive } from '@/node-win/virtual-drive'; +import { SyncContext } from '@/apps/sync-engine/config'; +import { NodeWin } from '@/infra/node-win/node-win.module'; + +type Props = { + ctx: SyncContext; +}; + +export async function loadVirtualDrive({ ctx }: Props) { + try { + await VirtualDrive.createSyncRootFolder({ rootPath: ctx.rootPath }); + + const error = await NodeWin.registerSyncRoot({ ctx }); + const info = await getSyncRootFromPath(ctx); + + if (error) { + if (error.code !== 'ACCESS_DENIED') throw error; + if (!info) throw error; + + await Addon.unregisterSyncRoot({ providerId: info.id }); + await Addon.registerSyncRoot({ rootPath: ctx.rootPath, providerId: ctx.providerId, providerName: ctx.providerName }); + } + + const connectionKey = Addon.connectSyncRoot({ ctx }); + ctx.logger.debug({ msg: 'Connection key', connectionKey }); + return connectionKey; + } catch (error) { + addSyncIssue({ error: 'CANNOT_REGISTER_VIRTUAL_DRIVE', name: ctx.rootPath }); + ctx.logger.error({ msg: 'Error loading virtual drive', error }); + return; + } +} + +async function getSyncRootFromPath(ctx: SyncContext) { + try { + const info = await Addon.getSyncRootFromPath({ path: ctx.rootPath }); + ctx.logger.debug({ msg: 'Sync root from path', info }); + return info; + } catch (error) { + ctx.logger.error({ msg: 'Error getting sync root from path', error }); + return; + } +} diff --git a/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.test.ts b/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.test.ts index d14ba6bc4..7b585c130 100644 --- a/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.test.ts +++ b/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.test.ts @@ -3,18 +3,14 @@ import { call, calls, mockProps, partialSpyOn } from 'tests/vitest/utils.helper. import { workers } from '@/apps/main/remote-sync/store'; import * as scheduleSync from './schedule-sync'; import { RecoverySyncModule } from '@/backend/features/sync/recovery-sync/recovery-sync.module'; -import { Addon } from '@/node-win/addon-wrapper'; -import * as addSyncIssue from '../../issues'; import * as refreshItemPlaceholders from '@/apps/sync-engine/refresh-item-placeholders'; -import { VirtualDrive } from '@/node-win/virtual-drive'; import * as initWatcher from '@/node-win/watcher/watcher'; import * as addPendingItems from '@/apps/sync-engine/in/add-pending-items'; +import * as loadVirtualDrive from './load-virtual-drive'; +import { loggerMock } from '@/tests/vitest/mocks.helper.test'; describe('spawn-sync-engine-worker', () => { - const createSyncRootFolderMock = partialSpyOn(VirtualDrive, 'createSyncRootFolder'); - const registerSyncRootMock = partialSpyOn(Addon, 'registerSyncRoot'); - const connectSyncRootMock = partialSpyOn(Addon, 'connectSyncRoot'); - const addSyncIssueMock = partialSpyOn(addSyncIssue, 'addSyncIssue'); + const loadVirtualDriveMock = partialSpyOn(loadVirtualDrive, 'loadVirtualDrive'); const scheduleSyncMock = partialSpyOn(scheduleSync, 'scheduleSync'); const recoverySyncMock = partialSpyOn(RecoverySyncModule, 'recoverySync'); const refreshItemPlaceholdersMock = partialSpyOn(refreshItemPlaceholders, 'refreshItemPlaceholders'); @@ -28,25 +24,32 @@ describe('spawn-sync-engine-worker', () => { workers.clear(); }); - it('should add issue if register sync root fails', async () => { + it('should catch errors', async () => { // Given - registerSyncRootMock.mockRejectedValue(new Error('message')); + loadVirtualDriveMock.mockRejectedValue(new Error()); // When await spawnSyncEngineWorker(props); // Then - call(addSyncIssueMock).toMatchObject({ error: 'CANNOT_REGISTER_VIRTUAL_DRIVE' }); + call(loggerMock.error).toMatchObject({ msg: 'Error loading sync engine worker' }); }); - it('should start sync engine process if register sync root success', async () => { + it('should skip if load virtual drive fails', async () => { // Given - registerSyncRootMock.mockResolvedValue(undefined); + loadVirtualDriveMock.mockResolvedValue(undefined); // When await spawnSyncEngineWorker(props); // Then - calls(addSyncIssueMock).toHaveLength(0); - calls(createSyncRootFolderMock).toHaveLength(1); - calls(registerSyncRootMock).toHaveLength(1); - calls(connectSyncRootMock).toHaveLength(1); + calls(loggerMock.error).toHaveLength(0); + calls(refreshItemPlaceholdersMock).toHaveLength(0); + }); + + it('should start sync engine process if load virtual drive success', async () => { + // Given + loadVirtualDriveMock.mockResolvedValue(1n); + // When + await spawnSyncEngineWorker(props); + // Then + calls(loggerMock.error).toHaveLength(0); calls(refreshItemPlaceholdersMock).toHaveLength(1); calls(scheduleSyncMock).toHaveLength(1); calls(recoverySyncMock).toHaveLength(1); diff --git a/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.ts b/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.ts index 5a0ce791e..22fc7d94e 100644 --- a/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.ts +++ b/src/apps/main/background-processes/sync-engine/services/spawn-sync-engine-worker.ts @@ -3,13 +3,11 @@ import { WorkerConfig, workers } from '@/apps/main/remote-sync/store'; import { scheduleSync } from './schedule-sync'; import { addRemoteSyncManager } from '@/apps/main/remote-sync/handlers'; import { RecoverySyncModule } from '@/backend/features/sync/recovery-sync/recovery-sync.module'; -import { Addon } from '@/node-win/addon-wrapper'; -import { addSyncIssue } from '../../issues'; import { refreshItemPlaceholders } from '@/apps/sync-engine/refresh-item-placeholders'; import { addPendingItems } from '@/apps/sync-engine/in/add-pending-items'; import { initWatcher } from '@/node-win/watcher/watcher'; -import { VirtualDrive } from '@/node-win/virtual-drive'; import { refreshWorkspaceToken } from '@/apps/sync-engine/refresh-workspace-token'; +import { loadVirtualDrive } from './load-virtual-drive'; type TProps = { ctx: SyncContext; @@ -18,18 +16,9 @@ type TProps = { export async function spawnSyncEngineWorker({ ctx }: TProps) { ctx.logger.debug({ msg: 'Spawn sync engine worker' }); - let connectionKey: bigint; - try { - try { - await VirtualDrive.createSyncRootFolder({ rootPath: ctx.rootPath }); - await Addon.registerSyncRoot({ rootPath: ctx.rootPath, providerId: ctx.providerId, providerName: ctx.providerName }); - connectionKey = Addon.connectSyncRoot({ ctx }); - ctx.logger.debug({ msg: 'Connection key', connectionKey }); - } catch (error) { - addSyncIssue({ error: 'CANNOT_REGISTER_VIRTUAL_DRIVE', name: ctx.rootPath }); - throw error; - } + const connectionKey = await loadVirtualDrive({ ctx }); + if (!connectionKey) return; /** * Jonathan Arce v2.5.1 diff --git a/src/apps/renderer/hooks/backups/useBackupProgress.tsx b/src/apps/renderer/hooks/backups/useBackupProgress.tsx index b2c168bb9..a749d2b43 100644 --- a/src/apps/renderer/hooks/backups/useBackupProgress.tsx +++ b/src/apps/renderer/hooks/backups/useBackupProgress.tsx @@ -8,9 +8,9 @@ export function useBackupProgress() { const [percentualProgress, setPercentualProgress] = useState(0); useEffect(() => { - const removeListener = window.electron.onBackupProgress(setBackupProgress); + const removeListener = globalThis.window.electron.onBackupProgress(setBackupProgress); - window.electron.getLastBackupProgress(); + void globalThis.window.electron.getLastBackupProgress(); return removeListener; }, []); diff --git a/src/backend/features/drive/actions/services/create-pending-files.test.ts b/src/backend/features/drive/actions/services/create-pending-files.test.ts index 0e9ff1c36..0ea6d95b7 100644 --- a/src/backend/features/drive/actions/services/create-pending-files.test.ts +++ b/src/backend/features/drive/actions/services/create-pending-files.test.ts @@ -4,7 +4,7 @@ import { abs } from '@/context/local/localFile/infrastructure/AbsolutePath'; import { NodeWin } from '@/infra/node-win/node-win.module'; import { FileUuid } from '@/apps/main/database/entities/DriveFile'; import * as createFile from './create-file'; -import { GetFileInfoError } from '@/infra/node-win/services/item-identity/get-file-info'; +import { GetFileInfoError } from '@/infra/node-win/services/get-file-info'; import { loggerMock } from '@/tests/vitest/mocks.helper.test'; describe('create-pending-files', () => { diff --git a/src/backend/features/drive/actions/services/create-pending-folders.test.ts b/src/backend/features/drive/actions/services/create-pending-folders.test.ts index 0e700953d..1002b2d64 100644 --- a/src/backend/features/drive/actions/services/create-pending-folders.test.ts +++ b/src/backend/features/drive/actions/services/create-pending-folders.test.ts @@ -4,7 +4,7 @@ import { abs } from '@/context/local/localFile/infrastructure/AbsolutePath'; import { NodeWin } from '@/infra/node-win/node-win.module'; import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; import * as createFolder from './create-folder'; -import { GetFolderInfoError } from '@/infra/node-win/services/item-identity/get-folder-info'; +import { GetFolderInfoError } from '@/infra/node-win/services/get-folder-info'; import { loggerMock } from '@/tests/vitest/mocks.helper.test'; import * as createPendingItems from './create-pending-items'; diff --git a/src/infra/node-win/node-win.module.ts b/src/infra/node-win/node-win.module.ts index 03126b11d..7c3de967d 100644 --- a/src/infra/node-win/node-win.module.ts +++ b/src/infra/node-win/node-win.module.ts @@ -1,7 +1,9 @@ -import { getFileInfo } from './services/item-identity/get-file-info'; -import { getFolderInfo } from './services/item-identity/get-folder-info'; +import { getFileInfo } from './services/get-file-info'; +import { getFolderInfo } from './services/get-folder-info'; +import { registerSyncRoot } from './services/register-sync-root'; export const NodeWin = { + registerSyncRoot, getFileInfo, getFolderInfo, }; diff --git a/src/infra/node-win/services/item-identity/get-file-info.infra.test.ts b/src/infra/node-win/services/get-file-info.infra.test.ts similarity index 100% rename from src/infra/node-win/services/item-identity/get-file-info.infra.test.ts rename to src/infra/node-win/services/get-file-info.infra.test.ts diff --git a/src/infra/node-win/services/item-identity/get-file-info.ts b/src/infra/node-win/services/get-file-info.ts similarity index 100% rename from src/infra/node-win/services/item-identity/get-file-info.ts rename to src/infra/node-win/services/get-file-info.ts diff --git a/src/infra/node-win/services/item-identity/get-folder-info.infra.test.ts b/src/infra/node-win/services/get-folder-info.infra.test.ts similarity index 100% rename from src/infra/node-win/services/item-identity/get-folder-info.infra.test.ts rename to src/infra/node-win/services/get-folder-info.infra.test.ts diff --git a/src/infra/node-win/services/item-identity/get-folder-info.ts b/src/infra/node-win/services/get-folder-info.ts similarity index 100% rename from src/infra/node-win/services/item-identity/get-folder-info.ts rename to src/infra/node-win/services/get-folder-info.ts diff --git a/src/infra/node-win/services/register-sync-root.infra.test.ts b/src/infra/node-win/services/register-sync-root.infra.test.ts new file mode 100644 index 000000000..b1b0dedd1 --- /dev/null +++ b/src/infra/node-win/services/register-sync-root.infra.test.ts @@ -0,0 +1,48 @@ +import { abs, join } from '@/context/local/localFile/infrastructure/AbsolutePath'; +import { v4 } from 'uuid'; +import { registerSyncRoot, RegisterSyncRootError } from './register-sync-root'; +import { mockProps } from '@/tests/vitest/utils.helper.test'; +import { TEST_FILES } from '@/tests/vitest/mocks.helper.test'; +import { VirtualDrive } from '@/node-win/virtual-drive'; +import { Addon } from '@/node-win/addon-wrapper'; + +describe('register-sync-root', () => { + const providerId = v4(); + const providerName = 'Internxt Drive'; + const testPath = join(TEST_FILES, v4()); + const rootPath = join(testPath, v4()); + + beforeAll(async () => { + await VirtualDrive.createSyncRootFolder({ rootPath }); + await Addon.registerSyncRoot({ rootPath, providerId, providerName }); + }); + + afterAll(async () => { + await Addon.unregisterSyncRoot({ providerId }); + }); + + it('should return error if path is too short', async () => { + // Given + const props = mockProps({ ctx: { rootPath: abs('/'), providerName, providerId: v4() } }); + // When + const error = await registerSyncRoot(props); + // Then + expect(error).toStrictEqual( + new RegisterSyncRootError( + 'UNKNOWN', + '[RegisterSyncRootAsync] WinRT error: The specified path is too short. It must have at least 1 character. (HRESULT: 0x80070057)', + ), + ); + }); + + it('should return error if path registered with different a provider id', async () => { + // Given + const props = mockProps({ ctx: { rootPath, providerName, providerId: v4() } }); + // When + const error = await registerSyncRoot(props); + // Then + expect(error).toStrictEqual( + new RegisterSyncRootError('ACCESS_DENIED', '[RegisterSyncRootAsync] WinRT error: Access is denied. (HRESULT: 0x80070005)'), + ); + }); +}); diff --git a/src/infra/node-win/services/register-sync-root.ts b/src/infra/node-win/services/register-sync-root.ts new file mode 100644 index 000000000..cadb4843d --- /dev/null +++ b/src/infra/node-win/services/register-sync-root.ts @@ -0,0 +1,35 @@ +import { SyncContext } from '@/apps/sync-engine/config'; +import { Addon } from '@/node-win/addon-wrapper'; + +export class RegisterSyncRootError extends Error { + constructor( + public readonly code: 'ACCESS_DENIED' | 'UNKNOWN', + cause?: unknown, + ) { + super(code, { cause }); + } +} + +type TProps = { + ctx: SyncContext; +}; + +export async function registerSyncRoot({ ctx }: TProps) { + try { + await Addon.registerSyncRoot({ + rootPath: ctx.rootPath, + providerId: ctx.providerId, + providerName: ctx.providerName, + }); + + return; + } catch (error) { + ctx.logger.error({ msg: 'Error registering sync root', error }); + + if (typeof error === 'string' && error.includes('0x80070005')) { + return new RegisterSyncRootError('ACCESS_DENIED', error); + } + + return new RegisterSyncRootError('UNKNOWN', error); + } +} diff --git a/src/node-win/addon-wrapper.ts b/src/node-win/addon-wrapper.ts index fb97bbc56..7f6968ab1 100644 --- a/src/node-win/addon-wrapper.ts +++ b/src/node-win/addon-wrapper.ts @@ -76,6 +76,11 @@ export class Addon { return parseAddonZod('getPlaceholderState', result); } + static async getSyncRootFromPath({ path }: { path: AbsolutePath }) { + const result = await addon.getSyncRootFromPath(toWin32Path(path)); + return parseAddonZod('getSyncRootFromPath', result); + } + static async updatePlaceholder({ path, placeholderId, size }: { path: AbsolutePath; placeholderId: FilePlaceholderId; size: number }) { await addon.updatePlaceholder(toWin32DevicePath(path), placeholderId, size); } diff --git a/src/node-win/addon.ts b/src/node-win/addon.ts index afb5ea3dd..a8d3ad58a 100644 --- a/src/node-win/addon.ts +++ b/src/node-win/addon.ts @@ -44,10 +44,11 @@ type TAddon = { disconnectSyncRoot(connectionKey: bigint): Promise; getPlaceholderState(path: Win32DevicePath): Promise>; getRegisteredSyncRoots(): z.infer; + getSyncRootFromPath(rootPath: Win32Path): Promise>; hydrateFile(path: Win32Path): Promise; - registerSyncRoot(rootPath: Win32Path, providerName: string, providerVersion: string, providerId: string, logoPath: string): Promise; + registerSyncRoot(rootPath: Win32Path, providerName: string, providerVersion: string, id: string, logoPath: string): Promise; setPinState(path: Win32DevicePath, pinState: PinState): Promise; - unregisterSyncRoot(providerId: string): Promise; + unregisterSyncRoot(id: string): Promise; unwatchPath(handle: object): void; updatePlaceholder(path: Win32DevicePath, placeholderId: FilePlaceholderId, size: number): Promise; updateSyncStatus(path: Win32DevicePath): Promise; diff --git a/src/node-win/addon/addon-zod.ts b/src/node-win/addon/addon-zod.ts index 8fe8ae2c6..25bd7a16a 100644 --- a/src/node-win/addon/addon-zod.ts +++ b/src/node-win/addon/addon-zod.ts @@ -11,6 +11,9 @@ export const addonZod = { inSyncState: z.enum(InSyncState), onDiskSize: z.number(), }), + getSyncRootFromPath: z.object({ + id: z.string(), + }), watchPath: z.object(), getRegisteredSyncRoots: z.array( z.object({ diff --git a/src/node-win/tests/register-sync-root.infra.test.ts b/src/node-win/tests/register-sync-root.infra.test.ts deleted file mode 100644 index 340f64a87..000000000 --- a/src/node-win/tests/register-sync-root.infra.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { abs } from '@/context/local/localFile/infrastructure/AbsolutePath'; -import { Addon } from '../addon-wrapper'; -import { v4 } from 'uuid'; - -describe('register-sync-root', () => { - it('should throw error if path is too short', async () => { - // When - const promise = Addon.registerSyncRoot({ rootPath: abs('/'), providerName: 'Internxt Drive', providerId: v4() }); - // Then - await expect(promise).rejects.toThrow( - '[RegisterSyncRootAsync] WinRT error: The specified path is too short. It must have at least 1 character. (HRESULT: 0x80070057)', - ); - }); -}); diff --git a/src/node-win/watcher/events/on-add-dir.service.test.ts b/src/node-win/watcher/events/on-add-dir.service.test.ts index 268c9c54c..bc2e450d0 100644 --- a/src/node-win/watcher/events/on-add-dir.service.test.ts +++ b/src/node-win/watcher/events/on-add-dir.service.test.ts @@ -6,7 +6,7 @@ import * as moveFolder from '@/backend/features/local-sync/watcher/events/rename import * as trackAddEvent from '@/backend/features/local-sync/watcher/events/unlink/is-move-event'; import { Drive } from '@/backend/features/drive'; import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; -import { GetFolderInfoError } from '@/infra/node-win/services/item-identity/get-folder-info'; +import { GetFolderInfoError } from '@/infra/node-win/services/get-folder-info'; describe('on-add-dir', () => { const getFolderInfoMock = partialSpyOn(NodeWin, 'getFolderInfo'); diff --git a/src/node-win/watcher/events/on-add.service.test.ts b/src/node-win/watcher/events/on-add.service.test.ts index bd447806f..fd33c3c42 100644 --- a/src/node-win/watcher/events/on-add.service.test.ts +++ b/src/node-win/watcher/events/on-add.service.test.ts @@ -5,10 +5,10 @@ import { NodeWin } from '@/infra/node-win/node-win.module'; import { FileUuid } from '@/apps/main/database/entities/DriveFile'; import * as moveFile from '@/backend/features/local-sync/watcher/events/rename-or-move/move-file'; import * as trackAddEvent from '@/backend/features/local-sync/watcher/events/unlink/is-move-event'; -import { GetFileInfoError } from '@/infra/node-win/services/item-identity/get-file-info'; +import { GetFileInfoError } from '@/infra/node-win/services/get-file-info'; import { Drive } from '@/backend/features/drive'; import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; -import { GetFolderInfoError } from '@/infra/node-win/services/item-identity/get-folder-info'; +import { GetFolderInfoError } from '@/infra/node-win/services/get-folder-info'; describe('on-add', () => { const getFileInfoMock = partialSpyOn(NodeWin, 'getFileInfo');