|
|
|
@@ -1,9 +1,9 @@
|
|
|
|
|
import {is_debug} from "../main_window";
|
|
|
|
|
|
|
|
|
|
const request = require('request');
|
|
|
|
|
const querystring = require('querystring');
|
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
|
const os = require('os');
|
|
|
|
|
import * as moment from "moment";
|
|
|
|
|
import * as request from "request";
|
|
|
|
|
import * as querystring from "querystring";
|
|
|
|
|
import * as fs from "fs-extra";
|
|
|
|
|
import * as os from "os";
|
|
|
|
|
const UUID = require('pure-uuid');
|
|
|
|
|
import * as path from "path";
|
|
|
|
|
import * as zlib from "zlib";
|
|
|
|
@@ -14,9 +14,10 @@ import {parse_version} from "../../shared/version";
|
|
|
|
|
import * as electron from "electron";
|
|
|
|
|
import MessageBoxOptions = Electron.MessageBoxOptions;
|
|
|
|
|
import {current_version, execute_graphical} from "../app-updater";
|
|
|
|
|
import * as local_ui_cache from "./local_ui_cache";
|
|
|
|
|
import {WriteStream} from "fs";
|
|
|
|
|
|
|
|
|
|
const TIMEOUT = 30000;
|
|
|
|
|
let local_path = undefined;
|
|
|
|
|
|
|
|
|
|
interface RemoteURL {
|
|
|
|
|
(): string;
|
|
|
|
@@ -29,85 +30,89 @@ const remote_url: RemoteURL = () => {
|
|
|
|
|
return remote_url.cached = (process_args.has_value(...Arguments.SERVER_URL) ? process_args.value(...Arguments.SERVER_URL) : default_path);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function data_directory() : string {
|
|
|
|
|
return electron.app.getPath('userData');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cache_directory() : string {
|
|
|
|
|
return path.join(data_directory(), "cache", "ui");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function working_directory() : string {
|
|
|
|
|
return path.join(data_directory(), "tmp", "ui");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface VersionedFile {
|
|
|
|
|
name: string,
|
|
|
|
|
hash: string,
|
|
|
|
|
path: string,
|
|
|
|
|
type: string,
|
|
|
|
|
|
|
|
|
|
local_url: () => Promise<String>
|
|
|
|
|
local_url: () => Promise<string>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generate_tmp() : Promise<String> {
|
|
|
|
|
if(local_path) return Promise.resolve(local_path);
|
|
|
|
|
function generate_tmp() : Promise<string> {
|
|
|
|
|
if(generate_tmp.promise) return generate_tmp.promise;
|
|
|
|
|
|
|
|
|
|
const id = new UUID(4).format();
|
|
|
|
|
const directory = path.join(os.tmpdir(), "TeaClient-" + id) + "/";
|
|
|
|
|
return (generate_tmp.promise = fs.mkdtemp(path.join(os.tmpdir(), "TeaClient-")).then(path => {
|
|
|
|
|
process.on('exit', event => {
|
|
|
|
|
try {
|
|
|
|
|
if(fs.pathExistsSync(path))
|
|
|
|
|
fs.removeSync(path);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("Failed to delete temp directory: %o", e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
global["browser-root"] = path;
|
|
|
|
|
console.log("Local browser path: %s", path);
|
|
|
|
|
return Promise.resolve(path);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fs.mkdirs(directory).then(() => {
|
|
|
|
|
local_path = directory;
|
|
|
|
|
global["browser-root"] = local_path;
|
|
|
|
|
console.log("Local browser path: %s", local_path);
|
|
|
|
|
return Promise.resolve(local_path);
|
|
|
|
|
});
|
|
|
|
|
namespace generate_tmp {
|
|
|
|
|
export let promise: Promise<string>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function get_raw_app_files() : Promise<VersionedFile[]> {
|
|
|
|
|
return generate_tmp().then(path => new Promise<VersionedFile[]>((resolve, reject) => {
|
|
|
|
|
const url = remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
type: "files",
|
|
|
|
|
return new Promise<VersionedFile[]>((resolve, reject) => {
|
|
|
|
|
const url = remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
type: "files",
|
|
|
|
|
});
|
|
|
|
|
console.debug("Requesting file list from %s", url);
|
|
|
|
|
request.get(url, {
|
|
|
|
|
timeout: TIMEOUT
|
|
|
|
|
}, (error, response, body: string) => {
|
|
|
|
|
if(error) {
|
|
|
|
|
reject(error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!response) {
|
|
|
|
|
reject("missing response object");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(response.statusCode != 200) { setImmediate(reject, "invalid status code " + response.statusCode + " for " + url); return; }
|
|
|
|
|
if(parseInt(response.headers["info-version"] as string) != 1 && !process_args.has_flag(Arguments.UPDATER_UI_IGNORE_VERSION)) { setImmediate(reject, "Invalid response version (" + response.headers["info-version"] + "). Update your app manually!"); return; }
|
|
|
|
|
if(!body) {
|
|
|
|
|
setImmediate(reject, "invalid body. (Missing)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result: VersionedFile[] = [];
|
|
|
|
|
|
|
|
|
|
body.split("\n").forEach(entry => {
|
|
|
|
|
if(entry.length == 0) return;
|
|
|
|
|
|
|
|
|
|
let info = entry.split("\t");
|
|
|
|
|
if(info[0] == "type") return;
|
|
|
|
|
|
|
|
|
|
result.push({
|
|
|
|
|
type: info[0],
|
|
|
|
|
hash: info[1],
|
|
|
|
|
path: info[2],
|
|
|
|
|
name: info[3]
|
|
|
|
|
} as VersionedFile);
|
|
|
|
|
});
|
|
|
|
|
console.debug("Requesting file list from %s", url);
|
|
|
|
|
request.get(url, {
|
|
|
|
|
timeout: TIMEOUT
|
|
|
|
|
}, (error, response, body: string) => {
|
|
|
|
|
response = response || {statusCode: -1};
|
|
|
|
|
|
|
|
|
|
if(error) { reject(error); return; }
|
|
|
|
|
if(response.statusCode != 200) { setImmediate(reject, "invalid status code " + response.statusCode + " for " + url); return; }
|
|
|
|
|
if(response.headers["info-version"] != 1 && !process_args.has_flag(Arguments.UPDATER_UI_IGNORE_VERSION)) { setImmediate(reject, "Invalid response version (" + response.headers["info-version"] + "). Update your app manually!"); return; }
|
|
|
|
|
if(!body) {
|
|
|
|
|
setImmediate(reject, "invalid body. (Missing)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result: VersionedFile[] = [];
|
|
|
|
|
|
|
|
|
|
body.split("\n").forEach(entry => {
|
|
|
|
|
if(entry.length == 0) return;
|
|
|
|
|
|
|
|
|
|
let info = entry.split("\t");
|
|
|
|
|
if(info[0] == "type") return;
|
|
|
|
|
|
|
|
|
|
result.push({
|
|
|
|
|
type: info[0],
|
|
|
|
|
hash: info[1],
|
|
|
|
|
path: info[2],
|
|
|
|
|
name: info[3]
|
|
|
|
|
} as VersionedFile);
|
|
|
|
|
});
|
|
|
|
|
setImmediate(resolve, result);
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
setImmediate(resolve, result);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function download_raw_app_files() : Promise<VersionedFile[]> {
|
|
|
|
|
async function download_raw_app_files() : Promise<VersionedFile[]> {
|
|
|
|
|
const local_temp_path = await generate_tmp();
|
|
|
|
|
return get_raw_app_files().then(response => {
|
|
|
|
|
for(let file of response) {
|
|
|
|
|
const full_path = path.join(local_path, file.path, file.name);
|
|
|
|
|
file.local_url = () => fs.mkdirs(path.dirname(full_path)).then(() => new Promise<String>((resolve, reject) => {
|
|
|
|
|
const full_path = path.join(local_temp_path, file.path, file.name);
|
|
|
|
|
file.local_url = () => fs.mkdirs(path.dirname(full_path)).then(() => new Promise<string>((resolve, reject) => {
|
|
|
|
|
const write_stream = fs.createWriteStream(full_path);
|
|
|
|
|
request.get(remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
type: "file",
|
|
|
|
@@ -122,11 +127,16 @@ function download_raw_app_files() : Promise<VersionedFile[]> {
|
|
|
|
|
}
|
|
|
|
|
}).on('complete', event => {
|
|
|
|
|
}).on('error', error => {
|
|
|
|
|
try { write_stream.close(); } catch (e) { }
|
|
|
|
|
setImmediate(reject, error);
|
|
|
|
|
}).pipe(write_stream)
|
|
|
|
|
.on('finish', event => {
|
|
|
|
|
setImmediate(resolve, file.path + "/" + file.name);
|
|
|
|
|
});
|
|
|
|
|
.on('finish', event => {
|
|
|
|
|
try { write_stream.close(); } catch (e) { }
|
|
|
|
|
setImmediate(resolve, file.path + "/" + file.name);
|
|
|
|
|
}).on('error', error => {
|
|
|
|
|
try { write_stream.close(); } catch (e) { }
|
|
|
|
|
setImmediate(reject, error);
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
return Promise.resolve(response);
|
|
|
|
@@ -136,77 +146,13 @@ function download_raw_app_files() : Promise<VersionedFile[]> {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface LocalUICache {
|
|
|
|
|
fetch_history?: FetchStatus;
|
|
|
|
|
versions?: LocalUICacheEntry[];
|
|
|
|
|
|
|
|
|
|
remote_index?: UIVersion[] | UIVersion;
|
|
|
|
|
remote_index_channel?: string; /* only set if the last status was a channel only*/
|
|
|
|
|
|
|
|
|
|
local_index?: UIVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface FetchStatus {
|
|
|
|
|
timestamp: number;
|
|
|
|
|
/**
|
|
|
|
|
* 0 = success
|
|
|
|
|
* 1 = connect fail
|
|
|
|
|
* 2 = internal fail
|
|
|
|
|
*/
|
|
|
|
|
status: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface LocalUICacheEntry {
|
|
|
|
|
version: UIVersion;
|
|
|
|
|
download_timestamp: number;
|
|
|
|
|
tar_file: string;
|
|
|
|
|
checksum: string; /* SHA512 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface UIVersion {
|
|
|
|
|
channel: string;
|
|
|
|
|
version: string;
|
|
|
|
|
git_hash: string;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
|
|
|
|
|
required_client?: string;
|
|
|
|
|
filename?: string;
|
|
|
|
|
|
|
|
|
|
client_shipped?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ui_file_path(version: UIVersion) : string {
|
|
|
|
|
if(version.client_shipped) {
|
|
|
|
|
const app_path = electron.app.getAppPath();
|
|
|
|
|
if(!app_path.endsWith(".asar"))
|
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
|
|
return path.join(path.join(path.dirname(app_path), "ui"), version.filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const file_name = "ui_" + version.channel + "_" + version.version + "_" + version.git_hash + "_" + version.timestamp + ".tar.gz";
|
|
|
|
|
return path.join(cache_directory(), file_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _ui_load_cache: LocalUICache;
|
|
|
|
|
async function ui_load_cache() : Promise<LocalUICache> {
|
|
|
|
|
if(_ui_load_cache) return _ui_load_cache;
|
|
|
|
|
|
|
|
|
|
const file = path.join(cache_directory(), "data.json");
|
|
|
|
|
if(!fs.existsSync(file)) return {} as LocalUICache;
|
|
|
|
|
|
|
|
|
|
console.log("Loading UI cache file %s", file);
|
|
|
|
|
_ui_load_cache = await fs.readJson(file) as LocalUICache;
|
|
|
|
|
return _ui_load_cache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function client_shipped_ui() : Promise<UIVersion | undefined> {
|
|
|
|
|
async function client_shipped_ui() : Promise<local_ui_cache.CachedUIPack | undefined> {
|
|
|
|
|
const app_path = electron.app.getAppPath();
|
|
|
|
|
if(!app_path.endsWith(".asar"))
|
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
|
|
const base_path = path.join(path.dirname(app_path), "ui");
|
|
|
|
|
console.debug("Looking for client shipped UI pack at %s", base_path);
|
|
|
|
|
//console.debug("Looking for client shipped UI pack at %s", base_path);
|
|
|
|
|
if(!(await fs.pathExists(base_path)))
|
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
|
@@ -220,138 +166,173 @@ async function client_shipped_ui() : Promise<UIVersion | undefined> {
|
|
|
|
|
} = await fs.readJson(path.join(base_path, "default_ui_info.json")) as any;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
channel: info.channel,
|
|
|
|
|
client_shipped: true,
|
|
|
|
|
download_timestamp: info.timestamp,
|
|
|
|
|
status: "valid",
|
|
|
|
|
invalid_reason: undefined,
|
|
|
|
|
local_checksum: "none",
|
|
|
|
|
local_file_path: path.join(path.join(path.dirname(app_path), "ui"), info.filename),
|
|
|
|
|
pack_info: {
|
|
|
|
|
channel: info.channel,
|
|
|
|
|
min_client_version: "0.0.0", //TODO: Just take the current client version
|
|
|
|
|
timestamp: info.timestamp,
|
|
|
|
|
version: info.version,
|
|
|
|
|
versions_hash: info.git_hash
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filename: info.filename,
|
|
|
|
|
git_hash: info.git_hash,
|
|
|
|
|
required_client: info.required_client,
|
|
|
|
|
timestamp: info.timestamp,
|
|
|
|
|
version: info.version,
|
|
|
|
|
async function query_ui_pack_versions() : Promise<local_ui_cache.UIPackInfo[]> {
|
|
|
|
|
const url = remote_url() + "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: TIMEOUT }, (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 ui_versions: local_ui_cache.UIPackInfo[] = [];
|
|
|
|
|
for(const entry of response["versions"]) {
|
|
|
|
|
ui_versions.push({
|
|
|
|
|
channel: entry["channel"],
|
|
|
|
|
versions_hash: entry["git-ref"],
|
|
|
|
|
version: entry["version"],
|
|
|
|
|
timestamp: entry["timestamp"],
|
|
|
|
|
min_client_version: entry["required_client"]
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ui_versions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function download_ui_pack(version: local_ui_cache.UIPackInfo) : Promise<local_ui_cache.CachedUIPack> {
|
|
|
|
|
const target_file = path.join(local_ui_cache.cache_path(), version.channel + "_" + version.versions_hash + "_" + version.timestamp + ".tar.gz");
|
|
|
|
|
if(await fs.pathExists(target_file)) {
|
|
|
|
|
try {
|
|
|
|
|
await fs.remove(target_file);
|
|
|
|
|
} 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 delete old file";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await fs.mkdirp(path.dirname(target_file));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to create target UI pack download directory at %s: %o", path.dirname(target_file), error);
|
|
|
|
|
throw "failed to create target directories";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
|
let fstream: WriteStream;
|
|
|
|
|
try {
|
|
|
|
|
request.get(remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
"type": "ui-download",
|
|
|
|
|
"git-ref": version.versions_hash,
|
|
|
|
|
"version": version.version,
|
|
|
|
|
"timestamp": version.timestamp,
|
|
|
|
|
"channel": version.channel
|
|
|
|
|
}), {
|
|
|
|
|
timeout: TIMEOUT
|
|
|
|
|
}).on('response', function(response: request.Response) {
|
|
|
|
|
if(response.statusCode != 200)
|
|
|
|
|
reject(response.statusCode + " " + response.statusMessage);
|
|
|
|
|
}).on('error', error => {
|
|
|
|
|
reject(error);
|
|
|
|
|
}).pipe(fstream = fs.createWriteStream(target_file)).on('finish', () => {
|
|
|
|
|
try { fstream.close(); } catch (e) { }
|
|
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
try { fstream.close(); } catch (e) { }
|
|
|
|
|
|
|
|
|
|
reject(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cache = await local_ui_cache.load();
|
|
|
|
|
const info: local_ui_cache.CachedUIPack = {
|
|
|
|
|
pack_info: version,
|
|
|
|
|
local_file_path: target_file,
|
|
|
|
|
local_checksum: "none", //TODO!
|
|
|
|
|
invalid_reason: undefined,
|
|
|
|
|
status: "valid",
|
|
|
|
|
download_timestamp: Date.now()
|
|
|
|
|
};
|
|
|
|
|
cache.cached_ui_packs.push(info);
|
|
|
|
|
await local_ui_cache.save();
|
|
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ui_save_cache(cache: LocalUICache) {
|
|
|
|
|
const file = path.join(cache_directory(), "data.json");
|
|
|
|
|
if(!fs.existsSync(path.dirname(file)))
|
|
|
|
|
await fs.mkdirs(path.dirname(file));
|
|
|
|
|
await fs.writeJson(file, cache);
|
|
|
|
|
async function ui_pack_usable(version: local_ui_cache.CachedUIPack) : Promise<boolean> {
|
|
|
|
|
if(version.status !== "valid") return false;
|
|
|
|
|
return await fs.pathExists(version.local_file_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function get_ui_pack(channel?: string) : Promise<UIVersion[] | UIVersion> {
|
|
|
|
|
return await new Promise<UIVersion[] | UIVersion>((resolve, reject) => {
|
|
|
|
|
const url = remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
type: "ui-info"
|
|
|
|
|
});
|
|
|
|
|
request.get(url, {
|
|
|
|
|
timeout: TIMEOUT
|
|
|
|
|
}, (error, response, body: string) => {
|
|
|
|
|
try {
|
|
|
|
|
response = response || {statusCode: -1};
|
|
|
|
|
async function unpack_local_ui_pack(version: local_ui_cache.CachedUIPack) : Promise<string> {
|
|
|
|
|
if(!await ui_pack_usable(version))
|
|
|
|
|
throw "UI pack has been invalidated";
|
|
|
|
|
|
|
|
|
|
if(error) { throw error; }
|
|
|
|
|
if(response.statusCode != 200) { throw "invalid status code " + response.statusCode + " for " + url; }
|
|
|
|
|
if(!body) throw "invalid response body";
|
|
|
|
|
|
|
|
|
|
let result: UIVersion[] = [];
|
|
|
|
|
|
|
|
|
|
const json = JSON.parse(body) || {success: false, msg: "invalid body"};
|
|
|
|
|
if(!json["success"]) throw "Failed to get ui info: " + json["msg"];
|
|
|
|
|
|
|
|
|
|
for(const entry of json["versions"]) {
|
|
|
|
|
if(!channel || entry["channel"] == channel)
|
|
|
|
|
result.push({
|
|
|
|
|
channel: entry["channel"],
|
|
|
|
|
version: entry["version"],
|
|
|
|
|
git_hash: entry["git-ref"],
|
|
|
|
|
timestamp: entry["timestamp"],
|
|
|
|
|
required_client: entry["required_client"]
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(result.length == 0 && channel) result.push(undefined);
|
|
|
|
|
const res = channel ? result[0] : result;
|
|
|
|
|
ui_load_cache().then(async cache => {
|
|
|
|
|
cache.fetch_history = cache.fetch_history || {} as any;
|
|
|
|
|
cache.fetch_history.timestamp = Date.now();
|
|
|
|
|
cache.fetch_history.status = 0;
|
|
|
|
|
cache.remote_index = res as any;
|
|
|
|
|
cache.remote_index_channel = channel;
|
|
|
|
|
await ui_save_cache(cache);
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
console.warn("Failed to save UI cache info: %o", error);
|
|
|
|
|
resolve(res);
|
|
|
|
|
}).then(err => resolve(res));
|
|
|
|
|
} catch(error) {
|
|
|
|
|
reject(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function download_ui_pack(version: UIVersion) : Promise<void> {
|
|
|
|
|
const directory = cache_directory();
|
|
|
|
|
const file = ui_file_path(version);
|
|
|
|
|
await fs.mkdirs(directory);
|
|
|
|
|
|
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
|
request.get(remote_url() + "api.php?" + querystring.stringify({
|
|
|
|
|
type: "ui-download",
|
|
|
|
|
"git-ref": version.git_hash,
|
|
|
|
|
version: version.version,
|
|
|
|
|
timestamp: version.timestamp,
|
|
|
|
|
channel: version.channel
|
|
|
|
|
}), {
|
|
|
|
|
timeout: TIMEOUT
|
|
|
|
|
}).on('response', function(response) {
|
|
|
|
|
if(response.statusCode != 200) { reject("Failed to download UI files (Status code " + response.statusCode + ")"); }
|
|
|
|
|
}).on('error', error => {
|
|
|
|
|
reject("Failed to download UI files: " + error);
|
|
|
|
|
}).pipe(fs.createWriteStream(file)).on('finish', () => {
|
|
|
|
|
ui_load_cache().then(cache => {
|
|
|
|
|
cache.versions.push({
|
|
|
|
|
checksum: "undefined",
|
|
|
|
|
tar_file: file,
|
|
|
|
|
download_timestamp: Date.now(),
|
|
|
|
|
version: version
|
|
|
|
|
});
|
|
|
|
|
return ui_save_cache(cache);
|
|
|
|
|
}).catch(error => resolve()).then(() => resolve());
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ui_pack_exists(version: UIVersion) : boolean {
|
|
|
|
|
return fs.existsSync(ui_file_path(version));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function unpack_cached(version: UIVersion) : Promise<string> {
|
|
|
|
|
const file = ui_file_path(version);
|
|
|
|
|
if(!fs.existsSync(file)) throw "missing file";
|
|
|
|
|
|
|
|
|
|
const target_dir = path.join(working_directory(), version.channel + "_" + version.timestamp);
|
|
|
|
|
if(fs.existsSync(target_dir)) fs.removeSync(target_dir);
|
|
|
|
|
|
|
|
|
|
await fs.mkdirs(target_dir);
|
|
|
|
|
const target_directory = await generate_tmp();
|
|
|
|
|
if(!await fs.pathExists(target_directory))
|
|
|
|
|
throw "failed to create temporary directory";
|
|
|
|
|
|
|
|
|
|
const gunzip = zlib.createGunzip();
|
|
|
|
|
const extract = tar.extract();
|
|
|
|
|
const fpipe = fs.createReadStream(file);
|
|
|
|
|
let fpipe: fs.ReadStream;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
fpipe = fs.createReadStream(version.local_file_path);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to open UI pack at %s: %o", version.local_file_path, error);
|
|
|
|
|
throw "failed to open UI pack";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extract.on('entry', function(header: tar.Headers, stream, next) {
|
|
|
|
|
if(header.type == 'file') {
|
|
|
|
|
const target_file = path.join(target_dir, header.name);
|
|
|
|
|
const target_file = path.join(target_directory, header.name);
|
|
|
|
|
if(!fs.existsSync(path.dirname(target_file))) fs.mkdirsSync(path.dirname(target_file));
|
|
|
|
|
|
|
|
|
|
stream.on('end', () => setImmediate(next));
|
|
|
|
|
const wfpipe = fs.createWriteStream(target_file);
|
|
|
|
|
stream.pipe(wfpipe);
|
|
|
|
|
} else if(header.type == 'directory') {
|
|
|
|
|
if(fs.existsSync(path.join(target_dir, header.name)))
|
|
|
|
|
if(fs.existsSync(path.join(target_directory, header.name)))
|
|
|
|
|
setImmediate(next);
|
|
|
|
|
fs.mkdirs(path.join(target_dir, header.name)).catch(error => {
|
|
|
|
|
console.warn("Failed to create unpacking fir " + path.join(target_dir, header.name));
|
|
|
|
|
fs.mkdirs(path.join(target_directory, header.name)).catch(error => {
|
|
|
|
|
console.warn("Failed to create unpacking dir " + path.join(target_directory, header.name));
|
|
|
|
|
console.error(error);
|
|
|
|
|
}).then(() => setImmediate(next));
|
|
|
|
|
} else {
|
|
|
|
@@ -360,162 +341,254 @@ async function unpack_cached(version: UIVersion) : Promise<string> {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const finish_promise = new Promise(resolve => {
|
|
|
|
|
const finish_promise = new Promise((resolve, reject) => {
|
|
|
|
|
extract.on('finish', resolve);
|
|
|
|
|
extract.on('error', event => {
|
|
|
|
|
if(!event) return;
|
|
|
|
|
throw event;
|
|
|
|
|
reject(event);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fpipe.pipe(gunzip).pipe(extract);
|
|
|
|
|
await finish_promise;
|
|
|
|
|
|
|
|
|
|
return target_dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function cleanup() {
|
|
|
|
|
if(await fs.pathExists(local_path))
|
|
|
|
|
await fs.remove(local_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function load_files(channel: string, static_cb: (message: string, index: number) => any) : Promise<String> {
|
|
|
|
|
const type = parseInt(process_args.has_value(Arguments.UPDATER_UI_LOAD_TYPE) ? process_args.value(Arguments.UPDATER_UI_LOAD_TYPE) : "-1");
|
|
|
|
|
if(type == 0 || !is_debug) {
|
|
|
|
|
console.log("Loading ui package");
|
|
|
|
|
|
|
|
|
|
static_cb("Fetching info", 0);
|
|
|
|
|
const cache = await ui_load_cache();
|
|
|
|
|
console.log("Local cache: %o", cache);
|
|
|
|
|
|
|
|
|
|
let ui_info: UIVersion;
|
|
|
|
|
try {
|
|
|
|
|
ui_info = await get_ui_pack(channel) as UIVersion;
|
|
|
|
|
} catch(error) {
|
|
|
|
|
if(error instanceof Error)
|
|
|
|
|
console.error("Failed to fetch ui info: %s. Using cached info!", error.message);
|
|
|
|
|
else
|
|
|
|
|
console.error("Failed to fetch ui info: %o. Using cached info!", error);
|
|
|
|
|
}
|
|
|
|
|
if(!ui_info) {
|
|
|
|
|
if(cache && !process_args.has_flag(Arguments.UPDATER_UI_NO_CACHE)) {
|
|
|
|
|
if(Array.isArray(cache.remote_index)) {
|
|
|
|
|
for(const index of cache.remote_index) {
|
|
|
|
|
if(index && index.channel == "release") {
|
|
|
|
|
ui_info = index;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
//TODO: test channel?
|
|
|
|
|
ui_info = cache.remote_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(ui_info) {
|
|
|
|
|
console.debug("Found local UI pack.");
|
|
|
|
|
} else {
|
|
|
|
|
//Test for the client shipped ui pack
|
|
|
|
|
try {
|
|
|
|
|
console.info("Looking for client shipped UI pack.");
|
|
|
|
|
ui_info = await client_shipped_ui();
|
|
|
|
|
if(!ui_info)
|
|
|
|
|
throw "failed to load info";
|
|
|
|
|
console.info("Using client shipped UI pack because we've no active internet connection.")
|
|
|
|
|
} catch(error) {
|
|
|
|
|
console.warn("Failed to load client shipped UI pack: %o", error);
|
|
|
|
|
throw "Failed to load UI pack from cache!\nPlease ensure a valid internet connection.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static_cb("Searching cache for file", .33);
|
|
|
|
|
console.log("Loading UI from data: %o. Target path: %s", ui_info, ui_file_path(ui_info));
|
|
|
|
|
if(ui_info.required_client && !process_args.has_flag(Arguments.DEBUG)) {
|
|
|
|
|
const ui_vers = parse_version(ui_info.required_client);
|
|
|
|
|
const current_vers = await current_version();
|
|
|
|
|
console.log("Checking required client version (Required: %s, Version: %s)", ui_vers.toString(true), current_vers.toString(true));
|
|
|
|
|
if(ui_vers.newer_than(current_vers) && !current_vers.in_dev()) {
|
|
|
|
|
const local_available = cache && cache.local_index ? ui_pack_exists(cache.local_index) : undefined;
|
|
|
|
|
|
|
|
|
|
const result = await electron.dialog.showMessageBox({
|
|
|
|
|
type: "question",
|
|
|
|
|
message:
|
|
|
|
|
"Local client is outdated.\n" +
|
|
|
|
|
"Newer UI packs (>= " + ui_info.version + ") require client " + ui_info.required_client + "\n" +
|
|
|
|
|
"Do you want to upgrade?",
|
|
|
|
|
title: "Client outdated!",
|
|
|
|
|
buttons: ["yes", local_available ? "ignore and use last possible (" + cache.local_index.version + ")" : "close client"]
|
|
|
|
|
} as MessageBoxOptions);
|
|
|
|
|
if(result.response == 0) {
|
|
|
|
|
await execute_graphical(channel, true);
|
|
|
|
|
throw "client outdated";
|
|
|
|
|
} else {
|
|
|
|
|
if(!local_available) {
|
|
|
|
|
electron.app.exit(1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ui_info = cache.local_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!ui_pack_exists(ui_info)) {
|
|
|
|
|
console.log("Ui version does not locally exists. Downloading new one");
|
|
|
|
|
static_cb("Downloading files", .34);
|
|
|
|
|
await download_ui_pack(ui_info);
|
|
|
|
|
console.log("Download completed!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("Unpacking cached ui info");
|
|
|
|
|
static_cb("Unpacking files", .66);
|
|
|
|
|
const target_path = await unpack_cached(ui_info);
|
|
|
|
|
cache.local_index = ui_info;
|
|
|
|
|
await ui_save_cache(cache);
|
|
|
|
|
|
|
|
|
|
console.log("Unpacked. Target path: %s", target_path);
|
|
|
|
|
static_cb("UI loaded", 1);
|
|
|
|
|
|
|
|
|
|
return path.join(target_path, "index.html");
|
|
|
|
|
} else {
|
|
|
|
|
console.log("Loading file by file");
|
|
|
|
|
|
|
|
|
|
static_cb("Fetching files", 0);
|
|
|
|
|
let files;
|
|
|
|
|
try {
|
|
|
|
|
files = await download_raw_app_files()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw "Failed to get file list: " + error;
|
|
|
|
|
}
|
|
|
|
|
console.log("Get raw files:");
|
|
|
|
|
let futures: Promise<void>[] = [];
|
|
|
|
|
let finish_count = files.length;
|
|
|
|
|
static_cb("Downloading files", 0);
|
|
|
|
|
|
|
|
|
|
const chunk_size = 5;
|
|
|
|
|
let left = [...files];
|
|
|
|
|
while(left.length > 0) {
|
|
|
|
|
const queue = left.slice(0, chunk_size);
|
|
|
|
|
left = left.slice(chunk_size);
|
|
|
|
|
|
|
|
|
|
futures = [];
|
|
|
|
|
for(const file of queue) {
|
|
|
|
|
console.log("Start downloading %s (%s)", file.name, file.path);
|
|
|
|
|
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
futures.push(file.local_url().then(data => {
|
|
|
|
|
console.log("Downloaded %s (%s) (%ims)", file.name, file.path, Date.now() - start);
|
|
|
|
|
static_cb("Downloading files", finish_count / files.length);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await Promise.all(futures);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw "Failed to download files: " + error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await generate_tmp() + "index.html"; /* entry point */
|
|
|
|
|
try {
|
|
|
|
|
await finish_promise;
|
|
|
|
|
} catch(error) {
|
|
|
|
|
console.error("Failed to extract UI files to %s: %o", target_directory, error);
|
|
|
|
|
throw "failed to unpack the UI pack";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return target_directory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load_files_from_dev_server(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> {
|
|
|
|
|
stats_update("Fetching files", 0);
|
|
|
|
|
let files: VersionedFile[];
|
|
|
|
|
try {
|
|
|
|
|
files = await download_raw_app_files()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log("Failed to fetch raw UI file list: %o", error);
|
|
|
|
|
let msg;
|
|
|
|
|
if(error instanceof Error)
|
|
|
|
|
msg = error.message;
|
|
|
|
|
else if(typeof error === "string")
|
|
|
|
|
msg = error;
|
|
|
|
|
throw "failed to get file list" + (msg ? " (" + msg + ")" : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const max_simultaneously_downloads = 8;
|
|
|
|
|
let pending_files: VersionedFile[] = files.slice(0);
|
|
|
|
|
let current_downloads: {[key: string]:Promise<void>} = {};
|
|
|
|
|
|
|
|
|
|
const update_download_status = () => {
|
|
|
|
|
const indicator = (pending_files.length + Object.keys(current_downloads).length) / files.length;
|
|
|
|
|
stats_update("Downloading raw UI files", 1 - indicator);
|
|
|
|
|
};
|
|
|
|
|
update_download_status();
|
|
|
|
|
|
|
|
|
|
let errors: { file: VersionedFile; error: any }[] = [];
|
|
|
|
|
while(pending_files.length > 0) {
|
|
|
|
|
while(pending_files.length > 0 && Object.keys(current_downloads).length < max_simultaneously_downloads) {
|
|
|
|
|
const file = pending_files.pop();
|
|
|
|
|
current_downloads[file.hash] = file.local_url().catch(error => {
|
|
|
|
|
errors.push({ file: file, error: error});
|
|
|
|
|
}).then(() => {
|
|
|
|
|
delete current_downloads[file.hash];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update_download_status();
|
|
|
|
|
await Promise.race(Object.keys(current_downloads).map(e => current_downloads[e]));
|
|
|
|
|
|
|
|
|
|
if(errors.length > 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* await full finish */
|
|
|
|
|
while(Object.keys(current_downloads).length > 0) {
|
|
|
|
|
update_download_status();
|
|
|
|
|
await Promise.race(Object.keys(current_downloads).map(e => current_downloads[e]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(errors.length > 0) {
|
|
|
|
|
console.log("Failed to load UI files (%d):", errors.length);
|
|
|
|
|
for(const error of errors)
|
|
|
|
|
console.error(" - %s: %o", path.join(error.file.path + error.file.name), error.error);
|
|
|
|
|
throw "failed to download file " + path.join(errors[0].file.path + errors[0].file.name) + " (" + errors[0].error + ")\nView console for a full error report.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("Successfully loaded UI files from remote server.");
|
|
|
|
|
/* generate_tmp has already been called an its the file destination */
|
|
|
|
|
return path.join(await generate_tmp(), "index.html"); /* entry point */
|
|
|
|
|
}
|
|
|
|
|
async function load_bundles_ui_pack(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> {
|
|
|
|
|
stats_update("Query local UI pack info", .33);
|
|
|
|
|
const bundles_ui = await client_shipped_ui();
|
|
|
|
|
if(!bundles_ui) throw "client has no bundled UI pack";
|
|
|
|
|
|
|
|
|
|
stats_update("Unpacking bundled UI", .66);
|
|
|
|
|
const result = await unpack_local_ui_pack(bundles_ui);
|
|
|
|
|
stats_update("Local UI pack loaded", 1);
|
|
|
|
|
console.log("Loaded bundles UI pack successfully. Version: {timestamp: %d, hash: %s}", bundles_ui.pack_info.timestamp, bundles_ui.pack_info.versions_hash);
|
|
|
|
|
return path.join(result, "index.html");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load_cached_or_remote_ui_pack(channel: string, stats_update: (message: string, index: number) => any, ignore_new_version_timestamp: boolean) : Promise<String> {
|
|
|
|
|
stats_update("Fetching info", 0);
|
|
|
|
|
const ui_cache = await local_ui_cache.load();
|
|
|
|
|
const bundles_ui = await client_shipped_ui();
|
|
|
|
|
const client_version = await current_version();
|
|
|
|
|
|
|
|
|
|
let available_versions: local_ui_cache.CachedUIPack[] = ui_cache.cached_ui_packs.filter(e => {
|
|
|
|
|
if(e.status !== "valid")
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if(bundles_ui) {
|
|
|
|
|
if(e.pack_info.timestamp <= bundles_ui.download_timestamp)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const required_version = parse_version(e.pack_info.min_client_version);
|
|
|
|
|
return client_version.newer_than(required_version) || client_version.equals(required_version);
|
|
|
|
|
});
|
|
|
|
|
if(process_args.has_flag(Arguments.UPDATER_UI_NO_CACHE))
|
|
|
|
|
available_versions = [];
|
|
|
|
|
|
|
|
|
|
let remote_version_dropped = false;
|
|
|
|
|
/* remote version gathering */
|
|
|
|
|
{
|
|
|
|
|
stats_update("Loading remote info", .25);
|
|
|
|
|
let remote_versions: local_ui_cache.UIPackInfo[];
|
|
|
|
|
try {
|
|
|
|
|
remote_versions = await query_ui_pack_versions();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if(available_versions.length === 0)
|
|
|
|
|
throw "failed to query remote UI packs: " + error;
|
|
|
|
|
console.error("Failed to query remote UI packs: %o", error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stats_update("Parsing UI packs", .40);
|
|
|
|
|
const remote_version = remote_versions.find(e => e.channel === channel);
|
|
|
|
|
if(!remote_version && available_versions.length === 0)
|
|
|
|
|
throw "no UI pack available for channel " + channel;
|
|
|
|
|
|
|
|
|
|
let newest_local_version = available_versions.map(e => e.download_timestamp).reduce((a, b) => Math.max(a, b), bundles_ui ? bundles_ui.download_timestamp : 0);
|
|
|
|
|
const required_version = parse_version(remote_version.min_client_version);
|
|
|
|
|
if(required_version.newer_than(client_version)) {
|
|
|
|
|
const result = await electron.dialog.showMessageBox({
|
|
|
|
|
type: "question",
|
|
|
|
|
message:
|
|
|
|
|
"Your client is outdated.\n" +
|
|
|
|
|
"Newer UI packs (>= " + remote_version.version + ", " + remote_version.versions_hash + ") require client " + remote_version.min_client_version + "\n" +
|
|
|
|
|
"Do you want to update your client?",
|
|
|
|
|
title: "Client outdated!",
|
|
|
|
|
buttons: ["yes", available_versions.length === 0 ? "close client" : "ignore and use last possible"]
|
|
|
|
|
} as MessageBoxOptions);
|
|
|
|
|
|
|
|
|
|
if(result.response == 0) {
|
|
|
|
|
await execute_graphical(channel, true);
|
|
|
|
|
throw "client outdated";
|
|
|
|
|
} else {
|
|
|
|
|
if(available_versions.length === 0) {
|
|
|
|
|
electron.app.exit(1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if(remote_version.timestamp <= newest_local_version && !ignore_new_version_timestamp) {
|
|
|
|
|
/* We've already a equal or newer version. Don't use the remote version */
|
|
|
|
|
remote_version_dropped = true;
|
|
|
|
|
} else {
|
|
|
|
|
/* update is possible because the timestamp is newer than out latest local version */
|
|
|
|
|
try {
|
|
|
|
|
stats_update("Download new UI pack", .55);
|
|
|
|
|
available_versions.push(await download_ui_pack(remote_version));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to download new UI pack: %o", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stats_update("Unpacking UI", .70);
|
|
|
|
|
available_versions.sort((a, b) => a.pack_info.timestamp - b.pack_info.timestamp);
|
|
|
|
|
|
|
|
|
|
/* Only invalidate the version if any other succeeded to load. Else we might fucked up (no permission to write etc) */
|
|
|
|
|
let invalidate_versions: local_ui_cache.CachedUIPack[] = [];
|
|
|
|
|
while(available_versions.length > 0) {
|
|
|
|
|
const pack = available_versions.pop();
|
|
|
|
|
console.log("Trying to load UI pack from %s (%s). Downloaded at %s", moment(pack.pack_info.timestamp).format("llll"), moment(pack.pack_info.versions_hash).format("llll"), moment(pack.download_timestamp).format("llll"))
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const target = await unpack_local_ui_pack(pack);
|
|
|
|
|
stats_update("UI pack loaded", 1);
|
|
|
|
|
|
|
|
|
|
if(invalidate_versions.length > 0) {
|
|
|
|
|
for(const version of invalidate_versions) {
|
|
|
|
|
version.invalid_reason = "failed to unpack";
|
|
|
|
|
version.status = "invalid";
|
|
|
|
|
}
|
|
|
|
|
await local_ui_cache.save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return path.join(target, "index.html");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
invalidate_versions.push(pack);
|
|
|
|
|
console.log("Failed to unpack UI pack: %o", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(remote_version_dropped) {
|
|
|
|
|
/* try again, but this time enforce a remote download */
|
|
|
|
|
await load_cached_or_remote_ui_pack(channel, stats_update, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw "Failed to load any UI pack (local and remote)\nView the console for more details.\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum UILoaderMethod {
|
|
|
|
|
PACK,
|
|
|
|
|
BUNDLED_PACK,
|
|
|
|
|
RAW_FILES
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function load_files(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> {
|
|
|
|
|
let enforced_loading_method = parseInt(process_args.has_value(Arguments.UPDATER_UI_LOAD_TYPE) ? process_args.value(Arguments.UPDATER_UI_LOAD_TYPE) : "-1") as UILoaderMethod;
|
|
|
|
|
|
|
|
|
|
if(typeof UILoaderMethod[enforced_loading_method] !== "undefined") {
|
|
|
|
|
switch (enforced_loading_method) {
|
|
|
|
|
case UILoaderMethod.PACK:
|
|
|
|
|
return await load_cached_or_remote_ui_pack(channel, stats_update, false);
|
|
|
|
|
|
|
|
|
|
case UILoaderMethod.BUNDLED_PACK:
|
|
|
|
|
return await load_bundles_ui_pack(channel, stats_update);
|
|
|
|
|
|
|
|
|
|
case UILoaderMethod.RAW_FILES:
|
|
|
|
|
return await load_files_from_dev_server(channel, stats_update);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let first_error;
|
|
|
|
|
if(is_debug) {
|
|
|
|
|
try {
|
|
|
|
|
return await load_files_from_dev_server(channel, stats_update);
|
|
|
|
|
} catch(error) {
|
|
|
|
|
console.warn("Failed to load raw UI files: %o", error);
|
|
|
|
|
first_error = first_error || error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return await load_cached_or_remote_ui_pack(channel, stats_update, false);
|
|
|
|
|
} catch(error) {
|
|
|
|
|
console.warn("Failed to load cached/remote UI pack: %o", error);
|
|
|
|
|
first_error = first_error || error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return await load_bundles_ui_pack(channel, stats_update);
|
|
|
|
|
} catch(error) {
|
|
|
|
|
console.warn("Failed to load bundles UI pack: %o", error);
|
|
|
|
|
first_error = first_error || error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw first_error;
|
|
|
|
|
}
|