Files
SubMiner/src/core/services/yomitan-extension-copy.ts

120 lines
3.6 KiB
TypeScript

import { createHash } from 'node:crypto';
import * as fs from 'fs';
import * as path from 'path';
const YOMITAN_SYNC_SCRIPT_PATHS = [
path.join('js', 'app', 'popup.js'),
path.join('js', 'display', 'popup-main.js'),
path.join('js', 'display', 'display.js'),
path.join('js', 'display', 'display-audio.js'),
];
function readManifestVersion(manifestPath: string): string | null {
try {
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as { version?: unknown };
return typeof parsed.version === 'string' ? parsed.version : null;
} catch {
return null;
}
}
export function hashDirectoryContents(dirPath: string): string | null {
try {
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
return null;
}
const hash = createHash('sha256');
const queue = [''];
while (queue.length > 0) {
const relativeDir = queue.shift()!;
const absoluteDir = path.join(dirPath, relativeDir);
const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
entries.sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
const relativePath = path.join(relativeDir, entry.name);
const normalizedRelativePath = relativePath.split(path.sep).join('/');
hash.update(normalizedRelativePath);
if (entry.isDirectory()) {
queue.push(relativePath);
continue;
}
if (!entry.isFile()) {
continue;
}
hash.update(fs.readFileSync(path.join(dirPath, relativePath)));
}
}
return hash.digest('hex');
} catch {
return null;
}
}
function areFilesEqual(sourcePath: string, targetPath: string): boolean {
if (!fs.existsSync(sourcePath) || !fs.existsSync(targetPath)) return false;
try {
return fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath));
} catch {
return false;
}
}
export function shouldCopyYomitanExtension(sourceDir: string, targetDir: string): boolean {
if (!fs.existsSync(targetDir)) {
return true;
}
const sourceManifest = path.join(sourceDir, 'manifest.json');
const targetManifest = path.join(targetDir, 'manifest.json');
if (!fs.existsSync(sourceManifest) || !fs.existsSync(targetManifest)) {
return true;
}
const sourceVersion = readManifestVersion(sourceManifest);
const targetVersion = readManifestVersion(targetManifest);
if (sourceVersion === null || targetVersion === null || sourceVersion !== targetVersion) {
return true;
}
for (const relativePath of YOMITAN_SYNC_SCRIPT_PATHS) {
if (!areFilesEqual(path.join(sourceDir, relativePath), path.join(targetDir, relativePath))) {
return true;
}
}
const sourceHash = hashDirectoryContents(sourceDir);
const targetHash = hashDirectoryContents(targetDir);
return sourceHash === null || targetHash === null || sourceHash !== targetHash;
}
export function ensureExtensionCopy(
sourceDir: string,
userDataPath: string,
): {
targetDir: string;
copied: boolean;
} {
if (process.platform === 'win32') {
return { targetDir: sourceDir, copied: false };
}
const extensionsRoot = path.join(userDataPath, 'extensions');
const targetDir = path.join(extensionsRoot, 'yomitan');
let shouldCopy = !fs.existsSync(targetDir);
if (!shouldCopy) {
shouldCopy = hashDirectoryContents(sourceDir) !== hashDirectoryContents(targetDir);
}
if (shouldCopy) {
fs.mkdirSync(extensionsRoot, { recursive: true });
fs.rmSync(targetDir, { recursive: true, force: true });
fs.cpSync(sourceDir, targetDir, { recursive: true });
}
return { targetDir, copied: shouldCopy };
}