Files
photo-renamer/src/main/ipc/files.js
T

132 lines
4.1 KiB
JavaScript

const { ipcMain, app } = require('electron');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const {
fileExists,
listImages,
moveFileWithCopyFallback,
readImageAsDataUrl,
} = require('./file-utils');
const logger = require('../logger');
function register() {
ipcMain.handle('list-images', async (_event, folderPath) => {
logger.log('ipc:files', 'list-images requested', { folderPath });
try {
const files = listImages(folderPath);
logger.log('ipc:files', 'list-images result', { folderPath, count: files.length });
return files;
} catch (e) {
logger.error('ipc:files', `list-images failed: ${folderPath}`, e);
return [];
}
});
ipcMain.handle('read-image', async (_event, filePath) => {
try {
return readImageAsDataUrl(filePath);
} catch (e) {
return null;
}
});
ipcMain.handle('check-files-exist', (_event, folderPath, names) => {
return names.filter(name => fileExists(path.join(folderPath, name)));
});
ipcMain.handle('move-files', async (_event, { targetFolder, files }) => {
const errors = [];
const moved = [];
for (const file of files) {
const destName = file.newName || file.name;
const dest = path.join(targetFolder, destName);
try {
moveFileWithCopyFallback(file.path, dest);
moved.push({ src: file.path, dest, name: destName });
} catch (e) {
errors.push(`${file.name}: ${e.message}`);
}
}
return { moved, errors };
});
// 삭제 → 스테이징 폴더로 이동 (undo 가능)
ipcMain.handle('stage-delete-files', async (_event, filePaths) => {
const batchDir = path.join(app.getPath('userData'), 'deleted_staging', Date.now().toString());
fs.mkdirSync(batchDir, { recursive: true });
const staged = [];
const errors = [];
for (const fp of filePaths) {
const name = path.basename(fp);
const dest = path.join(batchDir, name);
try {
moveFileWithCopyFallback(fp, dest);
staged.push({ name, originalPath: fp, stagingPath: dest });
} catch (e) {
errors.push(`${name}: ${e.message}`);
}
}
if (!staged.length) try { fs.rmdirSync(batchDir); } catch {}
return { staged, errors };
});
// 스테이징 파일 원래 위치로 복원 (undo delete)
ipcMain.handle('restore-staged-files', async (_event, deletedEntries) => {
const restored = [];
const errors = [];
for (const { name, originalPath, stagingPath } of deletedEntries) {
if (fs.existsSync(originalPath)) {
errors.push(`${name}: 같은 이름의 파일이 이미 존재합니다`);
continue;
}
try {
fs.mkdirSync(path.dirname(originalPath), { recursive: true });
moveFileWithCopyFallback(stagingPath, originalPath);
restored.push(originalPath);
} catch (e) {
errors.push(`${name}: ${e.message}`);
}
}
return { restored, errors };
});
ipcMain.handle('rename-files', async (_event, { folderPath, files, prefix, digits, postfix = '' }) => {
const errors = [];
const pad = (n) => String(n + 1).padStart(digits, '0');
const suffix = postfix ? `_${postfix}` : '';
// 1단계: 임시 이름으로 이동 (충돌 방지)
const tmpMap = [];
for (let i = 0; i < files.length; i++) {
const src = files[i].path;
const ext = path.extname(files[i].name).toLowerCase();
const tmp = path.join(folderPath, `__tmp_${crypto.randomUUID()}${ext}`);
try {
fs.renameSync(src, tmp);
tmpMap.push({ tmp, ext, idx: i });
} catch (e) {
errors.push(`${files[i].name}: ${e.message}`);
}
}
// 2단계: 최종 이름으로 이동
const renamed = [];
for (const { tmp, ext, idx } of tmpMap) {
const newName = `${prefix}_${pad(idx)}${suffix}${ext}`;
const dest = path.join(folderPath, newName);
try {
fs.renameSync(tmp, dest);
renamed.push({ oldPath: tmp, newPath: dest, newName });
} catch (e) {
errors.push(`${newName}: ${e.message}`);
}
}
return { renamed, errors };
});
}
module.exports = { register };