From a2c914399876bf0979cd41ec8a33f9d6b628ecfd Mon Sep 17 00:00:00 2001 From: notcancername <119271574+notcancername@users.noreply.github.com> Date: Tue, 31 Jan 2023 18:01:50 +0100 Subject: [PATCH 1/3] properly fetch, parse and use the manifest --- webAO/client/fetchLists.ts | 16 +++++++++++----- webAO/utils/binarySearch.js | 21 +++++++++++++++++++++ webAO/utils/fileExists.js | 26 ++++++++++++++++++++++++++ webAO/utils/paths.ts | 19 +++++++++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 webAO/utils/binarySearch.js diff --git a/webAO/client/fetchLists.ts b/webAO/client/fetchLists.ts index 9efd1817..a751ec07 100644 --- a/webAO/client/fetchLists.ts +++ b/webAO/client/fetchLists.ts @@ -1,6 +1,7 @@ import { client } from "../client"; import { AO_HOST } from "./aoHost"; import { request } from "../services/request.js"; +import { canonicalizePath } from "../utils/paths"; export const fetchBackgroundList = async () => { try { @@ -27,7 +28,7 @@ export const fetchCharacterList = async () => { char_select.innerHTML = ""; char_select.add(new Option("Custom", "0")); - + try { const chardata = await request(`${AO_HOST}characters.json`); const char_array = JSON.parse(chardata); @@ -58,7 +59,7 @@ export const fetchEvidenceList = async () => { evi_array.forEach((evi: string) => { evi_select.add(new Option(evi)); }); - + } catch (err) { console.warn("there was no evidence.json file"); } @@ -68,10 +69,15 @@ export const fetchEvidenceList = async () => { export const fetchManifest = async () => { try { const manifestdata = await request(`${AO_HOST}manifest.txt`); - client.manifest = manifestdata.split(/\r\n|\n\r|\n|\r/); + const tmp: string[] = manifestdata + .replace("\\", "/") + .split(/\r\n|\n\r|\n|\r/); + client.manifest = tmp.map(x => canonicalizePath(x)).sort(); + // the try catch will fail before here when there is no file } catch (err) { - console.warn("there was no manifest.txt file"); + console.warn("no manifest.txt, webao will be slow"); + client.manifest = []; } -} \ No newline at end of file +} diff --git a/webAO/utils/binarySearch.js b/webAO/utils/binarySearch.js new file mode 100644 index 00000000..7ffbf1a1 --- /dev/null +++ b/webAO/utils/binarySearch.js @@ -0,0 +1,21 @@ +/* Returns the index of `needle' in `haystack', or `null' if it wasn't found. */ +export function binarySearch(haystack, + needle, + cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0) { + let start = 0; + let end = haystack.length - 1; + + while (start <= end) { + const middle = Math.floor((start + end) / 2); + const comparison = cmp(haystack[middle], needle); + + if (comparison == 0) + return middle; + else if (comparison < 0) + start = middle + 1; + else + end = middle - 1; + } + + return null; +} diff --git a/webAO/utils/fileExists.js b/webAO/utils/fileExists.js index 7978cbc6..2afd4ed8 100644 --- a/webAO/utils/fileExists.js +++ b/webAO/utils/fileExists.js @@ -1,3 +1,6 @@ +import {canonicalizePath} from "./paths" +import {binarySearch} from "./binarySearch" + const fileExists = async (url) => new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('HEAD', url); @@ -16,3 +19,26 @@ const fileExists = async (url) => new Promise((resolve, reject) => { xhr.send(null); }); export default fileExists; + +/* Returns whether file exists. + * `manifest' is a sorted array of strings. + * `ao_head' is the base URL. + * `url' is a URL-encoded path. + * If manifest is empty, check the old way. + * Otherwise, look it up in the manifest */ +const fileExistsManifest = async (manifest, ao_host, url) => + new Promise((resolve, reject) => { + + if(manifest == undefined || + manifest == null || + manifest.length == 0) + resolve(fileExists(ao_host + url)); + + const c_url = encodeURI(canonicalizePath(decodeURI(url))); + + if(binarySearch(manifest, c_url) != null) + resolve(true); + + resolve(false); + }); +export {fileExistsManifest}; diff --git a/webAO/utils/paths.ts b/webAO/utils/paths.ts index f4284b66..9cf4f197 100644 --- a/webAO/utils/paths.ts +++ b/webAO/utils/paths.ts @@ -1 +1,20 @@ export const getFilenameFromPath = (path: string) => path.substring(path.lastIndexOf('/') + 1) + +/* Removes `/./`, `/../`, and `//`. + * Does not add a leading `/` or `./`. + * Does not add a trailing `/`. */ +export function canonicalizePath(path: string): string { + const path_elements = path.split("/"); + var result: string[] = []; + + for (const el of path_elements) { + if (el === "..") + result.pop(); + else if (el === "." || el === "") + continue; + + result.push(el); + } + + return result.join("/"); +} From a78a7b88f28ee8c299faf94c62f2fc4d4a1b3eb7 Mon Sep 17 00:00:00 2001 From: notcancername <119271574+notcancername@users.noreply.github.com> Date: Tue, 31 Jan 2023 18:51:24 +0100 Subject: [PATCH 2/3] use manifest --- webAO/client/handleCharacterInfo.ts | 8 +++++--- webAO/client/setEmote.ts | 9 ++++++--- webAO/packets/handlers/handlePV.ts | 8 +++++--- webAO/utils/getAnimLength.js | 8 ++++++-- webAO/utils/tryUrls.ts | 10 +++++++--- webAO/viewport/utils/setSide.ts | 12 ++++++++---- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts index 9d74a8bc..1a403e03 100644 --- a/webAO/client/handleCharacterInfo.ts +++ b/webAO/client/handleCharacterInfo.ts @@ -2,7 +2,7 @@ import { client } from "../client"; import { safeTags } from "../encoding"; import iniParse from "../iniParse"; import request from "../services/request"; -import fileExists from "../utils/fileExists"; +import { fileExistsManifest } from "../utils/fileExists"; import { AO_HOST } from "./aoHost"; @@ -23,7 +23,9 @@ export const handleCharacterInfo = async (chargs: string[], charid: number) => { )}/char_icon`; for (let i = 0; i < extensions.length; i++) { const fileUrl = charIconBaseUrl + extensions[i]; - const exists = await fileExists(fileUrl); + const exists = await fileExistsManifest(client, + AO_HOST, + fileUrl); if (exists) { img.alt = chargs[0]; img.title = chargs[0]; @@ -102,4 +104,4 @@ export const handleCharacterInfo = async (chargs: string[], charid: number) => { console.warn(`missing charid ${charid}`); img.style.display = "none"; } -} \ No newline at end of file +} diff --git a/webAO/client/setEmote.ts b/webAO/client/setEmote.ts index f4fbdbb1..62ebf25a 100644 --- a/webAO/client/setEmote.ts +++ b/webAO/client/setEmote.ts @@ -1,6 +1,6 @@ import Client from "../client"; import transparentPng from "../constants/transparentPng"; -import fileExists from "../utils/fileExists"; +import {fileExistsManifest} from "../utils/fileExists"; /** * Sets all the img tags to the right sources @@ -39,13 +39,16 @@ const setEmote = async ( } else if (extension === ".webp.static") { url = `${characterFolder}${encodeURI(charactername)}/${encodeURI( emotename - )}.webp`; + )}.webp`; } else { url = `${characterFolder}${encodeURI(charactername)}/${encodeURI( prefix )}${encodeURI(emotename)}${extension}`; } - const exists = await fileExists(url); + + const exists = await fileExistsManifest(client.manifest, + AO_HOST, + url.slice(AO_HOST.length)); if (exists) { emoteSelector.src = url; break; diff --git a/webAO/packets/handlers/handlePV.ts b/webAO/packets/handlers/handlePV.ts index 3c669b96..32a82a7a 100644 --- a/webAO/packets/handlers/handlePV.ts +++ b/webAO/packets/handlers/handlePV.ts @@ -1,5 +1,5 @@ import { client } from "../../client"; -import fileExists from "../../utils/fileExists"; +import { fileExistsManifest } from "../../utils/fileExists"; import { updateActionCommands } from '../../dom/updateActionCommands' import { pickEmotion } from '../../dom/pickEmotion' import { AO_HOST } from "../../client/aoHost"; @@ -73,8 +73,10 @@ export const handlePV = async (args: string[]) => { } if ( - await fileExists( - `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/custom.gif` + await fileExistsManifest( + client.manifest, + AO_HOST, + `characters/${encodeURI(me.name.toLowerCase())}/custom.gif` ) ) { document.getElementById("button_4")!.style.display = ""; diff --git a/webAO/utils/getAnimLength.js b/webAO/utils/getAnimLength.js index aa303cfb..45d550ff 100644 --- a/webAO/utils/getAnimLength.js +++ b/webAO/utils/getAnimLength.js @@ -1,5 +1,7 @@ +import {client} from '../client' +import {AO_HOST} from '../client/aoHost' import calculatorHandler from './calculatorHandler'; -import fileExists from './fileExists.js'; +import {fileExistsManifest} from './fileExists.js'; import { requestBuffer } from '../services/request.js'; /** * Gets animation length. If the animation cannot be found, it will @@ -11,7 +13,9 @@ const getAnimLength = async (url) => { const extensions = ['.gif', '.webp', '.apng']; for (const extension of extensions) { const urlWithExtension = url + extension; - const exists = await fileExists(urlWithExtension); + const exists = await fileExists(client.manifest, + AO_HOST, + urlWithExtension); if (exists) { const fileBuffer = await requestBuffer(urlWithExtension); const length = calculatorHandler[extension](fileBuffer); diff --git a/webAO/utils/tryUrls.ts b/webAO/utils/tryUrls.ts index 14ef8852..7bca6322 100644 --- a/webAO/utils/tryUrls.ts +++ b/webAO/utils/tryUrls.ts @@ -1,4 +1,6 @@ -import fileExists from './fileExists' +import {client} from "../client.ts" +import {AO_HOST} from "../client/aoHost.ts" +import {fileExistsManifest} from './fileExists' import transparentPng from '../constants/transparentPng' const urlExtensionsToTry = [ '.png', @@ -10,11 +12,13 @@ const tryUrls = async (url: string) => { for (let i = 0; i < urlExtensionsToTry.length; i++) { const extension = urlExtensionsToTry[i] const fullFileUrl = url + extension - const exists = await fileExists(fullFileUrl); + const exists = await fileExistsManifest(client.manifest, + AO_HOST, + fullFileUrl); if (exists) { return fullFileUrl } } return transparentPng } -export default tryUrls \ No newline at end of file +export default tryUrls diff --git a/webAO/viewport/utils/setSide.ts b/webAO/viewport/utils/setSide.ts index 3726e83d..518b25d4 100644 --- a/webAO/viewport/utils/setSide.ts +++ b/webAO/viewport/utils/setSide.ts @@ -2,7 +2,7 @@ import { positions } from '../constants/positions' import { AO_HOST } from '../../client/aoHost' import { client } from '../../client' import tryUrls from '../../utils/tryUrls'; -import fileExists from '../../utils/fileExists'; +import { fileExistsManifest } from '../../utils/fileExists'; /** * Changes the viewport background based on a given position. @@ -57,10 +57,14 @@ export const set_side = async ({ } else { court.src = await tryUrls(client.viewport.getBackgroundFolder() + bg); } - - + + if (showDesk === true && desk) { - const deskFilename = (await fileExists(client.viewport.getBackgroundFolder() + desk.ao2)) + const deskFilename = (await fileExistsManifest( + client.manifest, + AO_HOST, + (client.viewport.getBackgroundFolder() + + desk.ao2).slice(AO_HOST.length)) ? desk.ao2 : desk.ao1; bench.src = client.viewport.getBackgroundFolder() + deskFilename; From e1feeded9f2eff0174080d447080e51057bbf22f Mon Sep 17 00:00:00 2001 From: notcancername <119271574+notcancername@users.noreply.github.com> Date: Tue, 31 Jan 2023 20:56:06 +0100 Subject: [PATCH 3/3] fixup! use manifest --- webAO/client/handleCharacterInfo.ts | 4 +--- webAO/client/setEmote.ts | 4 +--- webAO/packets/handlers/handlePV.ts | 2 -- webAO/utils/fileExists.js | 22 ++++++++++------------ webAO/utils/getAnimLength.js | 4 +--- webAO/utils/tryUrls.ts | 6 +----- webAO/viewport/utils/setSide.ts | 6 ++---- 7 files changed, 16 insertions(+), 32 deletions(-) diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts index 1a403e03..f2d9bfcb 100644 --- a/webAO/client/handleCharacterInfo.ts +++ b/webAO/client/handleCharacterInfo.ts @@ -23,9 +23,7 @@ export const handleCharacterInfo = async (chargs: string[], charid: number) => { )}/char_icon`; for (let i = 0; i < extensions.length; i++) { const fileUrl = charIconBaseUrl + extensions[i]; - const exists = await fileExistsManifest(client, - AO_HOST, - fileUrl); + const exists = await fileExistsManifest(fileUrl); if (exists) { img.alt = chargs[0]; img.title = chargs[0]; diff --git a/webAO/client/setEmote.ts b/webAO/client/setEmote.ts index 62ebf25a..41fe6b51 100644 --- a/webAO/client/setEmote.ts +++ b/webAO/client/setEmote.ts @@ -46,9 +46,7 @@ const setEmote = async ( )}${encodeURI(emotename)}${extension}`; } - const exists = await fileExistsManifest(client.manifest, - AO_HOST, - url.slice(AO_HOST.length)); + const exists = await fileExistsManifest(url); if (exists) { emoteSelector.src = url; break; diff --git a/webAO/packets/handlers/handlePV.ts b/webAO/packets/handlers/handlePV.ts index 32a82a7a..affabbed 100644 --- a/webAO/packets/handlers/handlePV.ts +++ b/webAO/packets/handlers/handlePV.ts @@ -74,8 +74,6 @@ export const handlePV = async (args: string[]) => { if ( await fileExistsManifest( - client.manifest, - AO_HOST, `characters/${encodeURI(me.name.toLowerCase())}/custom.gif` ) ) { diff --git a/webAO/utils/fileExists.js b/webAO/utils/fileExists.js index 2afd4ed8..dab41072 100644 --- a/webAO/utils/fileExists.js +++ b/webAO/utils/fileExists.js @@ -1,3 +1,5 @@ +import {AO_HOST} from "../client/aoHost" +import {client} from "../client" import {canonicalizePath} from "./paths" import {binarySearch} from "./binarySearch" @@ -21,22 +23,18 @@ const fileExists = async (url) => new Promise((resolve, reject) => { export default fileExists; /* Returns whether file exists. - * `manifest' is a sorted array of strings. - * `ao_head' is the base URL. - * `url' is a URL-encoded path. + * `url' is a URL, including the base URL for bw compat. * If manifest is empty, check the old way. * Otherwise, look it up in the manifest */ -const fileExistsManifest = async (manifest, ao_host, url) => +const fileExistsManifest = async (url) => new Promise((resolve, reject) => { + if(client.manifest.length == 0) { + resolve(fileExists(url)); + return; + } + const c_url = encodeURI(canonicalizePath(decodeURI(url.slice(AO_HOST.length)))); - if(manifest == undefined || - manifest == null || - manifest.length == 0) - resolve(fileExists(ao_host + url)); - - const c_url = encodeURI(canonicalizePath(decodeURI(url))); - - if(binarySearch(manifest, c_url) != null) + if(binarySearch(client.manifest, c_url) != null) resolve(true); resolve(false); diff --git a/webAO/utils/getAnimLength.js b/webAO/utils/getAnimLength.js index 45d550ff..4eae58fa 100644 --- a/webAO/utils/getAnimLength.js +++ b/webAO/utils/getAnimLength.js @@ -13,9 +13,7 @@ const getAnimLength = async (url) => { const extensions = ['.gif', '.webp', '.apng']; for (const extension of extensions) { const urlWithExtension = url + extension; - const exists = await fileExists(client.manifest, - AO_HOST, - urlWithExtension); + const exists = await fileExistsManifest(urlWithExtension); if (exists) { const fileBuffer = await requestBuffer(urlWithExtension); const length = calculatorHandler[extension](fileBuffer); diff --git a/webAO/utils/tryUrls.ts b/webAO/utils/tryUrls.ts index 7bca6322..1e5107f7 100644 --- a/webAO/utils/tryUrls.ts +++ b/webAO/utils/tryUrls.ts @@ -1,5 +1,3 @@ -import {client} from "../client.ts" -import {AO_HOST} from "../client/aoHost.ts" import {fileExistsManifest} from './fileExists' import transparentPng from '../constants/transparentPng' const urlExtensionsToTry = [ @@ -12,9 +10,7 @@ const tryUrls = async (url: string) => { for (let i = 0; i < urlExtensionsToTry.length; i++) { const extension = urlExtensionsToTry[i] const fullFileUrl = url + extension - const exists = await fileExistsManifest(client.manifest, - AO_HOST, - fullFileUrl); + const exists = await fileExistsManifest(fullFileUrl); if (exists) { return fullFileUrl } diff --git a/webAO/viewport/utils/setSide.ts b/webAO/viewport/utils/setSide.ts index 518b25d4..69018227 100644 --- a/webAO/viewport/utils/setSide.ts +++ b/webAO/viewport/utils/setSide.ts @@ -61,10 +61,8 @@ export const set_side = async ({ if (showDesk === true && desk) { const deskFilename = (await fileExistsManifest( - client.manifest, - AO_HOST, - (client.viewport.getBackgroundFolder() + - desk.ao2).slice(AO_HOST.length)) + (client.viewport.getBackgroundFolder() + + desk.ao2))) ? desk.ao2 : desk.ao1; bench.src = client.viewport.getBackgroundFolder() + deskFilename;