webui-aria2/js/ctrls/main.js
Brian G. Olson c04de8da64 Show number of seeders along with number of connections.
For downloads that have seeders show "Connections (Seeders)" in the
connections button, otherwise show "Connections"
2015-06-15 01:46:46 -05:00

593 lines
15 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

angular
.module('webui.ctrls.download', [
"ui.bootstrap",
'webui.services.utils', 'webui.services.rpc', 'webui.services.rpc.helpers', 'webui.services.alerts',
'webui.services.settings', 'webui.services.modals', 'webui.services.configuration',
'webui.services.errors',
])
.controller('MainCtrl', [
'$scope', '$name', '$enable', '$rpc', '$rpchelpers', '$utils', '$alerts', '$modals',
'$fileSettings', '$activeInclude', '$waitingExclude', '$pageSize', '$getErrorStatus',
// for document title
'$rootScope',
function(
scope, name, enable, rpc, rhelpers, utils, alerts, modals,
fsettings, activeInclude, waitingExclude, pageSize, getErrorStatus,
rootScope
) {
scope.name = name; // default UI name
scope.enable = enable; // UI enable options
var re_slashes = /\\/g;
var slash = "/";
var allStopped = [];
scope.active = [], scope.waiting = [], scope.stopped = [];
scope.gstats = {};
scope.hideLinkedMetadata = true;
// 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]);
}
scope.restart = function(d) {
// assumes downloads which are started by URIs, not torrents.
// the preferences are also not transferred, just simple restart
rpc.once('getOption', [d.gid], function(data) {
var prefs = data[0];
rpc.once('getFiles', [d.gid], function(data) {
var files = data[0];
var uris =
_.chain(files).map(function(f) { return f.uris })
.filter(function(uris) { return uris && uris.length })
.map(function(uris) {
var u = _.chain(uris)
.map(function(u) { return u.uri })
.uniq().value();
return u;
}).value();
if (uris.length > 0) {
console.log('adding uris:', uris, prefs);
scope.remove(d, function() {
rhelpers.addUris(uris, prefs);
}, true);
}
});
});
}
scope.canRestart = function(d) {
return (['active', 'paused'].indexOf(d.status) == -1
&& !d.bittorrent);
}
// remove the download,
// put it in stopped list if active,
// otherwise permanantly remove it
// d: the download ctx
scope.remove = function(d, cb, noConfirm) {
// HACK to make sure an angular digest is not running, as only one can happen at a time, and confirm is a blocking
// call so an rpc response can also trigger a digest call
setTimeout(function() {
if (!noConfirm && !confirm("Remove %s and associated meta-data?".replace("%s", d.name))) {
return;
}
var method = 'remove';
if (scope.getType(d) == 'stopped')
method = 'removeDownloadResult';
if (d.followedFrom) {
scope.remove(d.followedFrom, function() {}, true);
d.followedFrom = null;
}
rpc.once(method, [d.gid], cb);
var lists = [scope.active, scope.waiting, scope.stopped], ind = -1, i;
for (var i = 0; i < lists.length; ++i) {
var list = lists[i];
var idx = list.indexOf(d);
if (idx < 0) {
continue;
}
list.splice(idx, 1);
return;
}
}, 0);
}
// start filling in the model of active,
// waiting and stopped download
rpc.subscribe('tellActive', [], function(data) {
scope.$apply(function() {
utils.mergeMap(data[0], scope.active, scope.getCtx);
});
});
rpc.subscribe('tellWaiting', [0, 1000], function(data) {
scope.$apply(function() {
utils.mergeMap(data[0], scope.waiting, scope.getCtx);
});
});
rpc.subscribe('tellStopped', [0, 1000], function(data) {
scope.$apply(function() {
if (!scope.hideLinkedMetadata) {
utils.mergeMap(data[0], scope.stopped, scope.getCtx);
return;
}
utils.mergeMap(data[0], allStopped, scope.getCtx);
var gids = {};
_.forEach(allStopped, function(e) {
gids[e.gid] = e;
});
_.forEach(scope.active, function(e) {
gids[e.gid] = e;
});
_.forEach(scope.waiting, function(e) {
gids[e.gid] = e;
});
scope.stopped = _.filter(allStopped, function(e) {
if (!e.metadata || !e.followedBy || !(e.followedBy in gids)) {
return true;
}
var linked = gids[e.followedBy];
linked.followedFrom = e;
return false;
});
});
});
rootScope.pageTitle = utils.getTitle();
rpc.subscribe('getGlobalStat', [], function(data) {
scope.$apply(function() {
scope.gstats = data[0];
rootScope.pageTitle = utils.getTitle(scope.gstats);
});
});
rpc.once('getVersion', [], function(data) {
scope.$apply(function() {
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.downloadFilterCommitted = "";
scope.onDownloadFilter = function() {
if (scope.downloadFilterTimer) {
clearTimeout(scope.downloadFilterTimer);
}
scope.downloadFilterTimer = setTimeout(function() {
delete scope.downloadFilterTimer;
if (scope.downloadFilterCommitted !== scope.downloadFilter) {
scope.downloadFilterCommitted = scope.downloadFilter;
scope.$digest();
}
}, 500);
};
scope.filterDownloads = function(downloads) {
if (!scope.downloadFilterCommitted) {
return downloads;
}
var filter = scope.downloadFilterCommitted.
replace(/[{}()\[\]\\^$.?]/g, "\\$&").
replace(/\*/g, ".*").
replace(/\./g, ".");
filter = new RegExp(filter, "i");
return _.filter(downloads, function(d) {
if (filter.test(d.name)) return true;
return _.filter(d.files, function(f) {
return filter.test(f.relpath);
}).length;
});
};
scope.clearFilter = function() {
scope.downloadFilter = scope.downloadFilterCommitted = "";
};
scope.toggleStateFilters = function() {
scope.filterSpeed = !scope.filterSpeed;
scope.filterActive = !scope.filterActive;
scope.filterWaiting = !scope.filterWaiting;
scope.filterComplete = !scope.filterComplete;
scope.filterError = !scope.filterError;
scope.filterPaused = !scope.filterPaused;
scope.filterRemoved = !scope.filterRemoved;
scope.persistFilters();
};
scope.resetFilters = function() {
scope.filterSpeed =
scope.filterActive =
scope.filterWaiting =
scope.filterComplete =
scope.filterError =
scope.filterPaused =
scope.filterRemoved =
true;
scope.clearFilter();
scope.persistFilters();
};
scope.persistFilters = function() {
var o = JSON.stringify({
s: scope.filterSpeed,
a: scope.filterActive,
w: scope.filterWaiting,
c: scope.filterComplete,
e: scope.filterError,
p: scope.filterPaused,
r: scope.filterRemoved
});
utils.setCookie("aria2filters", o);
};
scope.loadFilters = function() {
var o = JSON.parse(utils.getCookie("aria2filters"));
if (!o) {
scope.resetFilters();
return;
}
scope.filterSpeed = !!o.s;
scope.filterActive = !!o.a;
scope.filterWaiting = !!o.w;
scope.filterComplete = !!o.c;
scope.filterError = !!o.e;
scope.filterPaused = !!o.p;
scope.filterRemoved = !!o.r;
};
scope.loadFilters();
scope.toggleCollapsed = function(download) {
if (!download.collapsed) {
download.animCollapsed = true;
// ng-unswitch after half a second.
// XXX hacky way, because I'm to lazy right now to wire up proper
// transitionend events.
setTimeout(function() {
scope.$apply(function() {
download.collapsed = true;
});
}, 500);
return;
}
download.collapsed = false;
setTimeout(function() {
scope.$apply(function() {
download.animCollapsed = false;
});
}, 0);
};
// max downloads shown in one page
scope.pageSize = pageSize;
// current displayed page
scope.currentPage = 1;
// total amount of downloads returned by aria2
scope.totalAria2Downloads = function() {
return scope.active.length
+ scope.waiting.length
+ scope.stopped.length;
}
scope.getErrorStatus = function(errorCode) {
return getErrorStatus(+errorCode);
}
// actual downloads used by the view
scope.getDownloads = function() {
var downloads = [];
if (scope.filterActive) {
if (!scope.filterSpeed) {
downloads = _.filter(scope.active, function (e) {
return !+e.uploadSpeed && !+e.downloadSpeed;
});
}
else {
downloads = scope.active;
}
}
else if (scope.filterSpeed) {
downloads = _.filter(scope.active, function (e) {
return +e.uploadSpeed || +e.downloadSpeed;
});
}
if (scope.filterWaiting) {
downloads = downloads.concat(_.filter(scope.waiting, function (e) {
return e.status == "waiting";
}));
}
if (scope.filterPaused) {
downloads = downloads.concat(_.filter(scope.waiting, function (e) {
return e.status == "paused";
}));
}
if (scope.filterError) {
downloads = downloads.concat(_.filter(scope.stopped, function (e) {
return e.status == "error";
}));
}
if (scope.filterComplete) {
downloads = downloads.concat(_.filter(scope.stopped, function (e) {
return e.status == "complete";
}));
}
if (scope.filterRemoved) {
downloads = downloads.concat(_.filter(scope.stopped, function (e) {
return e.status == "removed";
}));
}
downloads = scope.filterDownloads(downloads);
scope.totalDownloads = downloads.length;
downloads = downloads.slice( (scope.currentPage - 1) * scope.pageSize );
downloads.splice( scope.pageSize );
return downloads;
}
scope.hasDirectURL = function() {
return rpc.getDirectURL() != '';
}
scope.getDirectURL = function() {
return rpc.getDirectURL();
}
// convert the donwload form aria2 to once used by the view,
// minor additions of some fields and checks
scope.getCtx = function(d, ctx) {
if (!ctx) {
ctx = {
dir: d.dir,
status: d.status,
gid: d.gid,
followedBy: (d.followedBy && d.followedBy.length == 1
? d.followedBy[0] : null),
followedFrom: null,
numPieces: d.numPieces,
connections: d.connections,
connectionsTitle: "Connections",
numSeeders: d.numSeeders,
bitfield: d.bitfield,
errorCode: d.errorCode,
totalLength: d.totalLength,
fmtTotalLength: utils.fmtsize(d.totalLength),
completedLength: d.completedLength,
fmtCompletedLength: utils.fmtsize(d.completedLength),
uploadLength: d.uploadLength,
fmtUploadLength: utils.fmtsize(d.uploadLength),
pieceLength: d.pieceLength,
fmtPieceLength: utils.fmtsize(d.pieceLength),
downloadSpeed: d.downloadSpeed,
fmtDownloadSpeed: utils.fmtspeed(d.downloadSpeed),
uploadSpeed: d.uploadSpeed,
fmtUploadSpeed: utils.fmtspeed(d.uploadSpeed),
collapsed: true,
animCollapsed: true,
files: [],
};
}
else {
ctx.dir = d.dir;
ctx.status = d.status;
ctx.errorCode = d.errorCode;
ctx.gid = d.gid;
ctx.followedBy = (d.followedBy && d.followedBy.length == 1
? d.followedBy[0] : null);
ctx.followedFrom = null;
ctx.numPieces = d.numPieces;
ctx.connections = d.connections;
if ( typeof d.numSeeders === 'undefined' ) {
ctx.numSeeders = "";
}
else {
ctx.connectionsTitle = "Connections (Seeders)";
ctx.numSeeders = " (" + d.numSeeders + ")";
}
ctx.bitfield = d.bitfield;
if (ctx.totalLength !== d.totalLength) {
ctx.totalLength = d.totalLength;
ctx.fmtTotalLength = utils.fmtsize(d.totalLength);
}
if (ctx.completedLength !== d.completedLength) {
ctx.completedLength = d.completedLength;
ctx.fmtCompletedLength = utils.fmtsize(d.completedLength);
}
if (ctx.uploadLength !== d.uploadength) {
ctx.uploadLength = d.uploadlength;
ctx.fmtUploadLength = utils.fmtsize(d.uploadLength);
}
if (ctx.pieceLength !== d.pieceLength) {
ctx.pieceLength = d.pieceLength;
ctx.fmtPieceLength = utils.fmtsize(d.pieceLength);
}
if (ctx.downloadSpeed !== d.downloadSpeed) {
ctx.downloadSpeed = d.downloadSpeed;
ctx.fmtDownloadSpeed = utils.fmtspeed(d.downloadSpeed);
}
if (ctx.uploadSpeed !== d.uploadSpeed) {
ctx.uploadSpeed = d.uploadSpeed;
ctx.fmtUploadSpeed = utils.fmtspeed(d.uploadSpeed);
}
}
var dlName;
var files = d.files;
if (files) {
var cfiles = 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.index = +file.index;
cfile.path = file.path;
cfile.length = file.length;
cfile.fmtLength = utils.fmtsize(file.length);
cfile.relpath = file.path.replace(re_slashes, slash);
if (!cfile.relpath) {
cfile.relpath = (file.uris && file.uris[0] && file.uris[0].uri) || "Unknown";
}
else if (!cfile.relpath.startsWith("[")) { // METADATA
cfile.relpath = cfile.relpath.substr(ctx.dir.length + 1);
}
}
cfile.selected = (file.selected === "true");
}
cfiles.length = files.length;
if (cfiles.length) {
dlName = cfiles[0].relpath;
}
}
else {
delete ctx.files;
}
var btName;
if (d.bittorrent) {
btName = d.bittorrent.info && d.bittorrent.info.name;
ctx.bittorrent = true;
}
else {
delete ctx.bittorrent;
}
ctx.name = btName || dlName || "Unknown";
ctx.metadata = ctx.name.startsWith("[METADATA]");
if (ctx.metadata) {
ctx.name = ctx.name.substr(10);
}
return ctx;
};
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-bar-info";
case "error":
return "progress-bar-danger";
case "removed":
return "progress-bar-warning";
case "active":
return "active";
case "complete":
return "progress-bar-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 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.selectFiles = function(d) {
console.log('got back files for the torrent ...');
modals.invoke('selectFiles', d.files, function(files) {
var indexes = "";
_.forEach(files, function(f) {
if (f.selected) {
indexes += "," + f.index;
}
});
indexes = indexes.slice(1);
rpc.once('changeOption', [d.gid, {'select-file': indexes}], function(res) {
console.log('changed indexes to:', indexes, res);
})
});
}
scope.showSettings = function(d) {
var type = scope.getType(d)
, settings = {};
rpc.once('getOption', [d.gid], function(data) {
var vals = data[0];
var sets = _.cloneDeep(fsettings);
for (var i in sets) {
if (type == 'active' && activeInclude.indexOf(i) == -1) continue;
if (type == 'waiting' && waitingExclude.indexOf(i) != -1) continue;
settings[i] = sets[i];
settings[i].val = vals[i] || settings[i].val;
}
modals.invoke('settings', settings, d.name + ' settings', 'Change', function(settings) {
var sets = {};
for (var i in settings) { sets[i] = settings[i].val };
rpc.once('changeOption', [d.gid, sets]);
});
});
return false;
}
}]);