Skip to content
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

Aria2c, but for real now #220

Draft
wants to merge 69 commits into
base: v3.0
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
0cbc962
first draft of DownloadManager
lukaarma Sep 5, 2020
5334293
first draft of Decrypter
lukaarma Sep 5, 2020
67573fc
installed WebSocket and updated Tmp
lukaarma Sep 5, 2020
685fa27
added debug and best quality flags
lukaarma Sep 5, 2020
0d8b420
changed UserAgent version
lukaarma Sep 5, 2020
6c0e37a
created WebSocket/Aria2c errors
lukaarma Sep 5, 2020
29a6fab
minor formatting changes
lukaarma Sep 5, 2020
d29bd54
very simple test for SIGINT
lukaarma Sep 5, 2020
5350bc3
added debug logging
lukaarma Sep 5, 2020
0c65ff7
added parsing of m3u8 file down to a list of links
lukaarma Sep 5, 2020
903f2bf
updated destreamer to use the new download method
lukaarma Sep 5, 2020
1dff41c
Merge branch 'master' of https://github.com/snobu/destreamer into ari…
lukaarma Sep 7, 2020
6e874f5
exclude debug launch config
lukaarma Sep 7, 2020
9e25870
updated packages
lukaarma Sep 7, 2020
d037b7c
renamed and finished decrypter
lukaarma Sep 9, 2020
8b61f86
added debug logging
lukaarma Sep 9, 2020
aa21e54
added quality option
lukaarma Sep 9, 2020
a185f51
fixed typo and added comment
lukaarma Sep 9, 2020
96f4c90
- removed useless function/properties
lukaarma Sep 9, 2020
ec099e9
- fixed progress bar not updating
lukaarma Sep 9, 2020
f1476ff
done decryption/merging of video/audio/sub traks
lukaarma Sep 9, 2020
a93b328
updating comments
lukaarma Sep 9, 2020
e9dea14
removed bug in quality selection
lukaarma Sep 9, 2020
16a8532
removed useless old code
lukaarma Sep 9, 2020
6f082e1
marked unused code
lukaarma Sep 9, 2020
796753f
change user agent
lukaarma Sep 12, 2020
6c8628e
code cleanup
lukaarma Sep 19, 2020
7cab44a
added debug statement
lukaarma Sep 19, 2020
9453458
installed portfinder
lukaarma Sep 19, 2020
3e472f9
created error for no port aviable
lukaarma Sep 19, 2020
8df5155
implemented port finding
lukaarma Sep 19, 2020
c9c9fef
removed random useless linting test
lukaarma Sep 19, 2020
af4725c
fixed linting errors
lukaarma Sep 19, 2020
38edbad
check for aria existance
lukaarma Sep 20, 2020
482a506
fixed bug on hanging on shutdown
lukaarma Sep 20, 2020
95c7150
improved shutdown sequence
lukaarma Sep 20, 2020
14cfe7c
typo fix
lukaarma Sep 24, 2020
c7e0415
process.exit on uncaught exceptions
lukaarma Sep 24, 2020
502565d
better webSocket initialization
lukaarma Sep 24, 2020
020518e
minor comments and variables name changes
lukaarma Sep 25, 2020
331efd9
fix for *nix platforms
lukaarma Sep 25, 2020
b89e041
workaround for listeners not removed
lukaarma Sep 27, 2020
8848f29
Update README.md
rohit404404 Oct 3, 2020
389be33
Update README.md
rohit404404 Oct 3, 2020
ec24ff9
Merge pull request #242 from rohit404404/patch-1
lukaarma Oct 3, 2020
22968f4
Misc fixes (#244)
lukaarma Oct 8, 2020
15c4203
Update report-trouble.md (#240)
lukaarma Oct 8, 2020
2a3c3eb
fixed buffer maxLenght exceded on aria2c daemon
lukaarma Oct 9, 2020
3249759
Merge branch 'master' of https://github.com/snobu/destreamer into ari…
lukaarma Oct 11, 2020
85f3bea
added access token refresh logic (closes #255)
lukaarma Oct 11, 2020
41206a9
fixed stupid session refreshing typo
lukaarma Oct 11, 2020
1763dc8
changed exec in favour of spawn for aria2c
lukaarma Oct 11, 2020
a179bdc
Update Errors.ts
rpaulucci3 Oct 17, 2020
eacb2b6
Merge pull request #265 from rpaulucci3/patch-1
lukaarma Oct 17, 2020
4a45c68
Fix group download, added 100 video limit
snobu Nov 14, 2020
0f2d902
Fix verbose logging when we get JSON back
snobu Nov 14, 2020
461df7b
Fix for group download IRL this time
snobu Nov 14, 2020
0b4c900
Update README.md
snobu Dec 2, 2020
ddafc50
fixed parsing for group with more than 100 videos
lukaarma Dec 3, 2020
08364ed
new version of error retry, couldn't test it
lukaarma Dec 3, 2020
6d28b3c
updated all packages
lukaarma Dec 3, 2020
c1a0994
add aria2 to prereqs
fulminemizzega Dec 7, 2020
66a7f60
fix quotes in input file example in README.md
fulminemizzega Dec 7, 2020
195d0dd
Added `aria2` prerequisite
pastacolsugo Dec 26, 2020
1e8db6f
Merge pull request #303 from pastacolsugo/patch-1
lukaarma Jan 8, 2021
6d8f3c6
Fix return HTTP 403 reason with or without verbose (#316)
snobu Jan 26, 2021
3fe64d1
Merge branch 'aria2c_forRealNow' into patch-1
lukaarma Feb 9, 2021
cea18bb
Merge pull request #289 from fulminemizzega/patch-1
lukaarma Feb 9, 2021
ffd76ba
Fix a small typo in terminal output (#363)
MahmoudAFarag Apr 7, 2021
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
first draft of DownloadManager
  • Loading branch information
lukaarma committed Sep 5, 2020
commit 0cbc962bf38e5c76bbf58355d61325a4c43b1ef1
234 changes: 234 additions & 0 deletions src/DownloadManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { logger } from './Logger';

import cliProgress from 'cli-progress';
import WebSocket from 'ws';

// TODO: ADD ERROR HANDLING!!!

export class DownloadManager {
private webSocket: WebSocket;
private progresBar: cliProgress.Bar;
private completed: number;
private queue: Set<string>;
private index: number;

public constructor(port: number) {
lukaarma marked this conversation as resolved.
Show resolved Hide resolved
this.webSocket = new WebSocket(`http://localhost:${port}/jsonrpc`);
this.completed = 0;
this.queue = new Set<string>();
this.index = 1;

// TODO: there's a not a tty mode for progresBar
// FIXME: is there a way to fix the ETA?
// Can't get not the size nor the ETA from aria2c that I can see
this.progresBar = new cliProgress.SingleBar({
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
format: 'progress [{bar}] {percentage}% {speed} MB/s {eta_formatted}',
// process.stdout.columns may return undefined in some terminals (Cygwin/MSYS)
barsize: Math.floor((process.stdout.columns || 30) / 3),
stopOnComplete: true,
hideCursor: true,
});

// Is this really needed having the 30 columns default if
lukaarma marked this conversation as resolved.
Show resolved Hide resolved
// process.stdout.columns undefined/0?
if (!process.stdout.columns) {
logger.warn(
'Unable to get number of columns from terminal.\n' +
'This happens sometimes in Cygwin/MSYS.\n' +
'No progress bar can be rendered, however the download process should not be affected.\n\n' +
'Please use PowerShell or cmd.exe to run destreamer on Windows.'
);
}

this.webSocket.on('message', data => {
const parsed = JSON.parse(data.toString());

// print only messaged not handled during download
// TODO: maybe we could remove this and re-add when the downloads are done
if (parsed.method !== 'aria2.onDownloadComplete' &&
parsed.method !== 'aria2.onDownloadStart' &&
parsed.id !== 'getSpeed' &&
parsed.id !== 'addUrl') {
logger.info('[INCOMING] \n' + JSON.stringify(parsed, null, 4) + '\n\n');
}
});
}

/**
* MUST BE CALLED BEFORE ANY OTHER OPERATION
*
* Wait for an established connection between the webSocket
* and Aria2c with a 10s timeout.
* Then send aria2c the global config option if specified.
*/
public async init(options?: {[option: string]: string}): Promise<void> {
let tries = 0;

while (this.webSocket.readyState !== this.webSocket.OPEN) {
if (tries < 5) {
tries++;
await new Promise(r => setTimeout(r, 2000));
}
else {
throw new Error();
}
}
logger.info('Connected! \n');

if (options) {
logger.info('Now trying to send configs...');
this.setOptions(options);
}
}

/**
* Wait for an established connection between the webSocket
* and Aria2c with a 10s timeout.
* Then send aria2c the global config option specified.
*/
public async close(): Promise<void> {
let tries = 0;
this.webSocket.close();

while (this.webSocket.readyState !== this.webSocket.CLOSED) {
if (tries < 10) {
tries++;
await new Promise(r => setTimeout(r, 1000));
}
else {
throw new Error();
}
}
}

private createMessage(method: 'aria2.addUri', params: [[string]] | [[string], object], id?: string): string;
private createMessage(method: 'aria2.changeOption', params: [string, object], id?: string): string;
private createMessage(method: 'aria2.changeGlobalOption', params: [{[option: string]: string}], id?: string): string;
private createMessage(method: 'system.multicall', params: [Array<object>], id?: string): string;
// FIXME: I don't know how to properly implement this one that doesn't require params..
private createMessage(method: 'aria2.getGlobalStat', params: null, id?: string): string;
private createMessage(method: string, params?: any, id?: string): string {
return JSON.stringify({
jsonrpc: '2.0',
id: id ?? 'Destreamer',
method: method,
// This took 40 mins just because I didn't want to use an if...so smart -_-
...(!!params && {params: params})
});
}

private createMulticallElement(method: string, params?: any): any {
return {
methodName: method,
// This took 40 mins just because I didn't want to use an if...so smart -_-
...(!!params && {params: params})
};
}

/**
* For general options see
* {@link https://aria2.github.io/manual/en/html/aria2c.html#aria2.changeOption here}.
* For single download options see
* {@link https://aria2.github.io/manual/en/html/aria2c.html#aria2.changeGlobalOption here}
*
* @param options object with key: value pairs
*/
private setOptions(options: {[option: string]: string}, guid?: string): void {
let message: string = guid ?
this.createMessage('aria2.changeOption', [guid, options]) :
this.createMessage('aria2.changeGlobalOption', [options]);

this.webSocket.send(message);
}

public downloadUrls(urls: Array<string>, directory: string): Promise<void> {
return new Promise (resolve => {

const handleResponse = (data: WebSocket.Data): void => {
const parsed = JSON.parse(data.toString());

/* I ordered them in order of (probable) times called so
that we don't check useless ifs (even if we aren't caring about efficency) */

// handle download completions
if (parsed.method === 'aria2.onDownloadComplete') {
this.queue.delete(parsed.params.pop().gid.toString());
this.progresBar.update(++this.completed);

/* TODO: probably we could use setIntervall because reling on
a completed download is good in most cases (since the segments
are small and a lot, somany and frequent updates) BUT if the user
internet speed is really low the completed downalods come in
less frequently and we have less updates */
this.webSocket.send(this.createMessage('aria2.getGlobalStat', null, 'getSpeed'));

if (this.queue.size === 0) {
this.index = 1;
this.webSocket.removeListener('message', handleResponse);
resolve();
}
}

// handle speed update packages
else if (parsed.id === 'getSpeed') {
this.progresBar.update(this.completed,
{ speed: ((parsed.result.downloadSpeed as number) / 1000000).toFixed(2) });
}


// handle download errors
else if (parsed.method === 'aria2.onDownloadError') {
// TODO: test download error parsing
logger.error(JSON.stringify(parsed));

let errorGid: string = parsed.params.pop().gid.toString();
this.queue.delete(errorGid);

// TODO: add this to createMessage
this.webSocket.send(JSON.stringify({
jsonrpc: '2.0',
id: 'getUrlForRetry',
method: 'aria2.getUris',
params: [errorGid]
}));
}

// TODO: handle download retries
else if (parsed.id === 'getUrlForRetry') {
console.warn(JSON.stringify(parsed));
}

// handle url added to download list in aria
else if (parsed.id === 'addUrl') {
// if we recive array it's the starting list of downloads
// if it's a single string it's an error download being re-added
if (typeof parsed.result === 'string') {
this.queue.add(parsed.result.gid.toString());
}
else if (Array.isArray(parsed.result)) {
parsed.result.forEach((gid: string) =>
this.queue.add(gid.toString())
);

this.progresBar.start(this.queue.size, 0, { speed: 0});
}
}
};

this.webSocket.on('message', data => handleResponse(data));

const params: Array<any> = urls.map(url => {
const title: string = (this.index++).toString().padStart(16, '0') + '.encr';

return this.createMulticallElement(
'aria2.addUri', [[url], {out: title, dir: directory}]);
});

this.webSocket.send(
this.createMessage('system.multicall', [params], 'addUrl')
);
});
}
}