From e62d1611c5516f9812150f39c910c3b958c8b636 Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Wed, 11 Sep 2024 16:59:39 -0300 Subject: [PATCH 1/6] Added validations to prevent temporary files in the app --- src/apps/sync-engine/BindingManager.ts | 25 +++++++++++-- src/apps/utils/isTemporalFile.ts | 36 +++++++++++++++++++ .../application/FileSyncOrchestrator.ts | 10 ++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/apps/utils/isTemporalFile.ts diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index c98480b18..ae7fb65c6 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -2,7 +2,6 @@ import Logger from 'electron-log'; import * as fs from 'fs'; import { VirtualDrive, QueueItem } from 'virtual-drive/dist'; import { FilePlaceholderId } from '../../context/virtual-drive/files/domain/PlaceholderId'; -import { PlatformPathConverter } from '../../context/virtual-drive/shared/application/PlatformPathConverter'; import { IControllers, buildControllers, @@ -20,6 +19,7 @@ import { QueueManager } from './dependency-injection/common/QueueManager'; import { DependencyInjectionLogWatcherPath } from './dependency-injection/common/logEnginePath'; import configStore from '../main/config'; import { FilePath } from '../../context/virtual-drive/files/domain/FilePath'; +import { isTemporaryFile } from '../utils/isTemporalFile'; export type CallbackDownload = ( success: boolean, @@ -93,12 +93,12 @@ export class BindingsManager { Sentry.captureException(error); callback(false); }); - ipcRenderer.send('CHECK_SYNC'); + ipcRenderer.send('SYNCED'); }, notifyDeleteCompletionCallback: () => { Logger.info('Deletion completed'); }, - notifyRenameCallback: ( + notifyRenameCallback: async ( absolutePath: string, contentsId: string, callback: (response: boolean) => void @@ -106,6 +106,16 @@ export class BindingsManager { try { Logger.debug('Path received from rename callback', absolutePath); + const isTempFile = await isTemporaryFile(absolutePath); + + Logger.debug('[isTemporaryFile]', isTempFile); + + if (isTempFile) { + Logger.debug('File is temporary, skipping'); + callback(true); + return; + } + const fn = executeControllerWithFallback({ handler: this.controllers.renameOrMove.execute.bind( this.controllers.renameOrMove @@ -316,6 +326,15 @@ export class BindingsManager { try { Logger.debug('Path received from handle add', task.path); + const tempFile = await isTemporaryFile(task.path); + + Logger.debug('[isTemporaryFile]', tempFile); + + if (tempFile) { + Logger.debug('File is temporary, skipping'); + return; + } + const itemId = await this.controllers.addFile.execute(task.path); if (!itemId) { Logger.error('Error adding file' + task.path); diff --git a/src/apps/utils/isTemporalFile.ts b/src/apps/utils/isTemporalFile.ts new file mode 100644 index 000000000..b8e0976fb --- /dev/null +++ b/src/apps/utils/isTemporalFile.ts @@ -0,0 +1,36 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import Logger from 'electron-log'; + +// Function to check if the file is marked as temporary by the OS (Windows example) +export const isTemporaryFile = async (filePath: string): Promise => { + try { + // Check if the file name starts with a common temporary prefix + if (path.basename(filePath).startsWith('~$')) { + return true; + } + + // // Check if the file has common temporary file extensions + const tempExtensions = ['.tmp', '.temp', '.swp']; + if (tempExtensions.includes(path.extname(filePath).toLowerCase())) { + return true; + } + + // check if havent extension + if (!path.extname(filePath)) { + Logger.debug(`File ${filePath} has no extension`); + return true; + } + + // Check if the file has the "TEMPORARY" attribute (Windows-specific) + // const stats = await fs.promises.stat(filePath); + // if (stats.isFile()) { + // return stats.mode !== 0; + // } + + return false; + } catch (error) { + Logger.error(`Failed to check if the file is temporary: ${error}`); + return false; + } +}; diff --git a/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts b/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts index 7d09a9e40..5d13b31cc 100644 --- a/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts +++ b/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts @@ -1,5 +1,7 @@ +import { isTemporaryFile } from '../../../../apps/utils/isTemporalFile'; import { RetryContentsUploader } from '../../contents/application/RetryContentsUploader'; import { FileSyncronizer } from '../../files/application/FileSyncronizer'; +import Logger from 'electron-log'; export class FileSyncOrchestrator { constructor( @@ -9,6 +11,14 @@ export class FileSyncOrchestrator { async run(absolutePaths: string[]): Promise { for (const absolutePath of absolutePaths) { + const tempFile = await isTemporaryFile(absolutePath); + + Logger.debug('[isTemporaryFile]', tempFile); + + if (tempFile) { + Logger.debug(`Skipping temporary file: ${absolutePath}`); + continue; + } try { await this.fileSyncronizer.run( absolutePath, From 52a26b3264654a006b41ef9fb14c4f04d9d30280 Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Wed, 18 Sep 2024 11:58:37 -0300 Subject: [PATCH 2/6] update start sync remote start --- .../main/remote-sync/RemoteSyncManager.ts | 1 - src/apps/main/remote-sync/handlers.ts | 11 ++- src/apps/renderer/pages/Widget/index.tsx | 6 +- src/apps/sync-engine/BindingManager.ts | 11 ++- .../infrastructure/FSLocalFileProvider.ts | 92 ++++++++++--------- 5 files changed, 68 insertions(+), 53 deletions(-) diff --git a/src/apps/main/remote-sync/RemoteSyncManager.ts b/src/apps/main/remote-sync/RemoteSyncManager.ts index 7daf24b41..32f304bef 100644 --- a/src/apps/main/remote-sync/RemoteSyncManager.ts +++ b/src/apps/main/remote-sync/RemoteSyncManager.ts @@ -128,7 +128,6 @@ export class RemoteSyncManager { await this.db.folders.connect(); Logger.info('Starting RemoteSyncManager'); - this.changeStatus('SYNCING'); try { const syncOptions = { retry: 1, diff --git a/src/apps/main/remote-sync/handlers.ts b/src/apps/main/remote-sync/handlers.ts index fc0a33457..fabf73c03 100644 --- a/src/apps/main/remote-sync/handlers.ts +++ b/src/apps/main/remote-sync/handlers.ts @@ -146,16 +146,17 @@ export async function updateRemoteSync(): Promise { // that we received the notification, but if we check // for new data we don't receive it Logger.info('Updating remote sync'); - const isSyncing = await checkSyncEngineInProcess(2_000); - if (isSyncing) { - Logger.info('Remote sync is already running'); - return; - } + const userData = configStore.get('userData'); const lastFilesSyncAt = await remoteSyncManager.getFileCheckpoint(); Logger.info('Last files sync at', lastFilesSyncAt); const folderId = lastFilesSyncAt ? undefined : userData?.root_folder_id; await startRemoteSync(folderId); + const isSyncing = await checkSyncEngineInProcess(2_000); + if (isSyncing) { + Logger.info('Remote sync is already running'); + return; + } updateSyncEngine(); } export async function fallbackRemoteSync(): Promise { diff --git a/src/apps/renderer/pages/Widget/index.tsx b/src/apps/renderer/pages/Widget/index.tsx index afae28a81..3f9bcd3d5 100644 --- a/src/apps/renderer/pages/Widget/index.tsx +++ b/src/apps/renderer/pages/Widget/index.tsx @@ -12,9 +12,9 @@ export default function Widget() { const [isLogoutModalOpen, setIsLogoutModalOpen] = useState(false); const handleRetrySync = () => { - // window.electron.startRemoteSync().catch((err) => { - // reportError(err); - // }); + window.electron.startRemoteSync().catch((err) => { + reportError(err); + }); }; const displayErrorInWidget = syncStatus && syncStatus === 'FAILED'; diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index ae7fb65c6..32cf5b1c5 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -330,10 +330,11 @@ export class BindingsManager { Logger.debug('[isTemporaryFile]', tempFile); - if (tempFile) { + if (tempFile && !task.isFolder) { Logger.debug('File is temporary, skipping'); return; } + ipcRenderer.send('RECEIVED_REMOTE_CHANGES'); const itemId = await this.controllers.addFile.execute(task.path); if (!itemId) { @@ -359,6 +360,8 @@ export class BindingsManager { try { const syncRoot = configStore.get('syncRoot'); Logger.debug('[Handle Hydrate Callback] Preparing begins', task.path); + const start = Date.now(); + const normalizePath = (path: string) => path.replace(/\\/g, '/'); const normalizedLastHydrated = normalizePath(this.lastHydrated); @@ -380,6 +383,12 @@ export class BindingsManager { await this.container.virtualDrive.hydrateFile(task.path); + const finish = Date.now(); + + if (finish - start < 1500) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + Logger.debug('[Handle Hydrate Callback] Finish begins', task.path); } catch (error) { Logger.error(`error hydrating file ${task.path}`); diff --git a/src/context/virtual-drive/contents/infrastructure/FSLocalFileProvider.ts b/src/context/virtual-drive/contents/infrastructure/FSLocalFileProvider.ts index 1b5c373aa..b92317266 100644 --- a/src/context/virtual-drive/contents/infrastructure/FSLocalFileProvider.ts +++ b/src/context/virtual-drive/contents/infrastructure/FSLocalFileProvider.ts @@ -80,50 +80,56 @@ export class FSLocalFileProvider implements LocalContentsProvider { } async provide(absoluteFilePath: string) { - const { readable, controller } = await this.createAbortableStream( - absoluteFilePath - ); - - const { size, mtimeMs, birthtimeMs } = await fs.stat(absoluteFilePath); - - const absoluteFolderPath = path.dirname(absoluteFilePath); - const nameWithExtension = path.basename(absoluteFilePath); - - const watcher = watch(absoluteFolderPath, (_, filename) => { - if (filename !== nameWithExtension) { - return; - } - Logger.warn( - filename, - ' has been changed during read, it will be aborted' + try { + // Creación del stream con posibilidad de aborto + const { readable, controller } = await this.createAbortableStream( + absoluteFilePath ); - controller.abort(); - }); - - readable.on('end', () => { - watcher.close(); - this.reading.delete(absoluteFilePath); - }); - - readable.on('close', () => { - this.reading.delete(absoluteFilePath); - }); - - const [name, extension] = extractNameAndExtension(nameWithExtension); - - const contents = LocalFileContents.from({ - name, - extension, - size, - modifiedTime: mtimeMs, - birthTime: birthtimeMs, - contents: readable, - }); - - return { - contents, - abortSignal: controller.signal, - }; + const { size, mtimeMs, birthtimeMs } = await fs.stat(absoluteFilePath); + + const absoluteFolderPath = path.dirname(absoluteFilePath); + const nameWithExtension = path.basename(absoluteFilePath); + + const watcher = watch(absoluteFolderPath, (_, filename) => { + if (filename !== nameWithExtension) { + return; + } + Logger.warn( + filename, + ' has been changed during read, it will be aborted' + ); + + controller.abort(); + }); + + readable.on('end', () => { + watcher.close(); + this.reading.delete(absoluteFilePath); + }); + + readable.on('close', () => { + this.reading.delete(absoluteFilePath); + }); + + const [name, extension] = extractNameAndExtension(nameWithExtension); + + const contents = LocalFileContents.from({ + name, + extension, + size, + modifiedTime: mtimeMs, + birthTime: birthtimeMs, + contents: readable, + }); + + return { + contents, + abortSignal: controller.signal, + }; + } catch (error) { + Logger.error(`Error providing file: ${error}`); + throw error; + } } } From ffac4e1f7be53c224ff05f4741e31eaefd6458ed Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Wed, 18 Sep 2024 12:09:33 -0300 Subject: [PATCH 3/6] update package json --- package.json | 2 +- release/app/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index db1f5a3ee..e3d50aa68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "internxt-drive", - "version": "2.1.2", + "version": "2.1.3", "author": "Internxt ", "description": "Internxt Drive client UI", "license": "AGPL-3.0", diff --git a/release/app/package.json b/release/app/package.json index 1b9c48bd8..0e6eab0a6 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "internxt-drive", - "version": "2.1.2", + "version": "2.1.3", "description": "Internxt Drive client UI", "main": "./dist/main/main.js", "author": "Internxt ", From 7c5702464e9ab231dfa7c1447032141df62f025a Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Mon, 23 Sep 2024 13:34:47 -0300 Subject: [PATCH 4/6] delete log --- src/apps/main/auth/handlers.ts | 2 +- .../main/background-processes/sync-engine.ts | 2 -- .../main/remote-sync/RemoteSyncManager.ts | 6 ++-- .../infrastructure/HttpRemoteFolderSystem.ts | 29 ++++++++++++------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/apps/main/auth/handlers.ts b/src/apps/main/auth/handlers.ts index fc6b49d0d..f605cf51c 100644 --- a/src/apps/main/auth/handlers.ts +++ b/src/apps/main/auth/handlers.ts @@ -66,7 +66,7 @@ ipcMain.on('user-logged-in', async (_, data: AccessResponse) => { if (!canHisConfigBeRestored(data.user.uuid)) { await setupRootFolder(); } - await await clearRootVirtualDrive(); + await clearRootVirtualDrive(); setIsLoggedIn(true); eventBus.emit('USER_LOGGED_IN'); diff --git a/src/apps/main/background-processes/sync-engine.ts b/src/apps/main/background-processes/sync-engine.ts index 0fab779af..12696a985 100644 --- a/src/apps/main/background-processes/sync-engine.ts +++ b/src/apps/main/background-processes/sync-engine.ts @@ -189,7 +189,6 @@ async function stopAndClearSyncEngineWatcher() { Logger.info('[MAIN] WORKER WAS NOT RUNNING'); worker?.destroy(); worker = null; - await clearRootVirtualDrive(); return; } @@ -232,7 +231,6 @@ async function stopAndClearSyncEngineWatcher() { worker?.destroy(); workerIsRunning = false; worker = null; - await clearRootVirtualDrive(); } } diff --git a/src/apps/main/remote-sync/RemoteSyncManager.ts b/src/apps/main/remote-sync/RemoteSyncManager.ts index 32f304bef..cd8eb8018 100644 --- a/src/apps/main/remote-sync/RemoteSyncManager.ts +++ b/src/apps/main/remote-sync/RemoteSyncManager.ts @@ -4,9 +4,9 @@ import { RemoteSyncedFolder, RemoteSyncedFile, SyncConfig, - FIVETEEN_MINUTES_IN_MILLISECONDS, rewind, WAITING_AFTER_SYNCING_DEFAULT, + SIX_HOURS_IN_MILLISECONDS, } from './helpers'; import { reportError } from '../bug-report/service'; @@ -251,7 +251,7 @@ export class RemoteSyncManager { const updatedAt = new Date(result.updatedAt); - return rewind(updatedAt, FIVETEEN_MINUTES_IN_MILLISECONDS); + return rewind(updatedAt, SIX_HOURS_IN_MILLISECONDS); } /** @@ -406,7 +406,7 @@ export class RemoteSyncManager { const updatedAt = new Date(result.updatedAt); - return rewind(updatedAt, FIVETEEN_MINUTES_IN_MILLISECONDS); + return rewind(updatedAt, SIX_HOURS_IN_MILLISECONDS); } private async syncRemoteFolders( diff --git a/src/context/virtual-drive/folders/infrastructure/HttpRemoteFolderSystem.ts b/src/context/virtual-drive/folders/infrastructure/HttpRemoteFolderSystem.ts index 1e350c13d..ae1fec613 100644 --- a/src/context/virtual-drive/folders/infrastructure/HttpRemoteFolderSystem.ts +++ b/src/context/virtual-drive/folders/infrastructure/HttpRemoteFolderSystem.ts @@ -146,19 +146,28 @@ export class HttpRemoteFolderSystem implements RemoteFolderSystem { } async rename(folder: Folder): Promise { - const url = `${process.env.API_URL}/storage/folder/${folder.id}/meta`; + try { + const url = `${process.env.API_URL}/storage/folder/${folder.id}/meta`; - const body: UpdateFolderNameDTO = { - metadata: { itemName: folder.name }, - relativePath: uuid.v4(), - }; + const body: UpdateFolderNameDTO = { + metadata: { itemName: folder.name }, + relativePath: uuid.v4(), + }; - const res = await this.driveClient.post(url, body); + const res = await this.driveClient.post(url, body); - if (res.status !== 200) { - throw new Error( - `[FOLDER FILE SYSTEM] Error updating item metadata: ${res.status}` - ); + if (res.status !== 200) { + throw new Error( + `[FOLDER FILE SYSTEM] Error updating item metadata: ${res.status}` + ); + } + } catch (error) { + Logger.error('[FOLDER FILE SYSTEM] Error renaming folder'); + if (axios.isAxiosError(error)) { + Logger.error('[Is Axios Error]', error.response?.data); + } + Sentry.captureException(error); + throw error; } } From 9bbd0d788c2a2369e43a897cebb9c70fb01f01fa Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Wed, 25 Sep 2024 16:17:42 -0300 Subject: [PATCH 5/6] Implemented the removal of items from the database once the server returns the correct status. Added a schedule to sync every 2 hours. --- .../main/background-processes/sync-engine.ts | 12 +- src/apps/main/database/adapters/base.ts | 8 ++ .../collections/DriveFileCollection.ts | 22 ++++ .../collections/DriveFolderCollection.ts | 21 ++++ .../main/remote-sync/RemoteSyncManager.ts | 4 +- src/apps/main/remote-sync/handlers.ts | 106 ++++++++++++++++++ src/apps/sync-engine/BindingManager.ts | 3 +- src/apps/utils/isTemporalFile.ts | 6 + .../application/FilesPlaceholderUpdater.ts | 23 ++++ .../application/UpdatePlaceholderFolder.ts | 17 +++ 10 files changed, 218 insertions(+), 4 deletions(-) diff --git a/src/apps/main/background-processes/sync-engine.ts b/src/apps/main/background-processes/sync-engine.ts index 12696a985..6a16012f5 100644 --- a/src/apps/main/background-processes/sync-engine.ts +++ b/src/apps/main/background-processes/sync-engine.ts @@ -5,12 +5,12 @@ import eventBus from '../event-bus'; import nodeSchedule from 'node-schedule'; import * as Sentry from '@sentry/electron/main'; import { checkSyncEngineInProcess } from '../remote-sync/handlers'; -import { clearRootVirtualDrive } from '../virtual-root-folder/service'; let worker: BrowserWindow | null = null; let workerIsRunning = false; let startingWorker = false; let healthCheckSchedule: nodeSchedule.Job | null = null; +let syncSchedule: nodeSchedule.Job | null = null; let attemptsAlreadyStarting = 0; ipcMain.once('SYNC_ENGINE_PROCESS_SETUP_SUCCESSFUL', () => { @@ -82,6 +82,15 @@ function scheduleHeathCheck() { } }); } +function scheduleSync() { + if (syncSchedule) { + syncSchedule.cancel(false); + } + + syncSchedule = nodeSchedule.scheduleJob('0 0 */2 * * *', async () => { + eventBus.emit('RECEIVED_REMOTE_CHANGES'); + }); +} function spawnSyncEngineWorker() { if (startingWorker) { @@ -113,6 +122,7 @@ function spawnSyncEngineWorker() { .then(() => { Logger.info('[MAIN] Sync engine worker loaded'); scheduleHeathCheck(); + scheduleSync(); }) .catch((err) => { Logger.error('[MAIN] Error loading sync engine worker', err); diff --git a/src/apps/main/database/adapters/base.ts b/src/apps/main/database/adapters/base.ts index b0622e5e9..b746965b9 100644 --- a/src/apps/main/database/adapters/base.ts +++ b/src/apps/main/database/adapters/base.ts @@ -41,4 +41,12 @@ export interface DatabaseCollectionAdapter { success: boolean; result: DatabaseItemType | null; }>; + + /** + * Gets items from partial data + */ + + searchPartialBy( + partialData: Partial + ): Promise<{ success: boolean; result: DatabaseItemType[] }>; } diff --git a/src/apps/main/database/collections/DriveFileCollection.ts b/src/apps/main/database/collections/DriveFileCollection.ts index 9c22ee07a..af69fe6ae 100644 --- a/src/apps/main/database/collections/DriveFileCollection.ts +++ b/src/apps/main/database/collections/DriveFileCollection.ts @@ -94,4 +94,26 @@ export class DriveFilesCollection }; } } + + async searchPartialBy( + partialData: Partial + ): Promise<{ success: boolean; result: DriveFile[] }> { + try { + Logger.info('Searching partial by', partialData); + const result = await this.repository.find({ + where: partialData, + }); + return { + success: true, + result, + }; + } catch (error) { + Sentry.captureException(error); + Logger.error('Error fetching drive folders:', error); + return { + success: false, + result: [], + }; + } + } } diff --git a/src/apps/main/database/collections/DriveFolderCollection.ts b/src/apps/main/database/collections/DriveFolderCollection.ts index c5fcf02a4..a95314b7e 100644 --- a/src/apps/main/database/collections/DriveFolderCollection.ts +++ b/src/apps/main/database/collections/DriveFolderCollection.ts @@ -93,4 +93,25 @@ export class DriveFoldersCollection }; } } + + async searchPartialBy( + partialData: Partial + ): Promise<{ success: boolean; result: DriveFolder[] }> { + try { + const result = await this.repository.find({ + where: partialData, + }); + return { + success: true, + result, + }; + } catch (error) { + Sentry.captureException(error); + Logger.error('Error fetching drive folders:', error); + return { + success: false, + result: [], + }; + } + } } diff --git a/src/apps/main/remote-sync/RemoteSyncManager.ts b/src/apps/main/remote-sync/RemoteSyncManager.ts index cd8eb8018..dfdf7c99a 100644 --- a/src/apps/main/remote-sync/RemoteSyncManager.ts +++ b/src/apps/main/remote-sync/RemoteSyncManager.ts @@ -142,11 +142,11 @@ export class RemoteSyncManager { ? this.syncRemoteFoldersByFolder(syncOptions, folderId) : this.syncRemoteFolders(syncOptions); - const [_files, folders] = await Promise.all([ + const [files, folders] = await Promise.all([ await syncFilesPromise, await syncFoldersPromise, ]); - return { files: _files, folders }; + return { files, folders }; } catch (error) { this.changeStatus('SYNC_FAILED'); reportError(error as Error); diff --git a/src/apps/main/remote-sync/handlers.ts b/src/apps/main/remote-sync/handlers.ts index fabf73c03..99302fab8 100644 --- a/src/apps/main/remote-sync/handlers.ts +++ b/src/apps/main/remote-sync/handlers.ts @@ -17,6 +17,8 @@ import { import { debounce } from 'lodash'; import configStore from '../config'; import { setTrayStatus } from '../tray/tray'; +import { FilePlaceholderId } from '../../../context/virtual-drive/files/domain/PlaceholderId'; +import { FolderPlaceholderId } from '../../../context/virtual-drive/folders/domain/FolderPlaceholderId'; const SYNC_DEBOUNCE_DELAY = 500; @@ -243,3 +245,107 @@ export async function checkSyncInProgress(milliSeconds: number) { ipcMain.handle('CHECK_SYNC_IN_PROGRESS', async (_, milliSeconds: number) => { return await checkSyncInProgress(milliSeconds); }); +// ipcMain.handle( +// 'DELETE_ITEM_DRIVE', +// async (_, itemId: FilePlaceholderId | FolderPlaceholderId) => { +// try { +// const [type, id] = itemId +// .replace( +// // eslint-disable-next-line no-control-regex +// /[\x00-\x1F\x7F-\x9F]/g, +// '' +// ) +// .normalize() +// .split(':'); + +// Logger.info('Deleting item in handler', itemId); +// Logger.info('Type and id', type, id); + +// const isFolder = type === 'FOLDER'; +// let result; +// if (isFolder) { +// result = await driveFoldersCollection.update(id, { status: 'TRASHED' }); +// } else { +// const item = await driveFilesCollection.searchPartialBy({ +// fileId: id, +// }); +// Logger.info('Item to delete', item); +// if (!item.result.length) return false; +// result = await driveFilesCollection.update(item.result[0].uuid, { +// status: 'TRASHED', +// }); +// } + +// Logger.info('Result deleting item in handler', result); +// return true; +// } catch (error) { +// Logger.error('Error deleting item in handler', error); +// return false; +// } +// } +// ); + +function parseItemId(itemId: string) { + const [type, id] = itemId + .replace( + // eslint-disable-next-line no-control-regex + /[\x00-\x1F\x7F-\x9F]/g, + '' + ) + .normalize() + .split(':'); + if (!type || !id) { + throw new Error(`Invalid itemId format: ${itemId}`); + } + return { type, id }; +} + +async function deleteFolder(folderId: string): Promise { + try { + const result = await driveFoldersCollection.update(folderId, { + status: 'TRASHED', + }); + return result.success; + } catch (error) { + Logger.error('Error deleting folder', { folderId, error }); + throw error; + } +} + +async function deleteFile(fileId: string): Promise { + try { + const item = await driveFilesCollection.searchPartialBy({ fileId }); + if (!item.result.length) { + Logger.warn('File not found', { fileId }); + return false; + } + const result = await driveFilesCollection.update(item.result[0].uuid, { + status: 'TRASHED', + }); + return result.success; + } catch (error) { + Logger.error('Error deleting file', { fileId, error }); + throw error; + } +} + +ipcMain.handle( + 'DELETE_ITEM_DRIVE', + async ( + _, + itemId: FilePlaceholderId | FolderPlaceholderId + ): Promise => { + try { + const { type, id } = parseItemId(itemId); + Logger.info('Deleting item in handler', { type, id }); + + const isFolder = type === 'FOLDER'; + const result = isFolder ? await deleteFolder(id) : await deleteFile(id); + + return result; + } catch (error) { + Logger.error('Error deleting item in handler', { error }); + return false; + } + } +); diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index 32cf5b1c5..dad60abf7 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -83,10 +83,12 @@ export class BindingsManager { contentsId: string, callback: (response: boolean) => void ) => { + Logger.debug('Path received from delete callback', contentsId); this.controllers.delete .execute(contentsId) .then(() => { callback(true); + ipcRenderer.invoke('DELETE_ITEM_DRIVE', contentsId); }) .catch((error: Error) => { Logger.error(error); @@ -334,7 +336,6 @@ export class BindingsManager { Logger.debug('File is temporary, skipping'); return; } - ipcRenderer.send('RECEIVED_REMOTE_CHANGES'); const itemId = await this.controllers.addFile.execute(task.path); if (!itemId) { diff --git a/src/apps/utils/isTemporalFile.ts b/src/apps/utils/isTemporalFile.ts index b8e0976fb..6fd2c147b 100644 --- a/src/apps/utils/isTemporalFile.ts +++ b/src/apps/utils/isTemporalFile.ts @@ -22,6 +22,12 @@ export const isTemporaryFile = async (filePath: string): Promise => { return true; } + // if start with $Recycle.Bin + if (filePath.includes('$Recycle.Bin')) { + Logger.debug(`File ${filePath} is in Recycle Bin`); + return true; + } + // Check if the file has the "TEMPORARY" attribute (Windows-specific) // const stats = await fs.promises.stat(filePath); // if (stats.isFile()) { diff --git a/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts b/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts index 3e672c5e6..ec3d7c7b7 100644 --- a/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts +++ b/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts @@ -26,6 +26,24 @@ export class FilesPlaceholderUpdater { return localExists && (remoteIsTrashed || remoteIsDeleted); } + private async hasToBeCreated(remote: File): Promise { + const remoteExists = remote.status.is(FileStatuses.EXISTS); + const win32AbsolutePath = this.relativePathToAbsoluteConverter.run( + remote.path + ); + const existsFile = await this.fileExists(win32AbsolutePath); + return remoteExists && !existsFile; + } + + private async fileExists(win32AbsolutePath: string): Promise { + try { + await fs.stat(win32AbsolutePath); + return true; + } catch { + return false; + } + } + private async update(remote: File): Promise { const local = this.repository.searchByPartial({ contentsId: remote.contentsId, @@ -77,6 +95,11 @@ export class FilesPlaceholderUpdater { ); await fs.unlink(win32AbsolutePath); } + + if (await this.hasToBeCreated(remote)) { + await this.localFileSystem.createPlaceHolder(remote); + await this.repository.update(remote); + } } async run(remotes: Array): Promise { diff --git a/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts b/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts index 3426b026d..3a2170ef3 100644 --- a/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts +++ b/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts @@ -73,6 +73,18 @@ export class FolderPlaceholderUpdater { return localExists && (remoteIsTrashed || remoteIsDeleted); } + private async hasToBeCreated(remote: Folder): Promise { + const remoteExists = remote.status.is(FolderStatuses.EXISTS); + + const win32AbsolutePath = this.relativePathToAbsoluteConverter.run( + remote.path + ); + + const existsFolder = await this.folderExists(win32AbsolutePath); + + return remoteExists && !existsFolder; + } + private async update(remote: Folder): Promise { if (remote.path === path.posix.sep) { return; @@ -135,6 +147,11 @@ export class FolderPlaceholderUpdater { await fs.rm(win32AbsolutePath, { recursive: true }); return; } + + if (await this.hasToBeCreated(remote)) { + await this.local.createPlaceHolder(remote); + await this.repository.update(remote); + } } async run(remotes: Array): Promise { From 310b92bcf8749299a65e9a7518d658ea9f55cd2f Mon Sep 17 00:00:00 2001 From: ArceDanielShok Date: Mon, 30 Sep 2024 11:30:17 -0300 Subject: [PATCH 6/6] delete comment and update rewind time --- src/apps/main/remote-sync/RemoteSyncManager.ts | 6 +++--- src/apps/utils/isTemporalFile.ts | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/apps/main/remote-sync/RemoteSyncManager.ts b/src/apps/main/remote-sync/RemoteSyncManager.ts index dfdf7c99a..4e21632ab 100644 --- a/src/apps/main/remote-sync/RemoteSyncManager.ts +++ b/src/apps/main/remote-sync/RemoteSyncManager.ts @@ -6,7 +6,7 @@ import { SyncConfig, rewind, WAITING_AFTER_SYNCING_DEFAULT, - SIX_HOURS_IN_MILLISECONDS, + FIVETEEN_MINUTES_IN_MILLISECONDS, } from './helpers'; import { reportError } from '../bug-report/service'; @@ -251,7 +251,7 @@ export class RemoteSyncManager { const updatedAt = new Date(result.updatedAt); - return rewind(updatedAt, SIX_HOURS_IN_MILLISECONDS); + return rewind(updatedAt, FIVETEEN_MINUTES_IN_MILLISECONDS); } /** @@ -406,7 +406,7 @@ export class RemoteSyncManager { const updatedAt = new Date(result.updatedAt); - return rewind(updatedAt, SIX_HOURS_IN_MILLISECONDS); + return rewind(updatedAt, FIVETEEN_MINUTES_IN_MILLISECONDS); } private async syncRemoteFolders( diff --git a/src/apps/utils/isTemporalFile.ts b/src/apps/utils/isTemporalFile.ts index 6fd2c147b..b22cf01ed 100644 --- a/src/apps/utils/isTemporalFile.ts +++ b/src/apps/utils/isTemporalFile.ts @@ -1,8 +1,6 @@ -import * as fs from 'fs'; import * as path from 'path'; import Logger from 'electron-log'; -// Function to check if the file is marked as temporary by the OS (Windows example) export const isTemporaryFile = async (filePath: string): Promise => { try { // Check if the file name starts with a common temporary prefix @@ -27,13 +25,6 @@ export const isTemporaryFile = async (filePath: string): Promise => { Logger.debug(`File ${filePath} is in Recycle Bin`); return true; } - - // Check if the file has the "TEMPORARY" attribute (Windows-specific) - // const stats = await fs.promises.stat(filePath); - // if (stats.isFile()) { - // return stats.mode !== 0; - // } - return false; } catch (error) { Logger.error(`Failed to check if the file is temporary: ${error}`);