-
Notifications
You must be signed in to change notification settings - Fork 982
Download testing emulators using a buffer #8569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
088b5ee
bf93c4c
7153bb7
cf6ce4e
840534a
01eebb7
8e44e2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,64 +53,78 @@ export abstract class Emulator { | |
return Promise.resolve(); | ||
} | ||
|
||
console.log(`Downloading emulator from [${this.binaryUrl}] ...`); | ||
const { name: tempDir } = tmp.dirSync({ unsafeCleanup: true }); | ||
const filepath = path.resolve(tempDir, this.binaryName); | ||
let cur = 0; | ||
// The emulators vary in size and are approximately 32MiB. If we define this array to be | ||
// only 32MiB, there's a risk in the future that the download will suddenly fail because there | ||
// was an increase in emulator size, and the buffer is no longer large enough. | ||
// To prevent this, we give some room for an increase in size of the emulators | ||
// and make the buffer 64MiB. | ||
let buf = new Uint8Array(2 ** 26); // 64 MiB | ||
return new Promise<void>((resolve, reject) => { | ||
tmp.dir((err: Error | null, dir: string) => { | ||
if (err) reject(err); | ||
console.log(`Created temporary directory at [${dir}].`); | ||
const filepath: string = path.resolve(dir, this.binaryName); | ||
const writer = fs.createWriteStream(filepath); | ||
console.log(`Downloading emulator from [${this.binaryUrl}] ...`); | ||
// Map the DOM's fetch Reader to node's streaming file system | ||
// operations. We will need to access class members `binaryPath` and `copyToCache` after the | ||
// download completes. It's a compilation error to pass `this` into the named function | ||
// `readChunk`, so the download operation is wrapped in a promise that we wait upon. | ||
const downloadPromise = new Promise<void>( | ||
(downloadComplete, downloadFailed) => { | ||
fetch(this.binaryUrl) | ||
.then(resp => { | ||
if (resp.status !== 200 || resp.body === null) { | ||
console.log('Download of emulator failed: ', resp.statusText); | ||
downloadFailed(); | ||
} else { | ||
const reader = resp.body.getReader(); | ||
reader.read().then(function readChunk({ done, value }): any { | ||
if (done) { | ||
downloadComplete(); | ||
} else { | ||
writer.write(value); | ||
return reader.read().then(readChunk); | ||
} | ||
}); | ||
/** | ||
* Once the download is `done` in `readChunk`, we want to set `this.binaryPath` to the path of the | ||
* downloaded emulator. Unfortunately, we can't access `this` when inside `readChunk`'s scope, since | ||
* it's a named function expression, and does not inherit the `this` object from it's parent. | ||
* To work around this, we wrap the fetch in a promise, | ||
* then once it's resolved we can access `this` in a callback arrow function that *does* inherit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a couple of other ways around this, I think, not 100% sure. One is to set like
And then I think you can call setBinaryPath inside readChunk. I'm not 100% sure but I think they should work? If they don't, never mind. |
||
* `this` from the parent object, allowing us to set `this.binaryPath`. | ||
* Note that we can't make readChunk an arrow function, since it needs to be named so that we can | ||
* perform recursion to read the next chunk. | ||
*/ | ||
const downloadPromise = new Promise<void>( | ||
(downloadComplete, downloadFailed) => { | ||
fetch(this.binaryUrl) | ||
.then(resp => { | ||
if (!resp.ok || resp.body === null) { | ||
return downloadFailed( | ||
`Failed to download emulator: [${resp.status}] ${resp.statusText}` | ||
); | ||
} | ||
|
||
const reader = resp.body.getReader(); | ||
reader.read().then(function readChunk({ done, value }): any { | ||
if (done) { | ||
return downloadComplete(); | ||
} | ||
|
||
if (!value) { | ||
return downloadFailed( | ||
'Did not receive chunk in response body' | ||
); | ||
} | ||
}) | ||
.catch(e => { | ||
console.log(`Download of emulator failed: ${e}`); | ||
downloadFailed(); | ||
|
||
buf.set(value, cur); | ||
cur += value.length; | ||
return reader.read().then(readChunk); | ||
}); | ||
} | ||
); | ||
}) | ||
.catch(err => downloadFailed(err)); | ||
} | ||
); | ||
|
||
downloadPromise | ||
.then(() => { | ||
console.log('Download complete.'); | ||
fs.writeFileSync(filepath, buf.slice(0, cur)); | ||
fs.chmod(filepath, 0o755, err => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
|
||
downloadPromise.then( | ||
() => { | ||
console.log('Download complete'); | ||
// Change emulator binary file permission to 'rwxr-xr-x'. | ||
// The execute permission is required for it to be able to start | ||
// with 'java -jar'. | ||
fs.chmod(filepath, 0o755, err => { | ||
if (err) reject(err); | ||
console.log(`Changed emulator file permissions to 'rwxr-xr-x'.`); | ||
this.binaryPath = filepath; | ||
if (this.copyToCache()) { | ||
console.log(`Cached emulator at ${this.cacheBinaryPath}`); | ||
} | ||
resolve(); | ||
}); | ||
}, | ||
() => { | ||
reject(); | ||
} | ||
); | ||
}); | ||
console.log(`Changed emulator file permissions to 'rwxr-xr-x'.`); | ||
// Since we are now in an arrow function, `this` is inherited from the `download()` method, so it is the Emulator object | ||
this.binaryPath = filepath; | ||
if (this.copyToCache()) { | ||
console.log(`Cached emulator at ${this.cacheBinaryPath}`); | ||
} | ||
|
||
resolve(); | ||
}); | ||
}) | ||
.catch(err => reject(err)); | ||
}); | ||
} | ||
|
||
|
@@ -184,7 +198,7 @@ export abstract class Emulator { | |
} | ||
|
||
if (this.binaryPath) { | ||
console.log(`Deleting the emulator jar at ${this.binaryPath}`); | ||
console.log(`Deleting the emulator at ${this.binaryPath}`); | ||
fs.unlinkSync(this.binaryPath); | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.