TeaSpeak-Client/modules/core/ui-loader/Remote.ts
2020-12-02 18:08:49 +01:00

125 lines
4.4 KiB
TypeScript

import {CachedUIPack, UIPackInfo} from "./CacheFile";
import * as request from "request";
import {remoteUiUrl} from "./Loader";
import * as fs from "fs-extra";
import {WriteStream} from "fs";
import {localUiCache, saveLocalUiCache, uiPackCachePath} from "./Cache";
import * as querystring from "querystring";
import * as path from "path";
const kDownloadTimeout = 30_000;
export async function queryRemoteUiPacks() : Promise<UIPackInfo[]> {
const url = remoteUiUrl() + "api.php?" + querystring.stringify({
type: "ui-info"
});
console.debug("Loading UI pack information (URL: %s)", url);
let body = await new Promise<string>((resolve, reject) => request.get(url, { timeout: kDownloadTimeout }, (error, response, body: string) => {
if(error) {
reject(error);
} else if(!response) {
reject("missing response object");
} else if(response.statusCode !== 200) {
reject(response.statusCode + " " + response.statusMessage);
} else if(!body) {
reject("missing body in response");
} else {
resolve(body);
}
}));
let response;
try {
response = JSON.parse(body);
} catch (error) {
console.error("Received unparsable response for UI pack info. Response: %s", body);
throw "failed to parse response";
}
if(!response["success"]) {
throw "request failed: " + (response["msg"] || "unknown error");
}
if(!Array.isArray(response["versions"])) {
console.error("Response object misses 'versions' tag or has an invalid value. Object: %o", response);
throw "response contains invalid data";
}
let uiVersions: UIPackInfo[] = [];
for(const entry of response["versions"]) {
uiVersions.push({
channel: entry["channel"],
versions_hash: entry["git-ref"],
version: entry["version"],
timestamp: parseInt(entry["timestamp"]) * 1000, /* server provices that stuff in seconds */
requiredClientVersion: entry["required_client"]
});
}
return uiVersions;
}
export async function downloadUiPack(version: UIPackInfo) : Promise<CachedUIPack> {
const targetFile = uiPackCachePath(version);
if(await fs.pathExists(targetFile)) {
try {
await fs.remove(targetFile);
} catch (error) {
console.error("Tried to download UI version %s, but we failed to delete the old file: %o", version.versions_hash, error);
throw "failed to remove the old file";
}
}
try {
await fs.mkdirp(path.dirname(targetFile));
} catch (error) {
console.error("Failed to create target UI pack download directory at %s: %o", path.dirname(targetFile), error);
throw "failed to create target directories";
}
await new Promise((resolve, reject) => {
let fstream: WriteStream;
try {
request.get(remoteUiUrl() + "api.php?" + querystring.stringify({
"type": "ui-download",
"git-ref": version.versions_hash,
"version": version.version,
"timestamp": Math.floor(version.timestamp / 1000), /* remote server has only the timestamp in seconds*/
"channel": version.channel
}), {
timeout: kDownloadTimeout
}).on('response', function(response: request.Response) {
if(response.statusCode != 200)
reject(response.statusCode + " " + response.statusMessage);
}).on('error', error => {
reject(error);
}).pipe(fstream = fs.createWriteStream(targetFile)).on('finish', () => {
try { fstream.close(); } catch (e) { }
resolve();
});
} catch (error) {
try { fstream.close(); } catch (e) { }
reject(error);
}
});
try {
const cache = await localUiCache();
const info: CachedUIPack = {
packInfo: version,
localFilePath: targetFile,
localChecksum: "none", //TODO!
status: { type: "valid" },
downloadTimestamp: Date.now()
};
cache.cachedPacks.push(info);
await saveLocalUiCache();
return info;
} catch (error) {
console.error("Failed to register downloaded UI pack to the UI cache: %o", error);
throw "failed to register downloaded UI pack to the UI cache";
}
}