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-status` | Object | Emitted when data is received while downloading |
| `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?

View file

@ -5,6 +5,7 @@ const request = require('request');
const checksum = require('checksum');
const zip = require('adm-zip');
const child = require('child_process');
let counter = 0;
class Handler {
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 => {
shelljs.mkdir('-p', directory);
const _request = this.baseRequest(url);
_request.on('error', (error) => {
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${error}`);
resolve({
failed: true,
asset: {
url: url,
directory: directory,
name: name
}
});
_request.on('error', async (error) => {
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${error}.` +
` Retrying... ${retry}`);
if (retry) await this.downloadAsync(url, directory, name, false);
resolve();
});
_request.on('data', (data) => {
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', {
"name": name,
"current": Math.round(size / 10000),
@ -50,20 +67,18 @@ class Handler {
file.once('finish', () => {
this.client.emit('download', name);
resolve({failed: false, asset: null});
resolve({
failed: false,
asset: null
});
});
file.on('error', (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));
resolve({
failed: true,
asset: {
url: url,
directory: directory,
name: name
}
});
file.on('error', async (e) => {
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}.` +
` Retrying... ${retry}`);
if (fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name));
if (retry) await this.downloadAsync(url, directory, name, false);
resolve();
});
});
}
@ -77,7 +92,7 @@ class Handler {
getVersion() {
return new Promise(resolve => {
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);
resolve(this.version);
return;
@ -90,7 +105,7 @@ class Handler {
const parsed = JSON.parse(body);
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) => {
if (error) resolve(error);
@ -105,7 +120,7 @@ class Handler {
}
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`);
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() {
return new Promise(async(resolve) => {
const failed = [];
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`);
}
@ -133,17 +146,16 @@ class Handler {
const subAsset = path.join(assetDirectory, 'objects', subhash);
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);
if(download.failed) failed.push(download.asset);
await this.downloadAsync(`${this.options.overrides.url.resource}/${subhash}/${hash}`, subAsset, hash);
counter = counter + 1;
this.client.emit('progress', {
type: 'assets',
task: counter,
total: Object.keys(index.objects).length
})
}
}));
// 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)))
}
counter = 0;
// Copy assets to legacy if it's an older Minecraft version.
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))) {
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');
resolve();
@ -197,32 +216,49 @@ class Handler {
if(!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) {
shelljs.mkdir('-p', nativeDirectory);
await Promise.all(this.version.libraries.map(async (lib) => {
if (!lib.downloads.classifiers) return;
if (this.parseRule(lib)) return;
const natives = () => {
return new Promise(async resolve => {
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'
? lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos']
: lib.downloads.classifiers[`natives-${this.getOS()}`];
const native = this.getOS() === 'osx' ?
lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos'] :
lib.downloads.classifiers[`natives-${this.getOS()}`];
if (native) {
const name = native.path.split('/').pop();
natives.push(native);
}));
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);
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');
}
counter = 0;
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`);
resolve(nativeDirectory);
});
@ -258,6 +294,8 @@ class Handler {
if(fs.existsSync(path.join(jarPath, name))) {
paths.push(`${jarPath}${path.sep}${name}`);
counter = counter + 1;
this.client.emit('progress', { type: 'forge', task: counter, total: forge.libraries.length});
return;
}
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath);
@ -265,8 +303,15 @@ class Handler {
await this.downloadAsync(downloadLink, jarPath, 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');
return {paths, forge};
@ -297,14 +342,31 @@ class Handler {
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}`);
}));
counter = 0;
}
await Promise.all(this.version.libraries.map(async (_lib) => {
if(!_lib.downloads.artifact) return;
if(this.parseRule(_lib)) return;
const parsedClasses = () => {
return new Promise(async resolve => {
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 libraryUrl = _lib.downloads.artifact.url;
const libraryHash = _lib.downloads.artifact.sha1;
@ -317,9 +379,15 @@ class Handler {
await this.downloadAsync(libraryUrl, directory, name);
}
counter = counter + 1;
this.client.emit('progress', {
type: 'classes',
task: counter,
total: parsed.length
});
libs.push(libraryDirectory);
}));
counter = 0;
this.client.emit('debug', '[MCLC]: Collected class paths');
resolve(libs)

View file

@ -28,6 +28,12 @@ class MCLCore extends EventEmitter {
await void(0);
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)) {
this.emit('debug', '[MCLC]: Attempting to create root folder');

View file

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