Update the API of downloading assets.

This commit is contained in:
Tipx-L 2024-04-15 23:45:02 -07:00
parent 0ec3f9e6be
commit dd923ea3e6
2 changed files with 142 additions and 91 deletions

View File

@ -1,4 +1,4 @@
import { lib, ui, game } from "../../noname.js"; import { ui, game } from "../../noname.js";
// https://github.com/libccy/noname/archive/refs/tags/v1.10.10.zip // https://github.com/libccy/noname/archive/refs/tags/v1.10.10.zip
@ -386,7 +386,7 @@ export async function request(url, onProgress, options = {}) {
try { try {
// @ts-ignore // @ts-ignore
filename = response.headers.get("Content-Disposition").split(";")[1].split("=")[1]; filename = response.headers.get("Content-Disposition").split(";")[1].split("=")[1];
} catch {} } catch { /* empty */ }
let receivedBytes = 0; let receivedBytes = 0;
let chunks = []; let chunks = [];
@ -516,3 +516,73 @@ export function createProgress(title, max, fileName, value) {
}; };
return parent; return parent;
} }
/**
* Retrieves the latest version tag from a GitHub repository, excluding a specific tag.
* This function fetches the list of tags from the GitHub repository specified by
* the owner and repository name, then returns the latest tag name that is not v1998.
* @param {string} owner - The username or organization name on GitHub that owns the repository.
* @param {string} repo - The name of the repository from which to fetch tags.
* @returns {Promise<string>} A promise that resolves with the name of the latest version tag,
* or rejects with an error if the operation fails.
* @throws {Error} Will throw an error if the fetch operation fails or if no valid tags are found.
*/
export async function getLatestVersionFromGitHub(owner = 'libccy', repo = 'noname') {
if (!localStorage.getItem('noname_authorization')) await gainAuthorization();
const tags = await getRepoTags({
username: owner,
repository: repo
});
for (const tag of tags) {
const tagName = tag.name;
if (tagName !== 'v1998') return tagName;
}
throw new Error('No valid tags found in the repository');
}
/**
* Fetches trees from a GitHub repository within specified directories.
* @param {string[]} directories - The list of directories to fetch the trees from.
* @param {string} version - The version or branch to fetch the trees from.
* @param {string} owner - The owner of the GitHub repository.
* @param {string} repo - The name of the GitHub repository.
* @returns {Promise<{
* path: string;
* mode: string;
* type: "blob" | "tree";
* sha: string;
* size: number;
* url: string;
* }[][]>} A promise that resolves with trees from the specified directories.
* @throws {Error} Will throw an error if unable to fetch the repository tree from GitHub.
*/
export async function getTreesFromGithub(directories, version, owner = 'libccy', repo = 'noname') {
if (!localStorage.getItem('noname_authorization')) await gainAuthorization();
const treesResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${version}?recursive=1`, {
headers: defaultHeaders
});
if (!treesResponse.ok) throw new Error(`Failed to fetch the GitHub repository tree: HTTP status ${treesResponse.status}`);
/**
* @type {{
* sha: string;
* url: string;
* tree: {
* path: string;
* mode: string;
* type: "blob" | "tree";
* sha: string;
* size: number;
* url: string;
* }[];
* truncated: boolean;
* }}
*/
const trees = await treesResponse.json();
const tree = trees.tree;
return directories.map(directory => tree.filter(({ type, path }) => type === 'blob' && path.startsWith(directory)));
}

View File

@ -1,11 +1,5 @@
import { import {
menuContainer, menuContainer,
popupContainer,
updateActive,
setUpdateActive,
updateActiveCard,
setUpdateActiveCard,
menux,
menuxpages, menuxpages,
menuUpdates, menuUpdates,
openMenu, openMenu,
@ -22,11 +16,12 @@ import {
checkVersion, checkVersion,
getRepoTags, getRepoTags,
getRepoTagDescription, getRepoTagDescription,
getRepoFilesList,
flattenRepositoryFiles, flattenRepositoryFiles,
request, request,
createProgress, createProgress,
gainAuthorization, gainAuthorization,
getLatestVersionFromGitHub,
getTreesFromGithub
} from "../../../../library/update.js"; } from "../../../../library/update.js";
export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) { export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) {
@ -338,7 +333,7 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
.then(() => { .then(() => {
cp.exec( cp.exec(
`start /b ${__dirname}\\noname-server.exe -platform=electron`, `start /b ${__dirname}\\noname-server.exe -platform=electron`,
() => {} () => { }
); );
function loadURL() { function loadURL() {
let myAbortController = let myAbortController =
@ -410,42 +405,41 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
} }
checkAssetButton.innerHTML = "正在检查更新"; checkAssetButton.innerHTML = "正在检查更新";
checkAssetButton.disabled = true; checkAssetButton.disabled = true;
function refresh() { function refresh() {
checkAssetButton.innerHTML = "检查素材更新"; checkAssetButton.innerHTML = "检查素材更新";
checkAssetButton.disabled = false; checkAssetButton.disabled = false;
} }
const assetDirs = [];
if (lib.config.asset_font) { const assetDirectories = [];
assetDirs.push("font"); if (lib.config.asset_font) assetDirectories.push('font');
} if (lib.config.asset_audio) assetDirectories.push('audio');
if (lib.config.asset_audio) { if (lib.config.asset_image) assetDirectories.push('image');
assetDirs.push("audio"); const version = await getLatestVersionFromGitHub();
} const files = await getTreesFromGithub(assetDirectories, version);
if (lib.config.asset_image) {
assetDirs.push("image"); assetDirectories.forEach((assetDirectory, index) => {
}
const files = await Promise.all(assetDirs.map((dir) => flattenRepositoryFiles(dir)));
assetDirs.forEach((value, index) => {
const arr = files[index]; const arr = files[index];
const size = arr.reduce((previous, current) => { const size = arr.reduce((previous, current) => {
return previous + current.size; return previous + current.size;
}, 0); }, 0);
game.saveConfig(`asset_${value}_size`, parseSize(size)); game.saveConfig(`asset_${assetDirectory}_size`, parseSize(size));
}); });
/** /**
* @param { any[] } arr * @template T
* @param { Function } predicate * @param { T[] } arr
* @param { (value: T) => Promise<boolean> } predicate
*/ */
const asyncFilter = async (arr, predicate) => { const asyncFilter = async (arr, predicate) => {
// @ts-ignore
const results = await Promise.all(arr.map(predicate)); const results = await Promise.all(arr.map(predicate));
// @ts-ignore
return arr.filter((_v, index) => results[index]); return arr.filter((_v, index) => results[index]);
}; };
// @ts-ignore
const result = await asyncFilter(files.flat(), async (v) => { const result = await asyncFilter(files.flat(), async (v) => {
return v.size != (await game.promises.readFile(v.path)).length; return v.size != (await game.promises.readFile(v.path)).length;
}).then((arr) => arr.map((v) => v.path)); }).then((arr) => arr.map((v) => v.path));
console.log("需要更新的文件有:", result); console.log("需要更新的文件有:", result);
game.print("需要更新的文件有:", result); game.print("需要更新的文件有:", result);
const finish = async () => { const finish = async () => {
@ -465,71 +459,57 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
* @type {progress} * @type {progress}
*/ */
let unZipProgress; let unZipProgress;
request( request('api.unitedrhythmized.club/noname', (receivedBytes, total, filename) => {
"noname.unitedrhythmized.club/api", if (typeof filename == 'string') {
(receivedBytes, total, filename) => { progress.setFileName(filename);
if (typeof filename == "string") {
progress.setFileName(filename);
}
let received = 0,
max = 0;
if (total) {
max = +(total / (1024 * 1024)).toFixed(1);
} else {
max = 1000;
}
received = +(receivedBytes / (1024 * 1024)).toFixed(1);
if (received > max) max = received;
progress.setProgressMax(max);
progress.setProgressValue(received);
},
{
method: "post",
body: JSON.stringify({
fileList: result.concat("game/asset.js"),
}),
} }
) let received = 0, max = 0;
.then(async (blob) => { if (total) {
progress.remove(); max = +(total / (1024 * 1024)).toFixed(1)
const zip = await get.promises.zip(); } else {
zip.load(await blob.arrayBuffer()); max = 1000;
const entries = Object.entries(zip.files); }
let root; received = +(receivedBytes / (1024 * 1024)).toFixed(1);
const hiddenFileFlags = [".", "_"]; if (received > max) max = received;
unZipProgress = createProgress( progress.setProgressMax(max);
"正在解压" + progress.getFileName(), progress.setProgressValue(received);
entries.length }, {
); method: 'POST',
let i = 0; body: JSON.stringify({
for (const [key, value] of entries) { action: 'downloadAssets',
unZipProgress.setProgressValue(i++); version,
const fileName = fileList: result.concat('game/asset.js')
typeof root == "string" && key.startsWith(root)
? key.replace(root, "")
: key;
if (hiddenFileFlags.includes(fileName[0])) continue;
if (value.dir) {
await game.promises.createDir(fileName);
continue;
}
unZipProgress.setFileName(fileName);
const [path, name] = [
fileName.split("/").slice(0, -1).join("/"),
fileName.split("/").slice(-1).join("/"),
];
game.print(`${fileName}(${i}/${entries.length})`);
await game.promises.writeFile(value.asArrayBuffer(), path, name);
}
unZipProgress.remove();
await finish();
}) })
.catch((e) => { }).then(async blob => {
if (progress.parentNode) progress.remove(); progress.remove();
if (unZipProgress && unZipProgress.parentNode) unZipProgress.remove(); const zip = await get.promises.zip();
refresh(); zip.load(await blob.arrayBuffer());
throw e; const entries = Object.entries(zip.files);
}); let root;
const hiddenFileFlags = ['.', '_'];
unZipProgress = createProgress('正在解压' + progress.getFileName(), entries.length);
let i = 0;
for (const [key, value] of entries) {
unZipProgress.setProgressValue(i++);
const fileName = typeof root == 'string' && key.startsWith(root) ? key.replace(root, '') : key;
if (hiddenFileFlags.includes(fileName[0])) continue;
if (value.dir) {
await game.promises.createDir(fileName);
continue;
}
unZipProgress.setFileName(fileName);
const [path, name] = [fileName.split('/').slice(0, -1).join('/'), fileName.split('/').slice(-1).join('/')];
game.print(`${fileName}(${i}/${entries.length})`);
await game.promises.writeFile(value.asArrayBuffer(), path, name);
}
unZipProgress.remove();
await finish();
}).catch(e => {
if (progress.parentNode) progress.remove();
if (unZipProgress && unZipProgress.parentNode) unZipProgress.remove();
refresh();
throw e;
});
} else { } else {
await finish(); await finish();
} }
@ -1666,3 +1646,4 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
if (!active.link) active._initLink(); if (!active.link) active._initLink();
rightPane.appendChild(active.link); rightPane.appendChild(active.link);
}; };