Files
aklive2d/libs/cf_pages.js
2024-08-15 11:41:47 +08:00

149 lines
5.2 KiB
JavaScript

/* eslint-disable no-undef */
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import pThrottle from 'p-throttle';
import { spawnSync } from 'child_process';
import { readdirSync, fileTypeSync, writeSync, mkdir, exists } from './file.js';
export default class CFPages {
#uploadPath = path.join(__projectRoot, __config.folder.operator_data);
#downloadPath = path.join(__projectRoot, __config.folder.operator_data);
#gitignorePath = path.join(__projectRoot, '.gitignore');
async upload() {
const tree = await this.#generateDirTree(this.#uploadPath);
writeSync(JSON.stringify(tree, null), path.join(this.#uploadPath, 'index.json'));
const wrangler = spawnSync('pnpm', ['wrangler', 'pages', 'deploy', this.#uploadPath]);
console.log('error', wrangler.error);
console.log('stdout ', wrangler.stdout.toString());
console.log('stderr ', wrangler.stderr.toString());
}
async download() {
const indexFile = `${__config.akassets_url}/index.json`
const resp = await fetch(indexFile);
const data = await resp.json();
mkdir(this.#downloadPath);
let list = data.children.flatMap((child) => {
return this.#generateDownloadList(child, this.#downloadPath);
});
const throttle = pThrottle({
limit: 10,
interval: 500
})
while (list.length > 0) {
const retry = [];
await Promise.all(list.map(throttle(async (file) => {
let isExists = false;
let suppressedPath = file.target.replace(this.#downloadPath, '');
if (exists(file.target)) {
const hash = await this.#getHash(file.target);
if (hash === file.hash) {
isExists = true
console.log("File already exists and hash matches:", suppressedPath);
}
}
if (!isExists) {
await fetch(file.url)
.then(response => {
return response.arrayBuffer();
})
.then(arraybuffer => {
console.log("Writing to file:", suppressedPath);
writeSync(Buffer.from(arraybuffer), file.target);
return this.#getHash(file.target)
})
.then(hash => {
if (hash !== file.hash) {
throw new Error(`Hash mismatch`);
}
})
.catch(err => {
console.log(`Found error for ${suppressedPath}:`, err)
retry.push(file);
});
}
})));
list = retry;
}
}
#generateDownloadList(data, baseDir, baseUrl = "") {
if (data.type === 'dir') {
let list = [];
const curDir = path.join(baseDir, data.name);
mkdir(curDir);
if (data.children.length > 0) {
for (const child of data.children) {
const filesToDownload = this.#generateDownloadList(child, curDir, baseUrl + data.name + '/');
if (filesToDownload) {
list = [...list, ...filesToDownload];
}
}
return list;
}
} else {
return [{
url: `${__config.akassets_url}/${baseUrl + data.name.replace('#', '%23')}`,
target: path.join(baseDir, data.name),
hash: data.hash
}]
}
return null;
}
async #generateDirTree(dir) {
const files = readdirSync(dir);
let tree = {
name: path.basename(dir),
type: 'dir',
children: []
};
for (const file of files) {
if (this.#isToIgnore(file)) {
continue;
}
const filePath = path.join(dir, file);
const dirType = fileTypeSync(filePath);
if (dirType === 'dir') {
tree.children.push(await this.#generateDirTree(filePath))
} else {
tree.children.push({
name: file,
type: 'file',
hash: await this.#getHash(filePath)
});
}
}
if (tree.children.length === 0) {
return null;
}
return tree
}
// TODO
#isToIgnore(file) {
switch (file) {
case '.DS_Store':
case 'index.json':
return true;
default:
return false;
}
}
#getHash(filePath) {
return new Promise((res, rej) => {
const hash = crypto.createHash('md5');
const rStream = fs.createReadStream(filePath);
rStream.on('data', (data) => {
hash.update(data);
});
rStream.on('end', () => {
res(hash.digest('hex'));
});
})
}
}