mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-27 12:55:20 -07:00
feat: add auto update support
This commit is contained in:
@@ -9,6 +9,13 @@ const YOMITAN_SYNC_SCRIPT_PATHS = [
|
||||
path.join('js', 'display', 'display-audio.js'),
|
||||
];
|
||||
|
||||
type ExtensionCopyResult = {
|
||||
targetDir: string;
|
||||
copied: boolean;
|
||||
};
|
||||
|
||||
const asyncExtensionCopyInFlight = new Map<string, Promise<ExtensionCopyResult>>();
|
||||
|
||||
function readManifestVersion(manifestPath: string): string | null {
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as { version?: unknown };
|
||||
@@ -18,6 +25,15 @@ function readManifestVersion(manifestPath: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
async function pathExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.promises.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function hashDirectoryContents(dirPath: string): string | null {
|
||||
try {
|
||||
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
||||
@@ -53,6 +69,42 @@ export function hashDirectoryContents(dirPath: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export async function hashDirectoryContentsAsync(dirPath: string): Promise<string | null> {
|
||||
try {
|
||||
const dirStat = await fs.promises.stat(dirPath);
|
||||
if (!dirStat.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 = await fs.promises.readdir(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(await fs.promises.readFile(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 {
|
||||
@@ -93,10 +145,7 @@ export function shouldCopyYomitanExtension(sourceDir: string, targetDir: string)
|
||||
export function ensureExtensionCopy(
|
||||
sourceDir: string,
|
||||
userDataPath: string,
|
||||
): {
|
||||
targetDir: string;
|
||||
copied: boolean;
|
||||
} {
|
||||
): ExtensionCopyResult {
|
||||
if (process.platform === 'win32') {
|
||||
return { targetDir: sourceDir, copied: false };
|
||||
}
|
||||
@@ -117,3 +166,53 @@ export function ensureExtensionCopy(
|
||||
|
||||
return { targetDir, copied: shouldCopy };
|
||||
}
|
||||
|
||||
export async function ensureExtensionCopyAsync(
|
||||
sourceDir: string,
|
||||
userDataPath: string,
|
||||
): Promise<ExtensionCopyResult> {
|
||||
if (process.platform === 'win32') {
|
||||
return { targetDir: sourceDir, copied: false };
|
||||
}
|
||||
|
||||
const extensionsRoot = path.join(userDataPath, 'extensions');
|
||||
const targetDir = path.join(extensionsRoot, 'yomitan');
|
||||
const inFlightKey = path.resolve(targetDir);
|
||||
const inFlight = asyncExtensionCopyInFlight.get(inFlightKey);
|
||||
if (inFlight) {
|
||||
return await inFlight;
|
||||
}
|
||||
|
||||
const copyPromise = ensureExtensionCopyAsyncInternal(sourceDir, extensionsRoot, targetDir);
|
||||
asyncExtensionCopyInFlight.set(inFlightKey, copyPromise);
|
||||
try {
|
||||
return await copyPromise;
|
||||
} finally {
|
||||
if (asyncExtensionCopyInFlight.get(inFlightKey) === copyPromise) {
|
||||
asyncExtensionCopyInFlight.delete(inFlightKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureExtensionCopyAsyncInternal(
|
||||
sourceDir: string,
|
||||
extensionsRoot: string,
|
||||
targetDir: string,
|
||||
): Promise<ExtensionCopyResult> {
|
||||
let shouldCopy = !(await pathExists(targetDir));
|
||||
if (!shouldCopy) {
|
||||
const [sourceHash, targetHash] = await Promise.all([
|
||||
hashDirectoryContentsAsync(sourceDir),
|
||||
hashDirectoryContentsAsync(targetDir),
|
||||
]);
|
||||
shouldCopy = sourceHash !== targetHash;
|
||||
}
|
||||
|
||||
if (shouldCopy) {
|
||||
await fs.promises.mkdir(extensionsRoot, { recursive: true });
|
||||
await fs.promises.rm(targetDir, { recursive: true, force: true });
|
||||
await fs.promises.cp(sourceDir, targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
return { targetDir, copied: shouldCopy };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user