(event, commandData)
| 9064 | console.error('Error extracting model from zip:', error); |
| 9065 | throw error; |
| 9066 | } |
| 9067 | }); |
| 9068 | |
| 9069 | // Renderer cleanup for extract temps (loadModel / preview) |
| 9070 | ipcMain.handle('delete-temp-file', async (event, filePath) => { |
| 9071 | try { |
| 9072 | return await cleanupExtractTempFile(filePath); |
| 9073 | } catch (error) { |
| 9074 | console.warn('delete-temp-file failed:', error.message); |
| 9075 | return false; |
| 9076 | } |
| 9077 | }); |
| 9078 | ipcHandlerRegistry.set('delete-temp-file', async (event, filePath) => { |
| 9079 | return await cleanupExtractTempFile(filePath); |
| 9080 | }); |
| 9081 | |
| 9082 | // Add handler to extract zip archive |
| 9083 | ipcMain.handle('extract-zip-archive', async (event, filePath, destinationPath) => { |
| 9084 | try { |
| 9085 | const pathInfo = parseZipPath(filePath); |
| 9086 | if (!pathInfo.isZipEntry) { |
| 9087 | throw new Error('Not a zip entry'); |
| 9088 | } |
| 9089 | |
| 9090 | const StreamZip = require('node-stream-zip'); |
| 9091 | const zip = new StreamZip.async({ file: pathInfo.zipPath }); |
| 9092 | |
| 9093 | // Extract the specific entry |
| 9094 | const entryData = await zip.entryData(pathInfo.entryPath); |
| 9095 | await zip.close(); |
| 9096 | |
| 9097 | // Create destination path preserving directory structure |
| 9098 | const destPath = path.join(destinationPath, pathInfo.entryPath); |
| 9099 | const destDir = path.dirname(destPath); |
| 9100 | await fs.promises.mkdir(destDir, { recursive: true }); |
| 9101 | await fs.promises.writeFile(destPath, entryData); |
| 9102 | |
| 9103 | return destPath; |
| 9104 | } catch (error) { |
| 9105 | console.error('Error extracting zip archive:', error); |
| 9106 | throw error; |
| 9107 | } |
| 9108 | }); |
| 9109 | |
| 9110 | // Add a new IPC handler for getting duplicates |
| 9111 | const getDuplicatesHandler = async (event, includeZip = false) => { |
| 9112 | const maxRetries = isServerMode && isGeneratingHashes ? 5 : 1; |
| 9113 | const retryDelayMs = 150; |
| 9114 | let lastError; |
| 9115 | for (let attempt = 0; attempt < maxRetries; attempt++) { |
| 9116 | try { |
| 9117 | // Only fetch rows whose hash has 2+ distinct paths (avoids loading every unique model into memory). |
| 9118 | // Zip entries use "archive::entry" paths — exclude them unless includeZip is true. |
| 9119 | const zipClause = includeZip ? '' : " AND instr(filePath, '::') = 0"; |
| 9120 | const rows = db.prepare(` |
| 9121 | SELECT filePath, fileName, hash, size |
| 9122 | FROM models |
| 9123 | WHERE hash IS NOT NULL |
nothing calls this directly
no test coverage detected