09120284c5
This will improve the performance a lot. First load performance may suffer a bit, though. Also, the leading "./" is skipped now, as this causes additional string ops, which turn out to be particularly harmful in some cases (slots vs heaps aka. shortstrings in spidermonkey, allocation overhead). And the leading "./" is not really required anymore.
330 lines
8.4 KiB
JavaScript
330 lines
8.4 KiB
JavaScript
angular
|
||
.module('webui.ctrls.download', [
|
||
'webui.services.utils', 'webui.services.rpc', 'webui.services.alerts',
|
||
'webui.services.settings', 'webui.services.modals'
|
||
])
|
||
.controller('DownloadCtrl', [
|
||
'$scope', '$rpc', '$utils', '$alerts', '$modals',
|
||
'$fileSettings', '$activeInclude', '$waitingExclude',
|
||
// for document title
|
||
'$window',
|
||
function(
|
||
scope, rpc, utils, alerts, modals,
|
||
fsettings, activeInclude, waitingExclude, window
|
||
) {
|
||
|
||
var re_slashes = /\\/g;
|
||
var slash = "/";
|
||
|
||
scope.active = [], scope.waiting = [], scope.stopped = [];
|
||
scope.gstats = {};
|
||
|
||
// pause the download
|
||
// d: the download ctx
|
||
scope.pause = function(d) {
|
||
rpc.once('forcePause', [d.gid]);
|
||
}
|
||
|
||
// resume the download
|
||
// d: the download ctx
|
||
scope.resume = function(d) {
|
||
rpc.once('unpause', [d.gid]);
|
||
}
|
||
|
||
// remove the download,
|
||
// put it in stopped list if active,
|
||
// otherwise permanantly remove it
|
||
// d: the download ctx
|
||
scope.remove = function(d, cb) {
|
||
var method = 'remove';
|
||
|
||
if (scope.getType(d) == 'stopped')
|
||
method = 'removeDownloadResult';
|
||
|
||
rpc.once(method, [d.gid], cb);
|
||
|
||
// also remove it from client cache assuming that it will be deleted in the aria2 list,
|
||
// but we could be wrong but the cache will update in next global update
|
||
var downloads = [scope.active, scope.waiting, scope.stopped], ind = -1, i;
|
||
for (i = 0; i < downloads.length; i++) {
|
||
ind = downloads[i].indexOf(d);
|
||
if (ind != -1) break;
|
||
}
|
||
|
||
if (ind == -1) {
|
||
return;
|
||
}
|
||
|
||
downloads[i].splice(ind, 1);
|
||
}
|
||
|
||
scope.restart = function(d) {
|
||
var uris =
|
||
_.chain(d.files).map(function(f) { return f.uris })
|
||
.filter(function(uris) { return uris.length })
|
||
.map(function(uris) {
|
||
return _.chain(uris)
|
||
.map(function(u) { return u.uri })
|
||
.uniq().value();
|
||
}).value();
|
||
|
||
if (uris.length > 0) {
|
||
scope.remove(d, function() {
|
||
rpc.once('addUri', uris, angular.noop, true);
|
||
});
|
||
}
|
||
}
|
||
|
||
// start filling in the model of active,
|
||
// waiting and stopped download
|
||
rpc.subscribe('tellActive', [], function(data) {
|
||
utils.mergeMap(data[0], scope.active, scope.getCtx);
|
||
});
|
||
|
||
rpc.subscribe('tellWaiting', [0, 1000], function(data) {
|
||
utils.mergeMap(data[0], scope.waiting, scope.getCtx);
|
||
});
|
||
|
||
|
||
rpc.subscribe('tellStopped', [0, 1000], function(data) {
|
||
utils.mergeMap(data[0], scope.stopped, scope.getCtx);
|
||
});
|
||
|
||
rpc.subscribe('getGlobalStat', [], function(data) {
|
||
scope.gstats = data[0];
|
||
window.document.title = utils.getTitle(scope.gstats);
|
||
|
||
});
|
||
|
||
rpc.once('getVersion', [], function(data) {
|
||
scope.miscellaneous = data[0];
|
||
});
|
||
|
||
// total number of downloads, updates dynamically as downloads are
|
||
// stored in scope
|
||
scope.totalDownloads = 0;
|
||
|
||
// download search filter
|
||
scope.downloadFilter = "";
|
||
|
||
scope.filterDownloads = function(downloads) {
|
||
var filter = scope.downloadFilter;
|
||
if (!filter.length) return downloads;
|
||
return _.filter(downloads, function(d) {
|
||
if (!d.files.length) return true;
|
||
|
||
return _.filter(d.files, function(f) {
|
||
return f.path.toLowerCase().indexOf(filter.toLowerCase()) != -1;
|
||
// return f.path.search(filter) != -1;
|
||
}).length;
|
||
});
|
||
};
|
||
|
||
// max downloads shown in one page
|
||
scope.pageSize = 10;
|
||
|
||
// current displayed page
|
||
scope.currentPage = 1;
|
||
|
||
scope.pageControlRadius = 3;
|
||
|
||
// total maximum pages
|
||
scope.totalPages = 0;
|
||
|
||
// total amount of downloads returned by aria2
|
||
scope.totalAria2Downloads = function() {
|
||
return scope.active.length
|
||
+ scope.waiting.length
|
||
+ scope.stopped.length;
|
||
}
|
||
|
||
// actual downloads used by the view
|
||
scope.getDownloads = function() {
|
||
var downloads =
|
||
scope.filterDownloads(
|
||
scope.active.concat( scope.waiting ).concat( scope.stopped )
|
||
)
|
||
;
|
||
|
||
scope.totalDownloads = downloads.length;
|
||
|
||
scope.totalPages = Math.ceil(scope.totalDownloads / scope.pageSize) || 1;
|
||
|
||
// fix the bug when downloads are deleted until no left on a specific page
|
||
if (scope.currentPage > scope.totalPages)
|
||
scope.currentPage = scope.totalPages;
|
||
|
||
downloads = downloads.slice( (scope.currentPage - 1) * scope.pageSize );
|
||
downloads.splice( scope.pageSize );
|
||
|
||
return downloads;
|
||
}
|
||
|
||
scope.setPage = function(pageNumber) {
|
||
scope.currentPage = pageNumber;
|
||
return false;
|
||
}
|
||
|
||
// get the pages to be displayed
|
||
scope.getPages = function() {
|
||
var minPage = scope.currentPage - scope.pageControlRadius;
|
||
|
||
if (minPage < 1) minPage = 1;
|
||
|
||
var maxPage = scope.currentPage + scope.pageControlRadius;
|
||
|
||
if (maxPage > scope.totalPages)
|
||
maxPage = scope.totalPages;
|
||
|
||
return _.range(minPage, maxPage + 1);
|
||
}
|
||
|
||
// convert the donwload form aria2 to once used by the view,
|
||
// minor additions of some fields and checks
|
||
scope.getCtx = function(d, ctx) {
|
||
ctx = ctx || {};
|
||
|
||
_.each([
|
||
'totalLength', 'completedLength', 'uploadLength', 'dir',
|
||
'pieceLength', 'downloadSpeed', 'uploadSpeed', 'status',
|
||
'gid', 'numPieces', 'connections', 'bitfield'
|
||
], function(e) {
|
||
ctx[e] = d[e];
|
||
});
|
||
|
||
var files = d["files"];
|
||
if (files) {
|
||
var cfiles = ctx["files"] || (ctx["files"] = []);
|
||
for (var i = 0; i < files.length; ++i) {
|
||
var cfile = cfiles[i] || (cfiles[i] = {});
|
||
var file = files[i];
|
||
if (file.path !== cfile.path) {
|
||
cfile.path = file.path;
|
||
cfile.length = file.length;
|
||
cfile.relpath = file.path.replace(re_slashes, slash);
|
||
if (!cfile.relpath.startsWith("[")) { // METADATA
|
||
cfile.relpath = cfile.relpath.substr(ctx.dir.length + 1);
|
||
}
|
||
}
|
||
}
|
||
cfiles.length = files.length;
|
||
}
|
||
else {
|
||
delete ctx["files"];
|
||
}
|
||
|
||
if (d.bittorrent) {
|
||
ctx.bittorrentName = d.bittorrent.info && d.bittorrent.info.name;
|
||
ctx.bittorrent = true;
|
||
}
|
||
else {
|
||
delete ctx.bittorrentName;
|
||
}
|
||
|
||
// collapse the download details initially
|
||
if (ctx.collapsed === undefined)
|
||
ctx.collapsed = true;
|
||
|
||
return ctx;
|
||
};
|
||
|
||
scope.canRestart = function(d) {
|
||
if (['active', 'paused'].indexOf(d.status) == -1
|
||
&& !d.bittorrent)
|
||
return true;
|
||
|
||
return false;
|
||
};
|
||
|
||
scope.hasStatus = function hasStatus(d, status) {
|
||
if (_.isArray(status)) {
|
||
if (status.length == 0) return false;
|
||
return hasStatus(d, status[0]) || hasStatus(d, status.slice(1));
|
||
}
|
||
else {
|
||
return d.status == status;
|
||
}
|
||
};
|
||
|
||
// get time left for the download with
|
||
// current download speed,
|
||
// could be smarter by different heuristics
|
||
scope.getEta = function(d) {
|
||
return (d.totalLength-d.completedLength) / d.downloadSpeed;
|
||
}
|
||
|
||
scope.getProgressClass = function(d) {
|
||
switch (d.status) {
|
||
case "paused":
|
||
return "progress-info";
|
||
case "error":
|
||
return "progress-danger";
|
||
case "removed":
|
||
return "progress-warning";
|
||
case "active":
|
||
return "progress-active";
|
||
case "complete":
|
||
return "progress-success";
|
||
default:
|
||
return "";
|
||
}
|
||
};
|
||
|
||
// gets the progress in percentages
|
||
scope.getProgress = function(d) {
|
||
var percentage = (d.completedLength / d.totalLength)*100 || 0;
|
||
percentage = percentage.toFixed(2);
|
||
if(!percentage) percentage = 0;
|
||
|
||
return percentage;
|
||
};
|
||
|
||
// gets a pretty name for the download
|
||
scope.getName = function(d) {
|
||
if (d.bittorrentName) {
|
||
return d.bittorrentName;
|
||
}
|
||
|
||
return utils.getFileName(
|
||
d.files[0].path || d.files[0].uris[0].uri
|
||
);
|
||
}
|
||
|
||
// gets the type for the download as classified by the aria2 rpc calls
|
||
scope.getType = function(d) {
|
||
var type = d.status;
|
||
if (type == "paused") type = "waiting";
|
||
if (["error", "removed", "complete"].indexOf(type) != -1)
|
||
type = "stopped";
|
||
return type;
|
||
};
|
||
|
||
scope.showSettings = function(d) {
|
||
var type = scope.getType(d)
|
||
, settings = {};
|
||
|
||
rpc.once('getOption', [d.gid], function(data) {
|
||
var vals = data[0];
|
||
|
||
for (var i in fsettings) {
|
||
if (type == 'active' && activeInclude.indexOf(i) == -1) continue;
|
||
|
||
if (type == 'waiting' && waitingExclude.indexOf(i) != -1) continue;
|
||
|
||
settings[i] = fsettings[i];
|
||
settings[i].val = vals[i] || settings[i].val;
|
||
}
|
||
|
||
modals.invoke('settings', settings, scope.getName(d) + ' settings', function(settings) {
|
||
var sets = {};
|
||
for (var i in settings) { sets[i] = settings[i].val };
|
||
|
||
rpc.once('changeOption', [d.gid, sets]);
|
||
});
|
||
});
|
||
|
||
return false;
|
||
}
|
||
|
||
}]);
|