evdh
Version:
evdh : EisF Video Download Helper, auto download videos on web pages.
986 lines (729 loc) • 20.5 kB
JavaScript
/* task.js, task: task manage part for evdh: EisF Video Download Helper, sceext <sceext@foxmail.com> 2009EisF2015, 2015.02
* version 0.1.3.0 test201502142027 (public version)
* author sceext <sceext@foxmail.com> 2015.02
* copyright 2015 sceext
*
* This is FREE SOFTWARE, released under GNU GPLv3+
* please see README.md and LICENSE for more information.
*
* evdh : EisF Video Download Helper, auto download videos with analyse service provided by flv.cn (api.flvxz.com)
* Copyright (C) 2015 sceext <sceext@foxmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* require import modules */
// node.js modules
var path = require('path');
// evdh modules
var _log = require('./log.js');
// this module global config object
var etc = {};
etc.dl_thread = 2; // download threads
etc.file_size_error_k = 0.01;
// import time_log for global use
var time_log = function(output){
_log.time_log(output);
};
/* objects */
/* object o_host_task start oht */
function o_host_task() {
// methods
this.create = oht_create; // create a new task
this.remove = oht_remove; // remove a exist task, this task
this.get_log = oht_get_log; // export log info object
this.set_log = oht_set_log; // import replace now task
this.pause = oht_pause; // pause now task, pause download
this.start = oht_start; // start now task, (start download) or (continue download)
this.set_status = oht_set_status; // set status of one part file, finish status
this.get_status = oht_get_status; // get this task status object
this.stop_update = oht_stop_update;
this.check_refresh_url = oht_check_refresh_url;
// private methods
this._check_add_item = oht_check_add_item; // check to add item to dl
this._add_item = oht_add_item;
this._update_sub_status = oht_update_sub_status; // get file download status
this._check_sub_status = oht_check_sub_status;
this._make_info_obj = oht_make_info_obj; // make a task info object
this._make_file_obj = oht_make_file_obj; // make a info object of a file in task
this._make_event_obj = oht_make_event_obj; // make a null callback event object
this._check_task_info = oht_check_task_info; // check task info, make sure no errors, check if it is right
this._select_video = oht_select_video;
this._on_sub_event = oht_on_sub_event; // sub download file event, callback
this._update = oht_update;
// attributes
this.callback = null; // event callback(event); with event object
this.dl = null; // o_host_file_dl object, used by this object
this.update_timeout_ms = 1000; // 1s update
// private attributes
this._info = null; // task info object
this._timeout_obj = null;
this._flag_stop = false;
}
function oht_stop_update() {
clearTimeout(this._timeout_obj);
this._timeout_obj = null;
this._flag_stop = true;
}
function oht_make_info_obj() { // make a null info obj
var info = {}; // task info object
// set task info
info.host_url = ''; // url of the video play page, use this url
// to analyse and get urls of files to download
info.base_path = ''; // base path of files to save, really path = base_path + file.path
info.site = ''; // video site, use this to check analyse result
// to select which video to download, hd quality, title
info.hd = ''; // hd, quality and title : all to decide which video to download
info.quality = ''; // video quality selected
info.title = ''; // title of this video
info.count = {}; // statistics count info object
info.count.all_file = 0; // all files in this task
info.count.done = 0; // download finished file
info.count.doing = 0; // doing download files number
info.count.wait = 0; // files wait to download
info.count.error = 0; // files download error
info.count.all_byte = 0; // all bytes of all files to download
info.count.ed_byte = 0; // finished bytes
info.count.all_time_s = 0; // sum of all video time in seconds
info.list = []; // list of file info objects
// done
return info;
}
function oht_make_file_obj() { // make a null file info obj
var info = {}; // file info object
// set file info
info.url = ''; // url of this file to download
info.path = ''; // path of this file to save, actually path of file is base_path + path
info.id = ''; // id of this file, used for o_host_file_dl
info.all_byte = 0; // this file all size
info.ed_byte = 0; // finished download size
info.time_s = 0; // time of this video file to play in seconds
info.format = ''; // file format, in video type
info.status = ''; // file download status, in [none, wait, doing, done, error]
// done
return info;
}
function oht_make_event_obj() { // make null event object
var event = {};
// set event info
event.type = ''; // event type, must
// these type can be used
// 'dl_done' all files download has been finished
// 'part_done' one part file download finished
// 'part_error' one part file download error
// 'error' other error
event.id = ''; // id of part file, optional, for part file event
event.list = []; // error object list, contain all error info objects
// done
return event;
}
function oht_check_task_info() { // check task info, if it is right, and crooect them
var info = this._info;
var err_l = []; // error level
var err_c = 0; // error count
// error level
// 0 can ignore
// 1 can fix
// 2 stop work, can not fix
// init count
var count = {};
count.all_file = 0;
count.done = 0;
count.doing = 0;
count.wait = 0;
count.error = 0;
count.all_byte = 0;
count.ed_byte = 0;
count.all_time_s = 0;
// check files info
var list = info.list;
for (var i = 0; i < list.length; i++) {
// check each file
var file = list[i];
if (file.all_byte < 0) {
err_l[2] = true;
err_c ++;
file.all_byte = 0;
}
if (file.ed_byte < 0) {
err_l[2] = true;
err_c ++;
file.ed_byte = 0;
}
if (file.ed_byte > file.all_byte) {
var d_size = Math.abs(file.ed_byte - file.all_byte);
var e_size = file.all_byte * etc.file_size_error_k;
// re check
if (d_size > e_size) {
// level 3 error
err_l[2] = true;
err_c ++;
file.ed_byte = file.all_byte;
}
}
if (file.time_s < 0) {
err_l[2] = true;
err_c ++;
file.time_s = 0;
}
// check status
switch (file.status) {
case 'none':
err_l[1] = true;
err_c ++;
// fix it
file.status = 'wait';
count.wait ++;
break;
case 'wait':
count.wait ++;
break;
case 'doing':
count.doing ++;
break;
case 'done':
count.done ++;
break;
case 'error':
count.error ++;
break;
default:
// error
err_l[2] = true;
err_c ++;
count.error ++;
}
// count
count.all_file ++;
count.all_byte += file.all_byte;
count.ed_byte += file.ed_byte;
count.all_time_s += file.time_s;
}
// check count
if (count.all_file != (count.done + count.doing + count.wait + count.error)) {
err_l[2] = true;
err_c ++;
return [err_l.length, err_c]; // very big error
}
if (count.all_file != list.length) {
err_l[2] = true;
err_c ++;
return [err_l.length, err_c]; // very big error
}
// check each count item
var cou = info.count;
if (cou.all_file != count.all_file) {
err_l[1] = true;
err_c ++;
cou.all_file = count.all_file;
}
if (cou.done != count.done) {
err_l[1] = true;
err_c ++;
cou.done = count.done;
}
if (cou.doing != count.doing) {
err_l[1] = true;
err_c ++;
cou.doing = count.doing;
}
if (cou.wait != count.wait) {
err_l[1] = true;
err_c ++;
cou.wait = count.wait;
}
if (cou.error != count.error) {
err_l[1] = true;
err_c ++;
cou.error = count.error;
}
if (cou.all_byte != count.all_byte) {
err_l[1] = true;
err_c ++;
cou.all_byte = count.all_byte;
}
if (cou.ed_byte != count.ed_byte) {
err_l[1] = true;
err_c ++;
cou.ed_byte = count.ed_byte;
}
if (cou.all_time_s != count.all_time_s) {
err_l[1] = true;
err_c ++;
cou.all_time_s = count.all_time_s;
}
// done
return [err_l.length, err_c];
}
function oht_create(host_url, hd, quality, title, info) { // give enough info, info : analyse xml get raw info object
// check select video
var video = this._select_video(info, hd, quality, title);
if (video.length != 1) {
// error
time_log('ERROR: task.js: no such video: ' + video.length + ' video exist ! ');
return true; // error true
} else {
video = video[0];
}
// create info object
var info = this._make_info_obj();
// set info obj
info.host_url = host_url.toString();
info.hd = hd.toString();
info.quality = quality.toString();
info.title = title.toString();
// other info
info.site = video.site;
info.base_path = video.base_path; // video object has been modified for this object
// set file info
var file_list = video.file;
// add each part file
for (var i = 0; i < file_list.length; i++) {
var file = file_list[i];
var fi = this._make_file_obj(); // null file info object
// set fi
fi.url = file.url;
fi.path = file.path; // file object is modifyed for this object too
fi.all_byte = file.size;
fi.time_s = file.time_s;
fi.format = file.type;
fi.status = 'wait'; // default status is wait
// set file id
fi.id = i.toString(); // just set a simple id
// add fi
info.list.push(fi);
}
// set info to this
this._info = info;
// just count it
this._check_task_info();
// done
return false; // error false
}
function oht_remove() {
// just remove task info object
var old_info = this._info;
this._info = null;
return old_info;
}
function oht_select_video(info, hd, quality, title) {
// index by hd
hd0 = hd.toString();
var ihd = {};
for (var i = 0; i < info.length; i++) {
var v = info[i]; // video
var hd1 = v.hd.toString();
if (ihd[hd1]) {
ihd[hd1].push(v);
} else {
ihd[hd1] = [v];
}
}
// check if has hd
if ((!ihd[hd0]) || (ihd[hd0].length < 1)) {
// error, no such video
return [];
}
// check if only one video
if (ihd[hd0].length == 1) {
// finished
return [ihd[hd0][0]];
}
// more video
var vs = ihd[hd0];
// index by quality
var q0 = quality.toString();
var iq = {};
for (var i = 0; i < vs.length; i++) {
var v = vs[i];
var q1 = v.quality.toString();
if (iq[q1]) {
iq[q1].push(v);
} else {
iq[q1] = [v];
}
}
// check if has quality
if ((!iq[q0]) || (iq[q0].length < 1)) {
// no such video
return [];
}
// check if only one video
if (iq[q0].length == 1) {
// ok finished
return [iq[q0][0]];
}
// more video
vs = iq[q0];
// index by title
var t0 = title.toString();
var it = {};
for (var i = 0; i < vs.length; i++) {
var v = vs[i];
var t1 = v.title.toString();
if (it[t1]) {
it[t1].push(v);
} else {
it[t1] = [v];
}
}
// check if has title
if ((!it[t0]) || (it[t0].length < 1)) {
// error
return []; // no such video
}
// just return it
return it[t0];
}
function oht_check_refresh_url(info) {
var info = this._info;
// select video
var video = this._select_video(info, info.hd, info.quality, info.title);
if (video.length != 1) {
time_log('ERROR: task.js oht_check_refresh_url: no such video: ' + video.length + 'video exist ! ');
return true; // error true
} else {
video = video[0];
}
// check all info
if (video.hd != info.hd) {
return true;
}
if (video.quality != info.quality) {
return true;
}
if (video.title != info.title) {
return true;
}
if (video.site != info.site) {
return true;
}
// recount this info
this._check_task_info();
// check file info
// check file number
var file_list = video.file;
if (file_list.length != info.list.length) {
return true;
}
// check each file
for (var i = 0; i < info.list.length; i++) {
var old = info.list[i];
var nfi = file_list[i]; // new file
// check if match
if (old.all_byte != nfi.size) {
return true;
}
if (old.time_s != nfi.time_s) {
return true;
}
if (old.format != nfi.type) {
return true;
}
}
// check done, update each url
for (var i = 0; i < info.list.length; i++) {
var old = info.list[i];
var nfi = file_list[i];
old.url = nfi.url;
}
// done
return false;
}
function oht_update_sub_status() {
// get sub ids
var ids = this.dl.get_id_list();
// update each status
for (var i = 0; i < ids.length; i++) {
var id = parseInt(ids[i], 10);
var file = this._info.list[id];
var status = this.dl.get_status(ids[i]);
// check it
if (!file) {
continue;
}
if (!status) {
continue;
}
// update it
file.ed_byte = status.ed_byte;
}
}
function oht_check_sub_status() {
// get sub id list
var ids = this.dl.get_id_list();
var flag_changed = false;
// check each part file
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var nid = parseInt(id, 10);
var file = this._info.list[nid];
var status = this.dl.get_status(id);
if (!status) {
continue;
}
// check sub status
switch (status.status) {
case 'done':
// check ed_byte
var d_size = Math.abs(status.ed_byte - file.all_byte);
var e_size = file.all_byte * etc.file_size_error_k;
if (d_size > e_size) {
if (status.ed_byte < (file.all_byte - 2 * e_size)) { // set it to wait, ready to retry
file.status = 'wait';
file.ed_byte = status.ed_byte;
} else {
// error
file.status = 'error';
time_log('ERROR: file download size error !!!!!!!! \n');
}
} else {
// set done
file.status = 'done';
file.ed_byte = file.all_byte;
}
flag_changed = true;
// remove this task
this.dl.rm_item(id);
break;
case 'doing':
// check ed_byte
if (status.ed_byte > file.all_byte) {
// error
file.status = 'error';
// stop this
this.dl.stop_item(id);
// remove it
this.dl.rm_item(id);
flag_changed = true;
}
// just update this
file.ed_byte = status.ed_byte;
break;
case 'error':
file.status = 'error';
// remove it
this.dl.rm_item(id);
flag_changed = true;
break;
case 'none':
file.status = 'wait';
// remove it
this.dl.rm_item(id);
flag_changed = true;
break;
default:
file.status = 'error';
// remove it
this.dl.rm_item(id);
flag_changed = true;
}
}
// if changed
if (flag_changed) {
// update sub status
this._update_sub_status();
// re count
this._check_task_info();
}
// done
}
function oht_get_status() {
// check no task
if (this._info === null) {
return null;
}
// update sub status
this._update_sub_status();
// re count
this._check_task_info();
// return this info
return this._info; // read only object
}
function oht_set_status(id, status) {
// check if it exist
var list = this._info.list;
id = parseInt(id, 10);
if ((id < 0) || (id >= list.length)) {
return true;
}
var file = list[id];
// check status
switch (status) { // you can only set status with this is wait and done
case 'wait':
file.status = 'wait';
break;
case 'done':
file.status = 'done';
file.ed_byte = file.all_byte;
break;
default: // error status
return true;
}
// count this info again
this._check_task_info();
// done
return false;
}
function oht_get_log() { // just the same as .get_status
return this.get_status();
}
function oht_set_log(olog) { // olog, log object
var old_info = this._info;
// just set it
this._info = olog;
// check if it is right
var err = this._check_task_info();
// if wrong, switch back
if (err[0] > 2) { // high level error
this._info = old_info;
this._check_task_info();
return true; // error true
}
// scan part file item status, change all 'doing' to 'wait'
var list = this._info.list;
for (var i = 0; i < list.length; i++) {
var file = list[i];
if (file.status == 'doing') {
file.status = 'wait';
}
}
// done, ok
return false;
}
function oht_pause() {
// stop each download item
var ids = this.dl.get_id_list();
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
// stop it
this.dl.stop_item(id);
}
// check sub status
this._check_sub_status();
// stop update
this.stop_update();
}
function oht_check_add_item() {
// first re check sub status
this._check_sub_status();
// check task now, thread
var ids = this.dl.get_id_list();
var now_thread = ids.length;
var id_text = '';
for (var i = 0; i < ids.length; i++) {
id_text += ' ' + ids[i];
}
// check if to add item
if (now_thread < etc.dl_thread) {
// should add item
var nid = this._add_item();
// and update status
this._check_sub_status();
this._update_sub_status();
}
// done
}
function oht_add_item() {
// found first wait task
var list = this._info.list;
for (var i = 0; i < list.length; i++) {
var file = list[i];
if (file.status == 'wait') {
// add this
var fpath = path.join(this._info.base_path, file.path);
this.dl.add_item(file.id, file.url, fpath);
// check to start item or continue item
if (file.ed_byte > 0) {
this.dl.continue_item(file.id);
} else {
this.dl.start_item(file.id);
}
// change this status
file.status = 'doing';
return file.id; // add ok
}
}
return null; // not found a item to add
}
function oht_update() {
// every second, update
this._check_sub_status();
this._update_sub_status();
this._check_add_item();
// check send all finished event
var info = this._info;
if (info.count.done == info.count.all_file) {
// should send dl_done event
var ev = this._make_event_obj;
ev.type = 'dl_done';
// stop update
this.stop_update();
var b_callback = this.callback;
// no send now, later
setTimeout(function(){
// send it
b_callback(ev);
}, 0);
}
// re set timeout, next timeout check
if (this._flag_stop) {
return;
}
if (this._timeout_obj) {
return;
}
// set timeout
var b = this;
this._timeout_obj = setTimeout(function(){
// null this timeout object
b._timeout_obj = null;
b._update();
}, this.update_timeout_ms);
}
function oht_start() {
// set sub callback
var b = this;
this.dl.callback = function(id, err){
b._on_sub_event(id, err);
};
this._flag_stop = false;
// start update
this._update();
}
function oht_on_sub_event(id, err) {
// update sub
this._update();
// make event object
var ev = this._make_event_obj();
// check event
if (!err) {
// part file finished
ev.type = 'part_done';
ev.id = id;
} else { // error info
ev.type = 'part_error';
ev.id = id;
ev.list.push(err);
}
// event callback
this.callback(ev);
}
/* end o_host_task object */
/* functions */
/* exports */
exports.etc = etc;
// objects
exports.o_host_task = o_host_task;
// functions
/* end task.js */