Performance improvements

* Pre-compute name, formatted sizes and speeds.
* Handle name edge-cases such as no-path and no-uris better.
* Unroll getCtx.
* Improve performance of mergeMap.
* Delay the search filter.

As indicated by Chrome/Gecko profilers.
This commit is contained in:
Nils Maier 2014-02-24 02:50:23 +01:00
parent 4ab27fb503
commit 38a413e95b
4 changed files with 162 additions and 103 deletions

View File

@ -264,7 +264,7 @@
<fieldset>
<div class="span4">
<label for="downloadFilter">Search</label>
<input id="downloadFilter" type="text" ng-model="downloadFilter" class="input-large"/>
<input id="downloadFilter" type="text" ng-model="downloadFilter" ng-change="onDownloadFilter()" class="input-large"/>
<br>
<b>Found: {{totalDownloads}} / {{totalAria2Downloads()}} </b>
</div>
@ -277,7 +277,7 @@
<tbody>
<tr>
<td class="download-name download-item" ng-click="download.collapsed = !download.collapsed">
{{getName(download)}}
{{download.name}}
</td>
<td class="download-controls download-item" rowspan="2">
<!-- {{{ download control buttons -->
@ -368,11 +368,11 @@
</li>
<li class="label label-active hidden-phone">
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.totalLength | blength}}</span>
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.fmtTotalLength}}</span>
</li>
<li class="label label-active hidden-phone">
<span title="Downloaded"><i class="icon-download-alt">&nbsp;</i> {{download.completedLength | blength}}</span>
<span title="Downloaded"><i class="icon-download-alt">&nbsp;</i> {{download.fmtCompletedLength}}</span>
</li>
<li class="label label-active hidden-phone hidden-tablet">
@ -389,11 +389,11 @@
</li>
<li class="label label-info">
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.totalLength | blength}}</span>
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.fmtTotalLength}}</span>
</li>
<li class="label label-info hidden-phone">
<span title="Downloaded"><i class="icon-download-alt">&nbsp;</i> {{download.completedLength | blength}}</span>
<span title="Downloaded"><i class="icon-download-alt">&nbsp;</i> {{download.fmtCompletedLength}}</span>
</li>
<li class="label label-info hidden-phone">
@ -436,7 +436,7 @@
</li>
<li class="label label-success">
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.totalLength | blength}}</span>
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.fmtTotalLength}}</span>
</li>
<!-- }}} -->
@ -455,7 +455,7 @@
<li ng-show="hasStatus(download, 'removed')"
class="label label-warning">
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.totalLength | blength}}</span>
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.fmtTotalLength}}</span>
</li>
<!-- }}} -->
</ul>
@ -471,7 +471,7 @@
</li>
<li class="label label-important">
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.totalLength | blength}}</span>
<span title="Download Size"><i class="icon-cloud-download">&nbsp;</i> {{download.fmtTotalLength}}</span>
</li>
<!-- }}} -->
</ul>
@ -495,21 +495,21 @@
<ul class="stats download-item">
<li class="label" title="Download Path"><i class="icon-folder-open">&nbsp;</i> <span class="download-dir">{{download.dir}}</span></li>
<li class="label" title="Estimated Time"><i class="icon-time">&nbsp;</i> <span class="download-eta">{{getEta(download) | time}}</span></li>
<li class="label" title="Download Size"><i class="icon-cloud-download">&nbsp;</i> <span class="download-totalLength">{{download.totalLength | blength}}</span></li>
<li class="label" title="Downloaded"><i class="icon-download-alt">&nbsp;</i> <span class="download-completedLength">{{download.completedLength | blength}}</span></li>
<li class="label" title="Download Speed"><i class="icon-download">&nbsp;</i> <span class="download-downloadSpeed">{{download.downloadSpeed | bspeed}}</span></li>
<li class="label" title="Download Size"><i class="icon-cloud-download">&nbsp;</i> <span class="download-totalLength">{{download.fmtTotalLength}}</span></li>
<li class="label" title="Downloaded"><i class="icon-download-alt">&nbsp;</i> <span class="download-completedLength">{{download.fmtCompletedLength}}</span></li>
<li class="label" title="Download Speed"><i class="icon-download">&nbsp;</i> <span class="download-downloadSpeed">{{download.fmtDownloadSpeed}}</span></li>
<li class="label" title="Upload Speed" ng-show="download.bittorrent"><i class="icon-upload">&nbsp;</i> <span class="download-uploadSpeed">{{download.uploadSpeed | bspeed}}</span></li>
<li class="label" title="Uploaded" ng-show="download.bittorrent"><i class="icon-upload-alt">&nbsp;</i> <span class="download-uploadLength">{{download.uploadLength | blength}}</span></li>
<li class="label" title="Upload Speed" ng-show="download.bittorrent"><i class="icon-upload">&nbsp;</i> <span class="download-uploadSpeed">{{download.fmtUploadSpeed}}</span></li>
<li class="label" title="Uploaded" ng-show="download.bittorrent"><i class="icon-upload-alt">&nbsp;</i> <span class="download-uploadLength">{{download.fmtUploadLength}}</span></li>
<li class="label" title="Connections"><i class="icon-link">&nbsp;</i> <span class="download-connections">{{download.connections}}</span></li>
<li class="label" title="Download GID"><i class="icon-reorder">&nbsp;</i> <span class="download-gid">{{download.gid}}</span></li>
<li class="label" title="Number of Pieces"># of <i class="icon-puzzle-piece">&nbsp;</i> <span class="download-numPieces">{{download.numPieces}}</span></li>
<li class="label" title="Piece Length"><i class="icon-puzzle-piece"></i> Length&nbsp; <span class="download-pieceLength">{{download.pieceLength | blength}}</span></li>
<li class="label" title="Piece Length"><i class="icon-puzzle-piece"></i> Length&nbsp; <span class="download-pieceLength">{{download.fmtPieceLength}}</span></li>
</ul>
<ul class="download-files hidden-phone download-item">
<li class="label" ng-repeat="file in download.files">{{file.relpath}} ({{file.length | blength}})</li>
<li class="label" ng-repeat="file in download.files">{{file.relpath}} ({{file.fmtLength}})</li>
</ul>
<div ng-show="hasStatus(download, 'active')" class="download-item">
<div class="download-graph" dspeed="download.downloadSpeed" uspeed="download.uploadSpeed" dgraph draw="!download.collapsed"></div>

View File

@ -106,10 +106,26 @@ function(
// 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) {
var filter = scope.downloadFilter;
if (!filter.length) return downloads;
if (!scope.downloadFilterCommitted) {
return downloads;
}
var filter = scope.downloadFilterCommitted;
return _.filter(downloads, function(d) {
if (!d.files.length) return true;
@ -187,27 +203,79 @@ function(
// 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 || {};
if (!ctx) {
ctx = {
dir: d.dir,
status: d.status,
gid: d.gid,
numPieces: d.numPieces,
connections: d.connections,
bitfield: d.bitfield,
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),
files: []
};
}
else {
ctx.dir = d.dir;
ctx.status = d.status;
ctx.gid = d.gid;
ctx.numPieces = d.numPieces;
ctx.connections = d.connections;
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);
}
}
_.each([
'totalLength', 'completedLength', 'uploadLength', 'dir',
'pieceLength', 'downloadSpeed', 'uploadSpeed', 'status',
'gid', 'numPieces', 'connections', 'bitfield'
], function(e) {
ctx[e] = d[e];
});
var files = d["files"];
var dlName;
var files = d.files;
if (files) {
var cfiles = ctx["files"] || (ctx["files"] = []);
dlName = files[0].path || d.files[0].uris[0].uri;
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.path = file.path;
cfile.length = file.length;
cfile.fmtLength = utils.fmtsize(file.length);
cfile.relpath = file.path.replace(re_slashes, slash);
if (!cfile.relpath.startsWith("[")) { // METADATA
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);
}
}
@ -215,20 +283,24 @@ function(
cfiles.length = files.length;
}
else {
delete ctx["files"];
delete ctx.files;
}
var btName;
if (d.bittorrent) {
ctx.bittorrentName = d.bittorrent.info && d.bittorrent.info.name;
btName = d.bittorrent.info && d.bittorrent.info.name;
ctx.bittorrent = true;
}
else {
delete ctx.bittorrentName;
delete ctx.bittorrent;
}
ctx.name = btName || utils.getFileName(dlName) || dlName || "Unknown";
// collapse the download details initially
if (ctx.collapsed === undefined)
if (ctx.collapsed === undefined) {
ctx.collapsed = true;
}
return ctx;
};
@ -284,17 +356,6 @@ function(
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;
@ -320,7 +381,7 @@ function(
settings[i].val = vals[i] || settings[i].val;
}
modals.invoke('settings', settings, scope.getName(d) + ' settings', function(settings) {
modals.invoke('settings', settings, scope.name + ' settings', function(settings) {
var sets = {};
for (var i in settings) { sets[i] = settings[i].val };

View File

@ -1,47 +1,25 @@
(function() {
function fmtlen(len) {
len = +len; // coerce to number
if (len <= 1024) {
return len.toFixed(0) + " B";
}
len /= 1024;
if (len <= 1024) {
return len.toFixed(1) + " KB"
}
len /= 1024;
if (len <= 1024) {
return len.toFixed(2) + " MB";
}
len /= 1024;
return len.toFixed(3) + " GB";
angular.module('webui.filters.bytes', ["webui.services.utils"])
.filter('blength', ['$filter', "$utils", function(filter, utils) {
return utils.fmtsize;
}])
.filter('bspeed', ['$filter', "$utils", function(filter, utils) {
return utils.fmtspeed;
}])
.filter('time', function() {
function pad(f) {
return ("0" + f).substr(-2);
}
angular .module('webui.filters.bytes', [])
.filter('blength', ['$filter', function(filter) {
return fmtlen;
}])
.filter('bspeed', ['$filter', function(filter) {
return function(speed) {
return fmtlen(speed) + "/s";
};
}])
.filter('time', function() {
function pad(f) {
return ("0" + f).substr(-2);
}
return function(time) {
time = parseInt(time, 10);
if (!time || !isFinite(time)) return "∞";
var secs = time % 60;
if (time < 60) return secs + "s";
var mins = Math.floor((time % 3600) / 60)
if (time < 3600) return pad(mins) + ":" + pad(secs);
var hrs = Math.floor((time % 86400) / 3600);
if (time < 86400) return pad(hrs) + ":" + pad(mins) + ":" + pad(secs);
var days = Math.floor(time / 86400);
return days + "::" + pad(hrs) + ":" + pad(mins) + ":" + pad(secs);
};
});
})();
return function(time) {
time = parseInt(time, 10);
if (!time || !isFinite(time)) return "∞";
var secs = time % 60;
if (time < 60) return secs + "s";
var mins = Math.floor((time % 3600) / 60)
if (time < 3600) return pad(mins) + ":" + pad(secs);
var hrs = Math.floor((time % 86400) / 3600);
if (time < 86400) return pad(hrs) + ":" + pad(mins) + ":" + pad(secs);
var days = Math.floor(time / 86400);
return days + "::" + pad(hrs) + ":" + pad(mins) + ":" + pad(secs);
};
});

View File

@ -26,7 +26,28 @@ angular.module('webui.services.utils', [])
};
})();
return {
var utils = {
fmtsize: function(len) {
len = +len; // coerce to number
if (len <= 1024) {
return len.toFixed(0) + " B";
}
len /= 1024;
if (len <= 1024) {
return len.toFixed(1) + " KB"
}
len /= 1024;
if (len <= 1024) {
return len.toFixed(2) + " MB";
}
len /= 1024;
return len.toFixed(3) + " GB";
},
fmtspeed: function(speed) {
return utils.fmtsize(speed) + "/s";
},
// saves the key value pair in cookies
setCookie: function(key, value) {
var exdate = new Date();
@ -70,31 +91,29 @@ angular.module('webui.services.utils', [])
};
})(),
randStr: function() {
return this.uuid();
return utils.uuid();
},
// maps the array in place to the destination
// arr, dest (optional): array
// func: a merge mapping func, see ctrls/download.js
mergeMap: function(arr, dest, func) {
if (!dest) dest = [];
for (var i = 0; i < dest.length; i++) {
if (i >= arr.length) {
// remove the deleted downloads
dest.splice(i, dest.length - arr.length);
break;
}
if (!dest[i]) dest[i] = {};
if (!dest) {
dest = [];
}
for (var i = 0, e = Math.min(arr.length, dest.length); i < e; ++i) {
func(arr[i], dest[i]);
}
// insert newly created downloads
// Insert newly created downloads
while (i < arr.length) {
dest.push(func(arr[i++]));
}
// Truncate if necessary.
dest.length = arr.length;
return dest;
},
// get info title from global statistics
@ -144,4 +163,5 @@ angular.module('webui.services.utils', [])
return chunks;
}
};
return utils;
}]);