Progress bar, java checker, changes to how things are downloaded

This commit is contained in:
Pierce 2019-08-26 16:32:32 -04:00
parent eec04746e6
commit 61f8af7a20
4 changed files with 136 additions and 61 deletions

View file

@ -164,6 +164,7 @@ You'll need to provide the folder created in the versions if you're running the
| `download` | String | Emitted when a file successfully downloads | | `download` | String | Emitted when a file successfully downloads |
| `download-status` | Object | Emitted when data is received while downloading | | `download-status` | Object | Emitted when data is received while downloading |
| `debug` | String | Emitted when functions occur, made to help debug if errors occur | | `debug` | String | Emitted when functions occur, made to help debug if errors occur |
| `progress` | Object | Emitted when files are being downloaded in order. (Assets, Forge Deps, Natives, Classes)|
#### What should it look like running from console? #### What should it look like running from console?

View file

@ -5,6 +5,7 @@ const request = require('request');
const checksum = require('checksum'); const checksum = require('checksum');
const zip = require('adm-zip'); const zip = require('adm-zip');
const child = require('child_process'); const child = require('child_process');
let counter = 0;
class Handler { class Handler {
constructor(client) { constructor(client) {
@ -17,27 +18,43 @@ class Handler {
}); });
} }
downloadAsync(url, directory, name) { checkJava(java) {
return new Promise(resolve => {
let spawned = false;
const javaVer = child.spawn(java, ["-version"]);
javaVer.stderr.on('data', (data) => {
if (spawned) return;
spawned = true;
this.client.emit('debug', `[MCLC]: Using Java version ${data.toString().match(/"(.*?)"/).pop()}`);
resolve({
run: true
});
});
javaVer.on('error', (e) => {
resolve({
run: false,
message: e
})
});
});
}
downloadAsync(url, directory, name, retry = true) {
return new Promise(resolve => { return new Promise(resolve => {
shelljs.mkdir('-p', directory); shelljs.mkdir('-p', directory);
const _request = this.baseRequest(url); const _request = this.baseRequest(url);
_request.on('error', (error) => { _request.on('error', async (error) => {
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${error}`); this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${error}.` +
resolve({ ` Retrying... ${retry}`);
failed: true, if (retry) await this.downloadAsync(url, directory, name, false);
asset: { resolve();
url: url,
directory: directory,
name: name
}
});
}); });
_request.on('data', (data) => { _request.on('data', (data) => {
let size = 0; let size = 0;
if(fs.existsSync(path.join(directory, name))) size = fs.statSync(path.join(directory, name))["size"]; if (fs.existsSync(path.join(directory, name))) size = fs.statSync(path.join(directory, name))["size"];
this.client.emit('download-status', { this.client.emit('download-status', {
"name": name, "name": name,
"current": Math.round(size / 10000), "current": Math.round(size / 10000),
@ -50,20 +67,18 @@ class Handler {
file.once('finish', () => { file.once('finish', () => {
this.client.emit('download', name); this.client.emit('download', name);
resolve({failed: false, asset: null}); resolve({
failed: false,
asset: null
});
}); });
file.on('error', (e) => { file.on('error', async (e) => {
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}`); this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}.` +
if(fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name)); ` Retrying... ${retry}`);
resolve({ if (fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name));
failed: true, if (retry) await this.downloadAsync(url, directory, name, false);
asset: { resolve();
url: url,
directory: directory,
name: name
}
});
}); });
}); });
} }
@ -77,7 +92,7 @@ class Handler {
getVersion() { getVersion() {
return new Promise(resolve => { return new Promise(resolve => {
const versionJsonPath = this.options.overrides.versionJson || path.join(this.options.directory, `${this.options.version.number}.json`); const versionJsonPath = this.options.overrides.versionJson || path.join(this.options.directory, `${this.options.version.number}.json`);
if(fs.existsSync(versionJsonPath)) { if (fs.existsSync(versionJsonPath)) {
this.version = require(versionJsonPath); this.version = require(versionJsonPath);
resolve(this.version); resolve(this.version);
return; return;
@ -90,7 +105,7 @@ class Handler {
const parsed = JSON.parse(body); const parsed = JSON.parse(body);
for (const desiredVersion in parsed.versions) { for (const desiredVersion in parsed.versions) {
if(parsed.versions[desiredVersion].id === this.options.version.number) { if (parsed.versions[desiredVersion].id === this.options.version.number) {
request.get(parsed.versions[desiredVersion].url, (error, response, body) => { request.get(parsed.versions[desiredVersion].url, (error, response, body) => {
if (error) resolve(error); if (error) resolve(error);
@ -105,7 +120,7 @@ class Handler {
} }
getJar() { getJar() {
return new Promise(async (resolve)=> { return new Promise(async (resolve) => {
await this.downloadAsync(this.version.downloads.client.url, this.options.directory, `${this.options.version.number}.jar`); await this.downloadAsync(this.version.downloads.client.url, this.options.directory, `${this.options.version.number}.jar`);
fs.writeFileSync(path.join(this.options.directory, `${this.options.version.number}.json`), JSON.stringify(this.version, null, 4)); fs.writeFileSync(path.join(this.options.directory, `${this.options.version.number}.json`), JSON.stringify(this.version, null, 4));
@ -118,8 +133,6 @@ class Handler {
getAssets() { getAssets() {
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
const failed = [];
if(!fs.existsSync(path.join(this.options.root, 'assets', 'indexes', `${this.version.assetIndex.id}.json`))) { if(!fs.existsSync(path.join(this.options.root, 'assets', 'indexes', `${this.version.assetIndex.id}.json`))) {
await this.downloadAsync(this.version.assetIndex.url, path.join(this.options.root, 'assets', 'indexes'), `${this.version.assetIndex.id}.json`); await this.downloadAsync(this.version.assetIndex.url, path.join(this.options.root, 'assets', 'indexes'), `${this.version.assetIndex.id}.json`);
} }
@ -133,17 +146,16 @@ class Handler {
const subAsset = path.join(assetDirectory, 'objects', subhash); const subAsset = path.join(assetDirectory, 'objects', subhash);
if(!fs.existsSync(path.join(subAsset, hash)) || !await this.checkSum(hash, path.join(subAsset, hash))) { if(!fs.existsSync(path.join(subAsset, hash)) || !await this.checkSum(hash, path.join(subAsset, hash))) {
const download = await this.downloadAsync(`${this.options.overrides.url.resource}/${subhash}/${hash}`, subAsset, hash); await this.downloadAsync(`${this.options.overrides.url.resource}/${subhash}/${hash}`, subAsset, hash);
counter = counter + 1;
if(download.failed) failed.push(download.asset); this.client.emit('progress', {
type: 'assets',
task: counter,
total: Object.keys(index.objects).length
})
} }
})); }));
counter = 0;
// why do we have this? B/c sometimes Minecraft's resource site times out!
if(failed) {
this.client.emit('debug', '[MCLC]: Attempting to download failed assets');
await Promise.all(failed.map(async asset => await this.downloadAsync(asset.url, asset.directory, asset.name)))
}
// Copy assets to legacy if it's an older Minecraft version. // Copy assets to legacy if it's an older Minecraft version.
if(this.version.assets === "legacy" || this.version.assets === "pre-1.6") { if(this.version.assets === "legacy" || this.version.assets === "pre-1.6") {
@ -164,8 +176,15 @@ class Handler {
if (!fs.existsSync(path.join(assetDirectory, 'legacy', asset))) { if (!fs.existsSync(path.join(assetDirectory, 'legacy', asset))) {
fs.copyFileSync(path.join(subAsset, hash), path.join(assetDirectory, 'legacy', asset)) fs.copyFileSync(path.join(subAsset, hash), path.join(assetDirectory, 'legacy', asset))
} }
counter = counter + 1;
this.client.emit('progress', {
type: 'assets-copy',
task: counter,
total: Object.keys(index.objects).length
})
})); }));
} }
counter = 0;
this.client.emit('debug', '[MCLC]: Downloaded assets'); this.client.emit('debug', '[MCLC]: Downloaded assets');
resolve(); resolve();
@ -197,32 +216,49 @@ class Handler {
if(!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) { if(!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) {
shelljs.mkdir('-p', nativeDirectory); shelljs.mkdir('-p', nativeDirectory);
await Promise.all(this.version.libraries.map(async (lib) => { const natives = () => {
if (!lib.downloads.classifiers) return; return new Promise(async resolve => {
if (this.parseRule(lib)) return; const natives = [];
await Promise.all(this.version.libraries.map(async (lib) => {
if (!lib.downloads.classifiers) return;
if (this.parseRule(lib)) return;
const native = this.getOS() === 'osx' const native = this.getOS() === 'osx' ?
? lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos'] lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos'] :
: lib.downloads.classifiers[`natives-${this.getOS()}`]; lib.downloads.classifiers[`natives-${this.getOS()}`];
if (native) { natives.push(native);
const name = native.path.split('/').pop(); }));
resolve (natives);
})
};
const stat = await natives();
await Promise.all(stat.map(async (native) => {
const name = native.path.split('/').pop();
await this.downloadAsync(native.url, nativeDirectory, name);
if (!await this.checkSum(native.sha1, path.join(nativeDirectory, name))) {
await this.downloadAsync(native.url, nativeDirectory, name); await this.downloadAsync(native.url, nativeDirectory, name);
if(!await this.checkSum(native.sha1, path.join(nativeDirectory, name))) {
await this.downloadAsync(native.url, nativeDirectory, name);
}
try {new zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true);} catch(e) {
// Only doing a console.warn since a stupid error happens. You can basically ignore this.
// if it says Invalid file name, just means two files were downloaded and both were deleted.
// All is well.
console.warn(e);
}
shelljs.rm(path.join(nativeDirectory, name));
} }
try {
new zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true);
} catch (e) {
// Only doing a console.warn since a stupid error happens. You can basically ignore this.
// if it says Invalid file name, just means two files were downloaded and both were deleted.
// All is well.
console.warn(e);
}
shelljs.rm(path.join(nativeDirectory, name));
counter = counter + 1;
this.client.emit('progress', {
type: 'natives',
task: counter,
total: stat.length
})
})); }));
this.client.emit('debug', '[MCLC]: Downloaded and extracted natives'); this.client.emit('debug', '[MCLC]: Downloaded and extracted natives');
} }
counter = 0;
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`); this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`);
resolve(nativeDirectory); resolve(nativeDirectory);
}); });
@ -258,6 +294,8 @@ class Handler {
if(fs.existsSync(path.join(jarPath, name))) { if(fs.existsSync(path.join(jarPath, name))) {
paths.push(`${jarPath}${path.sep}${name}`); paths.push(`${jarPath}${path.sep}${name}`);
counter = counter + 1;
this.client.emit('progress', { type: 'forge', task: counter, total: forge.libraries.length});
return; return;
} }
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath); if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath);
@ -265,8 +303,15 @@ class Handler {
await this.downloadAsync(downloadLink, jarPath, name); await this.downloadAsync(downloadLink, jarPath, name);
paths.push(`${jarPath}${path.sep}${name}`); paths.push(`${jarPath}${path.sep}${name}`);
counter = counter + 1;
this.client.emit('progress', {
type: 'natives-forge',
task: counter,
total: forge.libraries.length
})
})); }));
counter = 0;
this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies'); this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies');
return {paths, forge}; return {paths, forge};
@ -297,14 +342,31 @@ class Handler {
await this.downloadAsync(url, jarPath, name); await this.downloadAsync(url, jarPath, name);
} }
} }
counter = counter + 1;
this.client.emit('progress', {
type: 'classes-custom',
task: counter,
total: customJarJson.libraries.length
});
libs.push(`${jarPath}${path.sep}${name}`); libs.push(`${jarPath}${path.sep}${name}`);
})); }));
counter = 0;
} }
await Promise.all(this.version.libraries.map(async (_lib) => { const parsedClasses = () => {
if(!_lib.downloads.artifact) return; return new Promise(async resolve => {
if(this.parseRule(_lib)) return; const classes = [];
await Promise.all(this.version.libraries.map(async (_lib) => {
if(!_lib.downloads.artifact) return;
if(this.parseRule(_lib)) return;
classes.push(_lib);
}));
resolve(classes);
})
};
const parsed = await parsedClasses();
await Promise.all(parsed.map(async (_lib) => {
const libraryPath = _lib.downloads.artifact.path; const libraryPath = _lib.downloads.artifact.path;
const libraryUrl = _lib.downloads.artifact.url; const libraryUrl = _lib.downloads.artifact.url;
const libraryHash = _lib.downloads.artifact.sha1; const libraryHash = _lib.downloads.artifact.sha1;
@ -317,9 +379,15 @@ class Handler {
await this.downloadAsync(libraryUrl, directory, name); await this.downloadAsync(libraryUrl, directory, name);
} }
counter = counter + 1;
this.client.emit('progress', {
type: 'classes',
task: counter,
total: parsed.length
});
libs.push(libraryDirectory); libs.push(libraryDirectory);
})); }));
counter = 0;
this.client.emit('debug', '[MCLC]: Collected class paths'); this.client.emit('debug', '[MCLC]: Collected class paths');
resolve(libs) resolve(libs)

View file

@ -28,6 +28,12 @@ class MCLCore extends EventEmitter {
await void(0); await void(0);
this.emit('debug', `[MCLC]: MCLC version ${require(path.join(__dirname,'..', 'package.json')).version}`); this.emit('debug', `[MCLC]: MCLC version ${require(path.join(__dirname,'..', 'package.json')).version}`);
const java = await this.handler.checkJava(this.options.javaPath || 'java');
if(!java.run) {
this.emit('debug', `[MCLC]: Couldn't start Minecraft due to: ${java.message}`);
this.emit('close', 1);
return null;
}
if(!fs.existsSync(this.options.root)) { if(!fs.existsSync(this.options.root)) {
this.emit('debug', '[MCLC]: Attempting to create root folder'); this.emit('debug', '[MCLC]: Attempting to create root folder');

View file

@ -1,6 +1,6 @@
{ {
"name": "minecraft-launcher-core", "name": "minecraft-launcher-core",
"version": "3.9.1", "version": "3.10.0",
"description": "Lightweight module that downloads and runs Minecraft using javascript / NodeJS", "description": "Lightweight module that downloads and runs Minecraft using javascript / NodeJS",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {