Skip to content

Commit

Permalink
feat(download): refactor, remove flow and add some more events
Browse files Browse the repository at this point in the history
  • Loading branch information
mamal72 committed Feb 14, 2017
1 parent 3c46cbf commit 8aebf89
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 126 deletions.
3 changes: 1 addition & 2 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
]
],
"plugins": [
"babel-plugin-syntax-flow",
"babel-plugin-transform-flow-strip-types"
["transform-object-rest-spread", { "useBuiltIns": true }]
]
}
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,19 @@ download.on('progress', data => {
download.on('error', err => {
console.log('Error', err);
});
download.on('connection-finished', data => {
console.log('Connection Finished', data);
});
download.on('finish', () => {
console.log('Download Finished!');
});
download.on('stop', () => {
console.log('Download Stoped!');
console.log('Download Stopped!');
});
download.start();

```

`Download` class inherits from EventEmitter. You can listen on it for `start`, `progress`, `error`, `finish`, `stop` events.
`Download` class inherits from EventEmitter. You can listen on it for `start`, `progress`, `error`, `connection-finished`, `finish`, `stop` events.

*More details will be added.*

Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axel-downloader",
"version": "0.1.6",
"version": "0.2.0",
"description": "A node wrapper around axel downloader",
"main": "lib/index.js",
"files": [
Expand All @@ -20,17 +20,17 @@
"devDependencies": {
"ava": "^0.18.1",
"babel-cli": "^6.22.2",
"babel-plugin-syntax-flow": "^6.18.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.22.0",
"babel-preset-env": "^1.1.8",
"coveralls": "^2.11.16",
"eslint": "^3.15.0",
"eslint-config-airbnb-base": "^11.0.1",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-plugin-import": "^2.2.0",
"nyc": "^10.1.2"
},
"dependencies": {
"babel-eslint": "^7.1.1",
"deep-equal": "^1.0.1",
"stream-splitter": "^0.3.2"
},
"ava": {
Expand Down
43 changes: 30 additions & 13 deletions src/Download.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
// @flow
import EventEmitter from 'events';
import deepEqual from 'deep-equal';
import { spawn } from 'child_process';

import { splitStream, parseProgress } from './helpers';
import { DownloadOptions } from './types';


// A class for download task
export default class Download extends EventEmitter {
constructor(
url: string,
downloadPath: string = process.cwd(),
url,
downloadPath = process.cwd(),
{
connections = 16,
maxSpeed = 0,
userAgent,
headers = []
}: DownloadOptions = {}
} = {}
) {
super();

Expand All @@ -30,10 +29,11 @@ export default class Download extends EventEmitter {
this.userAgent = userAgent;
this.headers = headers;
this.childProcess = null;
this.status = {};
}

// Start download
start(): void {
start() {
Download.checkAxel();

this.childProcess = spawn('axel', this.getCommandArgs());
Expand All @@ -43,20 +43,37 @@ export default class Download extends EventEmitter {

stdout.on('token', (data) => {
const rawDownloadProgress = data.toString();
const progressInfo = parseProgress(rawDownloadProgress);
this.emit('progress', progressInfo);
const operation = parseProgress(rawDownloadProgress);

switch (operation.type) {
case 'start':
this.emit('start');
break;
case 'progress':
if (!deepEqual(operation.data, this.status)) {
this.status = operation.data;
this.emit('progress', operation.data);
}
break;
case 'connection-finished':
this.emit('connection-finished', operation.data);
break;
default:
// nothing for now
}
});
stdout.on('close', () => {

this.childProcess.stdout.on('close', () => {
this.emit('finish');
});

stderr.on('data', (err) => {
this.emit('error', err.toString());
});
this.emit('start');
}

// Stop download
stop(): void {
stop() {
if (this.childProcess) {
this.childProcess.kill();
this.childProcess = null;
Expand All @@ -65,7 +82,7 @@ export default class Download extends EventEmitter {
}

// Get axel command arguments
getCommandArgs(): Array<string> {
getCommandArgs() {
const args = [];

// download path
Expand Down Expand Up @@ -100,7 +117,7 @@ export default class Download extends EventEmitter {
}

// Check if axel downloader is installed
static checkAxel(): void {
static checkAxel() {
const axelCheck = spawn('axel');
axelCheck.on('error', () => {
throw new Error('Axel is not installed or is not available in your path');
Expand Down
63 changes: 46 additions & 17 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
// @flow
import StreamSplitter from 'stream-splitter';

import { DownloadProgress } from './types';

// Download operations
const downloadOperations = [
{
type: 'start',
pattern: /Starting download/,
props: []
},
{
type: 'progress',
pattern: /\[\s+(\d+)%][\s.]+\[\s*(\d+\.?\d)KB\/s]/,
props: [
'progress',
'speed'
]
},
{
type: 'connection-finished',
pattern: /Connection\s(\d+)\sfinished/,
props: [
'number'
]
}
];

// Get a stream that splits on a token
export function splitStream(stream: any, token: string = '\n'): any {
export function splitStream(stream, token = '\n') {
return stream.pipe(StreamSplitter(token));
}

// Get progress and speed of download from a line of string stdout
export function parseProgress(rawDownloadProgress: string): DownloadProgress {
const regexPattern = /\[\s+(\d+)%][\s.]+\[\s*(\d+\.?\d)KB\/s]/;
const matchedPatterns = rawDownloadProgress.match(regexPattern);
// Parse stdout and return operation type and params
export function parseProgress(rawDownloadProgress) {
const operation = downloadOperations.find(item => rawDownloadProgress.match(item.pattern));

if (!matchedPatterns) {
if (!operation) {
return {
downloading: false,
progress: 0,
speed: 0
type: 'none'
};
}

const [progress, speed] = [Number(matchedPatterns[1]), Number(matchedPatterns[2])];
return {
downloading: true,
progress,
speed
};
const matchedPattern = rawDownloadProgress.match(operation.pattern);
matchedPattern.shift();

const result = operation.props.reduce((acc, curr) => (
{
type: acc.type,
data: {
...acc.data,
[curr]: matchedPattern.shift()
}
}
), {
type: operation.type,
data: {}
});

return result;
}
12 changes: 0 additions & 12 deletions src/types/index.js

This file was deleted.

14 changes: 6 additions & 8 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import { parseProgress } from '../src/helpers';


test('wrong raw progress', t => {
const { downloading, progress, speed } = parseProgress('some wrong raw progress data');
t.is(downloading, false);
t.is(progress, 0);
t.is(speed, 0);
const { type } = parseProgress('some wrong raw progress data');
t.is(type, 'none');
});

test('correct raw progress', t => {
const { downloading, progress, speed } = parseProgress(
const { type, data } = parseProgress(
'[ 79%] .......... .......... .......... .......... .......... [ 3412.9KB/s]'
);
t.is(downloading, true);
t.is(progress, 79);
t.is(speed, 3412.9);
t.is(type, 'progress');
t.is(data.progress, '79');
t.is(data.speed, '3412.9');
});

0 comments on commit 8aebf89

Please sign in to comment.