From 292aaac4aae8ac5e2d28e518588e1c158c9c5c20 Mon Sep 17 00:00:00 2001 From: Jorben Date: Wed, 11 Feb 2026 02:09:48 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(splitter):=20=F0=9F=90=9B=20add=20file?= =?UTF-8?q?=20availability=20pre-check=20and=20upload=20validation=20for?= =?UTF-8?q?=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add FileWaitUtil to retry file access before splitting, addressing "PDF file not found" errors reported on Windows where antivirus software or filesystem sync delays may temporarily lock newly copied files. - Add FileWaitUtil with retry logic (5 attempts, 1s interval) and diagnostic logging for file availability checks - Enhance file upload handlers with directory write permission verification, post-copy/write validation, and empty file detection - Fix path traversal vulnerability in uploadFileContent by sanitizing filename with path.basename() - Fix TaskRepository.create ignoring caller-provided id and status values (status was hardcoded to 0 instead of using passed value) - Update PDFSplitter and ImageSplitter to use shared FileWaitUtil - Remove redundant fs.access call in ImageSplitter after pre-check Co-Authored-By: Claude Opus 4.6 --- .../domain/repositories/TaskRepository.ts | 6 +- .../adapters/split/FileWaitUtil.ts | 91 +++++++++++++++++++ .../adapters/split/ImageSplitter.ts | 7 +- .../adapters/split/PDFSplitter.ts | 5 + .../split/__tests__/ImageSplitter.test.ts | 22 ++++- .../split/__tests__/PDFSplitter.test.ts | 20 +++- .../infrastructure/adapters/split/index.ts | 1 + src/main/ipc/__tests__/handlers.test.ts | 5 +- .../handlers/__tests__/file.handler.test.ts | 4 +- src/main/ipc/handlers/file.handler.ts | 56 +++++++++++- 10 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 src/core/infrastructure/adapters/split/FileWaitUtil.ts diff --git a/src/core/domain/repositories/TaskRepository.ts b/src/core/domain/repositories/TaskRepository.ts index f650dc7..47d2838 100644 --- a/src/core/domain/repositories/TaskRepository.ts +++ b/src/core/domain/repositories/TaskRepository.ts @@ -28,7 +28,7 @@ const findById = async (id: string) => { const create = async (task: Task) => { return await prisma.task.create({ data: { - id: uuidv4(), + id: task?.id || uuidv4(), filename: task?.filename || '', type: task?.type || '', page_range: task?.page_range || '', @@ -36,8 +36,8 @@ const create = async (task: Task) => { provider: task?.provider || 0, model: task?.model || '', model_name: task?.model_name || '', - progress: 0, - status: 0, + progress: task?.progress ?? 0, + status: task?.status ?? 0, } }); }; diff --git a/src/core/infrastructure/adapters/split/FileWaitUtil.ts b/src/core/infrastructure/adapters/split/FileWaitUtil.ts new file mode 100644 index 0000000..56e71e8 --- /dev/null +++ b/src/core/infrastructure/adapters/split/FileWaitUtil.ts @@ -0,0 +1,91 @@ +import { promises as fs } from 'fs'; +import path from 'path'; + +/** + * Utility for waiting on file availability. + * + * On Windows, antivirus software may temporarily lock newly copied files for scanning, + * or filesystem operations may have slight delays. This utility retries file access + * checks before proceeding with actual file processing. + */ +export class FileWaitUtil { + private static readonly MAX_ATTEMPTS = 5; + private static readonly DELAY_MS = 1000; + + /** + * Wait for a file to become available and non-empty. + * + * @param filePath - Full path to the file + * @param uploadsDir - Base uploads directory for diagnostic logging + * @param taskId - Task ID for diagnostic logging + * @param filename - Original filename for error messages + * @param label - Log label (e.g., 'PDFSplitter', 'ImageSplitter') + * @throws Error if the file is not found after all retries + */ + static async waitForFile( + filePath: string, + uploadsDir: string, + taskId: string, + filename: string, + label: string + ): Promise { + for (let attempt = 1; attempt <= this.MAX_ATTEMPTS; attempt++) { + try { + await fs.access(filePath); + const stats = await fs.stat(filePath); + if (stats.size > 0) { + if (attempt > 1) { + console.log( + `[${label}] File became available on attempt ${attempt}: ${filePath}` + ); + } + return; + } + console.warn( + `[${label}] File exists but is empty (attempt ${attempt}/${this.MAX_ATTEMPTS}): ${filePath}` + ); + } catch { + console.warn( + `[${label}] File not accessible (attempt ${attempt}/${this.MAX_ATTEMPTS}): ${filePath}` + ); + } + + if (attempt < this.MAX_ATTEMPTS) { + await new Promise((resolve) => setTimeout(resolve, this.DELAY_MS)); + } + } + + // Log diagnostic info before throwing + await this.logDiagnostics(uploadsDir, taskId, filename, label); + + throw new Error( + `${label === 'PDFSplitter' ? 'PDF' : 'Image'} file not found: ${filename}. The file may have been moved or deleted.` + ); + } + + /** + * Log diagnostic information about the task directory contents. + */ + private static async logDiagnostics( + uploadsDir: string, + taskId: string, + filename: string, + label: string + ): Promise { + const taskDir = path.join(uploadsDir, taskId); + try { + const dirExists = await fs.stat(taskDir).then(() => true).catch(() => false); + if (dirExists) { + const files = await fs.readdir(taskDir); + console.error( + `[${label}] Task directory exists but target file not found. ` + + `Dir: ${taskDir}, Files in dir: [${files.join(', ')}], Expected: ${filename}` + ); + } else { + console.error(`[${label}] Task directory does not exist: ${taskDir}`); + } + } catch (diagError) { + console.error(`[${label}] Diagnostic check failed:`, diagError); + } + } +} diff --git a/src/core/infrastructure/adapters/split/ImageSplitter.ts b/src/core/infrastructure/adapters/split/ImageSplitter.ts index a50dc5f..da58818 100644 --- a/src/core/infrastructure/adapters/split/ImageSplitter.ts +++ b/src/core/infrastructure/adapters/split/ImageSplitter.ts @@ -3,6 +3,7 @@ import path from 'path'; import { ISplitter, SplitResult, PageInfo } from '../../../domain/split/ISplitter.js'; import { Task } from '../../../../shared/types/index.js'; import { ImagePathUtil } from './ImagePathUtil.js'; +import { FileWaitUtil } from './FileWaitUtil.js'; /** * Image splitter implementation for single-page image files. @@ -41,10 +42,10 @@ export class ImageSplitter implements ISplitter { const filename = task.filename; const sourcePath = path.join(this.uploadsDir, taskId, filename); - try { - // Validate source file exists - await fs.access(sourcePath); + // Pre-check: wait for file to become available (handles antivirus scanning delays on Windows) + await FileWaitUtil.waitForFile(sourcePath, this.uploadsDir, taskId, filename, 'ImageSplitter'); + try { // Get file extension (preserve original format) const ext = path.extname(filename).toLowerCase(); if (!ext) { diff --git a/src/core/infrastructure/adapters/split/PDFSplitter.ts b/src/core/infrastructure/adapters/split/PDFSplitter.ts index 6b15398..6653039 100644 --- a/src/core/infrastructure/adapters/split/PDFSplitter.ts +++ b/src/core/infrastructure/adapters/split/PDFSplitter.ts @@ -6,6 +6,7 @@ import { ISplitter, SplitResult, PageInfo } from '../../../domain/split/ISplitte import { Task } from '../../../../shared/types/index.js'; import { PageRangeParser } from '../../../domain/split/PageRangeParser.js'; import { ImagePathUtil } from './ImagePathUtil.js'; +import { FileWaitUtil } from './FileWaitUtil.js'; import { WORKER_CONFIG } from '../../config/worker.config.js'; /** @@ -43,6 +44,10 @@ export class PDFSplitter implements ISplitter { const filename = task.filename; const sourcePath = path.join(this.uploadsDir, taskId, filename); + // Pre-check: verify source file exists before processing + // Retry with short delays to handle antivirus scanning or filesystem sync delays + await FileWaitUtil.waitForFile(sourcePath, this.uploadsDir, taskId, filename, 'PDFSplitter'); + try { // Step 1: Get total page count with retry const totalPages = await this.getPDFPageCountWithRetry(sourcePath); diff --git a/src/core/infrastructure/adapters/split/__tests__/ImageSplitter.test.ts b/src/core/infrastructure/adapters/split/__tests__/ImageSplitter.test.ts index af48ebe..e5c9cb8 100644 --- a/src/core/infrastructure/adapters/split/__tests__/ImageSplitter.test.ts +++ b/src/core/infrastructure/adapters/split/__tests__/ImageSplitter.test.ts @@ -1,13 +1,23 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { ImageSplitter } from '../ImageSplitter.js'; import { ImagePathUtil } from '../ImagePathUtil.js'; +import { FileWaitUtil } from '../FileWaitUtil.js'; import { promises as fs } from 'fs'; import path from 'path'; +// Mock FileWaitUtil +vi.mock('../FileWaitUtil.js', () => ({ + FileWaitUtil: { + waitForFile: vi.fn(), + }, +})); + // Mock fs promises vi.mock('fs', () => ({ promises: { access: vi.fn(), + stat: vi.fn(), + readdir: vi.fn(), mkdir: vi.fn(), copyFile: vi.fn(), rm: vi.fn(), @@ -27,6 +37,9 @@ describe('ImageSplitter', () => { // Reset mocks vi.clearAllMocks(); + + // Setup default mock for FileWaitUtil (file available immediately) + vi.mocked(FileWaitUtil.waitForFile).mockResolvedValue(undefined); }); afterEach(() => { @@ -189,7 +202,10 @@ describe('ImageSplitter', () => { filename: 'missing.jpg', }; - vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT: no such file')); + // Mock FileWaitUtil to reject (file not found) + vi.mocked(FileWaitUtil.waitForFile).mockRejectedValue( + new Error('Image file not found: missing.jpg. The file may have been moved or deleted.') + ); await expect(splitter.split(task)).rejects.toThrow(/Image file not found/); }); @@ -200,7 +216,9 @@ describe('ImageSplitter', () => { filename: 'photo.jpg', }; - vi.mocked(fs.access).mockRejectedValue(new Error('EACCES: permission denied')); + // FileWaitUtil passes, but file copy fails with permission denied + vi.mocked(fs.mkdir).mockResolvedValue(undefined); + vi.mocked(fs.copyFile).mockRejectedValue(new Error('EACCES: permission denied')); await expect(splitter.split(task)).rejects.toThrow(/Permission denied/); }); diff --git a/src/core/infrastructure/adapters/split/__tests__/PDFSplitter.test.ts b/src/core/infrastructure/adapters/split/__tests__/PDFSplitter.test.ts index a3d54ef..fcec091 100644 --- a/src/core/infrastructure/adapters/split/__tests__/PDFSplitter.test.ts +++ b/src/core/infrastructure/adapters/split/__tests__/PDFSplitter.test.ts @@ -16,6 +16,13 @@ vi.mock('pdf-lib', () => ({ }, })); +// Mock FileWaitUtil +vi.mock('../FileWaitUtil.js', () => ({ + FileWaitUtil: { + waitForFile: vi.fn(), + }, +})); + // Mock fs promises vi.mock('fs', () => ({ promises: { @@ -24,11 +31,15 @@ vi.mock('fs', () => ({ rm: vi.fn(), unlink: vi.fn(), readFile: vi.fn(), + access: vi.fn(), + stat: vi.fn(), + readdir: vi.fn(), }, })); import { pdfToPng } from 'pdf-to-png-converter'; import { PDFDocument } from 'pdf-lib'; +import { FileWaitUtil } from '../FileWaitUtil.js'; describe('PDFSplitter', () => { const uploadsDir = '/mock/uploads'; @@ -44,6 +55,9 @@ describe('PDFSplitter', () => { // Reset mocks vi.clearAllMocks(); + // Setup default mock for FileWaitUtil (file available immediately) + vi.mocked(FileWaitUtil.waitForFile).mockResolvedValue(undefined); + // Setup default mocks for PDFDocument vi.mocked(fs.readFile).mockResolvedValue(Buffer.from('mock-pdf-bytes')); vi.mocked(PDFDocument.load).mockResolvedValue({ @@ -191,8 +205,10 @@ describe('PDFSplitter', () => { page_range: '', }; - // Mock fs.readFile to throw file not found error - vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT: no such file')); + // Mock FileWaitUtil to reject (file not found) + vi.mocked(FileWaitUtil.waitForFile).mockRejectedValue( + new Error('PDF file not found: missing.pdf. The file may have been moved or deleted.') + ); await expect(splitter.split(task)).rejects.toThrow(/PDF file not found/); }); diff --git a/src/core/infrastructure/adapters/split/index.ts b/src/core/infrastructure/adapters/split/index.ts index 6ebce57..4a01d65 100644 --- a/src/core/infrastructure/adapters/split/index.ts +++ b/src/core/infrastructure/adapters/split/index.ts @@ -1,5 +1,6 @@ // Infrastructure layer exports - implementations with external dependencies export { ImagePathUtil } from './ImagePathUtil.js'; +export { FileWaitUtil } from './FileWaitUtil.js'; export { PDFSplitter } from './PDFSplitter.js'; export { ImageSplitter } from './ImageSplitter.js'; export { SplitterFactory } from './SplitterFactory.js'; diff --git a/src/main/ipc/__tests__/handlers.test.ts b/src/main/ipc/__tests__/handlers.test.ts index 16715ab..78ad9b1 100644 --- a/src/main/ipc/__tests__/handlers.test.ts +++ b/src/main/ipc/__tests__/handlers.test.ts @@ -152,7 +152,10 @@ vi.mock('fs', () => ({ existsSync: vi.fn(() => true), mkdirSync: vi.fn(), copyFileSync: vi.fn(), - statSync: vi.fn(() => ({ size: 1024 })) + statSync: vi.fn(() => ({ size: 1024 })), + writeFileSync: vi.fn(), + accessSync: vi.fn(), + constants: { W_OK: 2 } } })) diff --git a/src/main/ipc/handlers/__tests__/file.handler.test.ts b/src/main/ipc/handlers/__tests__/file.handler.test.ts index 2a465aa..c23e108 100644 --- a/src/main/ipc/handlers/__tests__/file.handler.test.ts +++ b/src/main/ipc/handlers/__tests__/file.handler.test.ts @@ -19,7 +19,9 @@ const mockFs = { mkdirSync: vi.fn(), copyFileSync: vi.fn(), statSync: vi.fn(), - writeFileSync: vi.fn() + writeFileSync: vi.fn(), + accessSync: vi.fn(), + constants: { W_OK: 2 } } const mockPath = { diff --git a/src/main/ipc/handlers/file.handler.ts b/src/main/ipc/handlers/file.handler.ts index 9d19bf8..07863b9 100644 --- a/src/main/ipc/handlers/file.handler.ts +++ b/src/main/ipc/handlers/file.handler.ts @@ -135,8 +135,9 @@ export function registerFileHandlers() { return { success: false, error: "Task ID and file path are required" }; } - // Check if file exists + // Check if source file exists if (!fs.existsSync(filePath)) { + console.error(`[IPC] file:upload - Source file does not exist: ${filePath}`); return { success: false, error: "File does not exist" }; } @@ -148,16 +149,38 @@ export function registerFileHandlers() { fs.mkdirSync(uploadDir, { recursive: true }); } + // Verify directory was created and is writable + try { + fs.accessSync(uploadDir, fs.constants.W_OK); + } catch { + console.error(`[IPC] file:upload - Upload directory is not writable: ${uploadDir}`); + return { success: false, error: `Upload directory is not writable: ${uploadDir}` }; + } + // Get file info const fileName = path.basename(filePath); const destPath = path.join(uploadDir, fileName); // Copy file + console.log(`[IPC] file:upload - Copying file: ${filePath} -> ${destPath}`); fs.copyFileSync(filePath, destPath); + // Verify copied file exists and has content + if (!fs.existsSync(destPath)) { + console.error(`[IPC] file:upload - File copy verification failed, destination not found: ${destPath}`); + return { success: false, error: "File copy failed: destination file not found after copy" }; + } + // Get file stats const stats = fs.statSync(destPath); + if (stats.size === 0) { + console.error(`[IPC] file:upload - Copied file is empty: ${destPath}`); + return { success: false, error: "File copy failed: destination file is empty" }; + } + + console.log(`[IPC] file:upload - File copied successfully: ${destPath} (${stats.size} bytes)`); + const fileInfo = { originalName: fileName, savedName: fileName, @@ -250,19 +273,42 @@ export function registerFileHandlers() { fs.mkdirSync(uploadDir, { recursive: true }); } - // Build destination path - const destPath = path.join(uploadDir, fileName); + // Verify directory was created and is writable + try { + fs.accessSync(uploadDir, fs.constants.W_OK); + } catch { + console.error(`[IPC] file:uploadFileContent - Upload directory is not writable: ${uploadDir}`); + return { success: false, error: `Upload directory is not writable: ${uploadDir}` }; + } + + // Sanitize filename to prevent path traversal + const safeName = path.basename(fileName); + const destPath = path.join(uploadDir, safeName); // Convert ArrayBuffer to Buffer and write to file const buffer = Buffer.from(fileBuffer); + console.log(`[IPC] file:uploadFileContent - Writing file: ${destPath} (${buffer.length} bytes)`); fs.writeFileSync(destPath, buffer); + // Verify written file exists and has content + if (!fs.existsSync(destPath)) { + console.error(`[IPC] file:uploadFileContent - File write verification failed, not found: ${destPath}`); + return { success: false, error: "File write failed: file not found after write" }; + } + // Get file stats const stats = fs.statSync(destPath); + if (stats.size === 0) { + console.error(`[IPC] file:uploadFileContent - Written file is empty: ${destPath}`); + return { success: false, error: "File write failed: file is empty" }; + } + + console.log(`[IPC] file:uploadFileContent - File written successfully: ${destPath} (${stats.size} bytes)`); + const fileInfo = { - originalName: fileName, - savedName: fileName, + originalName: safeName, + savedName: safeName, path: destPath, size: stats.size, taskId: taskId, From d387a6a02318cd2031a0173d97e0e2e7da75bb9e Mon Sep 17 00:00:00 2001 From: Jorben Date: Wed, 11 Feb 2026 16:26:00 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(ipc):=20=F0=9F=94=A5=20remove=20un?= =?UTF-8?q?used=20uploadMultiple=20handler=20and=20related=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The uploadMultiple IPC handler had no callers in the renderer process (dead code). Removing it reduces maintenance burden and avoids the inconsistency of having an unguarded upload path alongside the hardened upload and uploadFileContent handlers. Co-Authored-By: Claude Opus 4.6 --- docs/IPC_API.md | 1 - src/main/ipc/__tests__/handlers.test.ts | 1 - .../handlers/__tests__/file.handler.test.ts | 47 --------------- src/main/ipc/handlers/file.handler.ts | 57 ------------------- src/preload/electron.d.ts | 1 - src/preload/index.ts | 2 - src/renderer/electron.d.ts | 4 -- src/shared/ipc/channels.ts | 1 - tests/helpers/window-api-mock.ts | 3 +- tests/setup.renderer.ts | 1 - 10 files changed, 1 insertion(+), 117 deletions(-) diff --git a/docs/IPC_API.md b/docs/IPC_API.md index da3274d..0d9ee15 100644 --- a/docs/IPC_API.md +++ b/docs/IPC_API.md @@ -80,7 +80,6 @@ Handle file operations (upload, download, dialog). |--------|-------------| | `window.api.file.selectDialog()` | Open file selection dialog | | `window.api.file.upload(taskId, filePath)` | Upload file | -| `window.api.file.uploadMultiple(taskId, filePaths[])` | Upload multiple files | | `window.api.file.uploadFileContent(taskId, fileName, fileBuffer)` | Upload file content as ArrayBuffer | | `window.api.file.getImagePath(taskId, page)` | Get image path for a page | | `window.api.file.downloadMarkdown(taskId)` | Download merged markdown file | diff --git a/src/main/ipc/__tests__/handlers.test.ts b/src/main/ipc/__tests__/handlers.test.ts index 78ad9b1..c4cf866 100644 --- a/src/main/ipc/__tests__/handlers.test.ts +++ b/src/main/ipc/__tests__/handlers.test.ts @@ -133,7 +133,6 @@ vi.mock('../../../shared/ipc/channels.js', () => ({ DOWNLOAD_MARKDOWN: 'file:downloadMarkdown', SELECT_DIALOG: 'file:selectDialog', UPLOAD: 'file:upload', - UPLOAD_MULTIPLE: 'file:uploadMultiple', UPLOAD_FILE_CONTENT: 'file:uploadFileContent', }, COMPLETION: { diff --git a/src/main/ipc/handlers/__tests__/file.handler.test.ts b/src/main/ipc/handlers/__tests__/file.handler.test.ts index c23e108..757ca48 100644 --- a/src/main/ipc/handlers/__tests__/file.handler.test.ts +++ b/src/main/ipc/handlers/__tests__/file.handler.test.ts @@ -68,7 +68,6 @@ vi.mock('../../../../shared/ipc/channels.js', () => ({ DOWNLOAD_MARKDOWN: 'file:downloadMarkdown', SELECT_DIALOG: 'file:selectDialog', UPLOAD: 'file:upload', - UPLOAD_MULTIPLE: 'file:uploadMultiple', UPLOAD_FILE_CONTENT: 'file:uploadFileContent' } } @@ -329,52 +328,6 @@ describe('File Handler', () => { }) }) - describe('file:uploadMultiple', () => { - it('should upload multiple files successfully', async () => { - mockFs.existsSync.mockReturnValue(true) - - const handler = handlers.get('file:uploadMultiple') - const result = await handler!({}, 'task-123', ['/file1.pdf', '/file2.pdf']) - - expect(result.success).toBe(true) - expect(result.data.files).toHaveLength(2) - expect(mockFs.copyFileSync).toHaveBeenCalledTimes(2) - }) - - it('should return error when taskId is missing', async () => { - const handler = handlers.get('file:uploadMultiple') - const result = await handler!({}, '', ['/file.pdf']) - - expect(result).toEqual({ - success: false, - error: 'Task ID and file path list are required' - }) - }) - - it('should return error when filePaths is empty', async () => { - const handler = handlers.get('file:uploadMultiple') - const result = await handler!({}, 'task-1', []) - - expect(result).toEqual({ - success: false, - error: 'Task ID and file path list are required' - }) - }) - - it('should skip non-existent files', async () => { - mockFs.existsSync - .mockReturnValueOnce(true) // file1 exists - .mockReturnValueOnce(true) // upload dir check - .mockReturnValueOnce(false) // file2 doesn't exist - - const handler = handlers.get('file:uploadMultiple') - const result = await handler!({}, 'task-1', ['/file1.pdf', '/non-existent.pdf']) - - expect(result.success).toBe(true) - expect(result.data.files).toHaveLength(1) - }) - }) - describe('file:uploadFileContent', () => { it('should save file content successfully', async () => { const fileBuffer = new ArrayBuffer(8) diff --git a/src/main/ipc/handlers/file.handler.ts b/src/main/ipc/handlers/file.handler.ts index 07863b9..7f54305 100644 --- a/src/main/ipc/handlers/file.handler.ts +++ b/src/main/ipc/handlers/file.handler.ts @@ -197,63 +197,6 @@ export function registerFileHandlers() { } ); - /** - * Multiple file upload - */ - ipcMain.handle( - IPC_CHANNELS.FILE.UPLOAD_MULTIPLE, - async (_, taskId: string, filePaths: string[]): Promise => { - try { - if (!taskId || !Array.isArray(filePaths) || filePaths.length === 0) { - return { success: false, error: "Task ID and file path list are required" }; - } - - const uploadResults = []; - - for (const filePath of filePaths) { - // Check if file exists - if (!fs.existsSync(filePath)) { - continue; - } - - const baseUploadDir = fileLogic.getUploadDir(); - const uploadDir = path.join(baseUploadDir, taskId); - - // Ensure directory exists - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); - } - - // Get file info - const fileName = path.basename(filePath); - const destPath = path.join(uploadDir, fileName); - - // Copy file - fs.copyFileSync(filePath, destPath); - - // Get file stats - const stats = fs.statSync(destPath); - - uploadResults.push({ - originalName: fileName, - savedName: fileName, - path: destPath, - size: stats.size, - taskId: taskId, - }); - } - - return { - success: true, - data: { message: "Files uploaded successfully", files: uploadResults }, - }; - } catch (error: any) { - console.error("[IPC] file:uploadMultiple error:", error); - return { success: false, error: error.message }; - } - } - ); - /** * File content upload (for drag and drop) */ diff --git a/src/preload/electron.d.ts b/src/preload/electron.d.ts index 6744aca..4e60f12 100644 --- a/src/preload/electron.d.ts +++ b/src/preload/electron.d.ts @@ -72,7 +72,6 @@ interface WindowAPI { file: { selectDialog: () => Promise; upload: (taskId: string, filePath: string) => Promise; - uploadMultiple: (taskId: string, filePaths: string[]) => Promise; uploadFileContent: (taskId: string, fileName: string, fileBuffer: ArrayBuffer) => Promise; getImagePath: (taskId: string, page: number) => Promise; downloadMarkdown: (taskId: string) => Promise; diff --git a/src/preload/index.ts b/src/preload/index.ts index 0067fb4..5714d95 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -58,8 +58,6 @@ contextBridge.exposeInMainWorld("api", { selectDialog: () => ipcRenderer.invoke("file:selectDialog"), upload: (taskId: string, filePath: string) => ipcRenderer.invoke("file:upload", taskId, filePath), - uploadMultiple: (taskId: string, filePaths: string[]) => - ipcRenderer.invoke("file:uploadMultiple", taskId, filePaths), uploadFileContent: (taskId: string, fileName: string, fileBuffer: ArrayBuffer) => ipcRenderer.invoke("file:uploadFileContent", taskId, fileName, fileBuffer), getImagePath: (taskId: string, page: number) => diff --git a/src/renderer/electron.d.ts b/src/renderer/electron.d.ts index 4037933..a7b917d 100644 --- a/src/renderer/electron.d.ts +++ b/src/renderer/electron.d.ts @@ -202,10 +202,6 @@ interface ElectronAPI { taskId: string, filePath: string, ) => Promise>; - uploadMultiple: ( - taskId: string, - filePaths: string[], - ) => Promise>; getImagePath: ( taskId: string, page: number, diff --git a/src/shared/ipc/channels.ts b/src/shared/ipc/channels.ts index ed4f0eb..f889cbf 100644 --- a/src/shared/ipc/channels.ts +++ b/src/shared/ipc/channels.ts @@ -48,7 +48,6 @@ export const IPC_CHANNELS = { DOWNLOAD_MARKDOWN: 'file:downloadMarkdown', SELECT_DIALOG: 'file:selectDialog', UPLOAD: 'file:upload', - UPLOAD_MULTIPLE: 'file:uploadMultiple', UPLOAD_FILE_CONTENT: 'file:uploadFileContent', }, diff --git a/tests/helpers/window-api-mock.ts b/tests/helpers/window-api-mock.ts index 571bf56..b86a831 100644 --- a/tests/helpers/window-api-mock.ts +++ b/tests/helpers/window-api-mock.ts @@ -23,8 +23,7 @@ export const createMockWindowApi = () => ({ }, file: { selectDialog: vi.fn().mockResolvedValue({ success: true, data: ['/mock/file.pdf'] }), - upload: vi.fn().mockResolvedValue({ success: true, data: { path: '/mock/upload.pdf' } }), - uploadMultiple: vi.fn().mockResolvedValue({ success: true, data: [] }) + upload: vi.fn().mockResolvedValue({ success: true, data: { path: '/mock/upload.pdf' } }) }, completion: { markImagedown: vi.fn().mockResolvedValue({ success: true }), diff --git a/tests/setup.renderer.ts b/tests/setup.renderer.ts index 0329fff..8809d10 100644 --- a/tests/setup.renderer.ts +++ b/tests/setup.renderer.ts @@ -33,7 +33,6 @@ const mockWindowApi = { file: { selectDialog: vi.fn(), upload: vi.fn(), - uploadMultiple: vi.fn(), downloadMarkdown: vi.fn() }, completion: {