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> <fieldset>
<div class="span4"> <div class="span4">
<label for="downloadFilter">Search</label> <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> <br>
<b>Found: {{totalDownloads}} / {{totalAria2Downloads()}} </b> <b>Found: {{totalDownloads}} / {{totalAria2Downloads()}} </b>
</div> </div>
@ -277,7 +277,7 @@
<tbody> <tbody>
<tr> <tr>
<td class="download-name download-item" ng-click="download.collapsed = !download.collapsed"> <td class="download-name download-item" ng-click="download.collapsed = !download.collapsed">
{{getName(download)}} {{download.name}}
</td> </td>
<td class="download-controls download-item" rowspan="2"> <td class="download-controls download-item" rowspan="2">
<!-- {{{ download control buttons --> <!-- {{{ download control buttons -->
@ -368,11 +368,11 @@
</li> </li>
<li class="label label-active hidden-phone"> <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>
<li class="label label-active hidden-phone"> <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>
<li class="label label-active hidden-phone hidden-tablet"> <li class="label label-active hidden-phone hidden-tablet">
@ -389,11 +389,11 @@
</li> </li>
<li class="label label-info"> <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>
<li class="label label-info hidden-phone"> <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>
<li class="label label-info hidden-phone"> <li class="label label-info hidden-phone">
@ -436,7 +436,7 @@
</li> </li>
<li class="label label-success"> <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> </li>
<!-- }}} --> <!-- }}} -->
@ -455,7 +455,7 @@
<li ng-show="hasStatus(download, 'removed')" <li ng-show="hasStatus(download, 'removed')"
class="label label-warning"> 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> </li>
<!-- }}} --> <!-- }}} -->
</ul> </ul>
@ -471,7 +471,7 @@
</li> </li>
<li class="label label-important"> <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> </li>
<!-- }}} --> <!-- }}} -->
</ul> </ul>
@ -495,21 +495,21 @@
<ul class="stats download-item"> <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="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="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="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.completedLength | blength}}</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.downloadSpeed | bspeed}}</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="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.uploadLength | blength}}</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="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="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="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>
<ul class="download-files hidden-phone download-item"> <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> </ul>
<div ng-show="hasStatus(download, 'active')" class="download-item"> <div ng-show="hasStatus(download, 'active')" class="download-item">
<div class="download-graph" dspeed="download.downloadSpeed" uspeed="download.uploadSpeed" dgraph draw="!download.collapsed"></div> <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 // download search filter
scope.downloadFilter = ""; 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) { scope.filterDownloads = function(downloads) {
var filter = scope.downloadFilter; if (!scope.downloadFilterCommitted) {
if (!filter.length) return downloads; return downloads;
}
var filter = scope.downloadFilterCommitted;
return _.filter(downloads, function(d) { return _.filter(downloads, function(d) {
if (!d.files.length) return true; if (!d.files.length) return true;
@ -187,27 +203,79 @@ function(
// convert the donwload form aria2 to once used by the view, // convert the donwload form aria2 to once used by the view,
// minor additions of some fields and checks // minor additions of some fields and checks
scope.getCtx = function(d, ctx) { 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([ var dlName;
'totalLength', 'completedLength', 'uploadLength', 'dir', var files = d.files;
'pieceLength', 'downloadSpeed', 'uploadSpeed', 'status',
'gid', 'numPieces', 'connections', 'bitfield'
], function(e) {
ctx[e] = d[e];
});
var files = d["files"];
if (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) { for (var i = 0; i < files.length; ++i) {
var cfile = cfiles[i] || (cfiles[i] = {}); var cfile = cfiles[i] || (cfiles[i] = {});
var file = files[i]; var file = files[i];
if (file.path !== cfile.path) { if (file.path !== cfile.path) {
cfile.path = file.path; cfile.path = file.path;
cfile.length = file.length; cfile.length = file.length;
cfile.fmtLength = utils.fmtsize(file.length);
cfile.relpath = file.path.replace(re_slashes, slash); 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); cfile.relpath = cfile.relpath.substr(ctx.dir.length + 1);
} }
} }
@ -215,20 +283,24 @@ function(
cfiles.length = files.length; cfiles.length = files.length;
} }
else { else {
delete ctx["files"]; delete ctx.files;
} }
var btName;
if (d.bittorrent) { if (d.bittorrent) {
ctx.bittorrentName = d.bittorrent.info && d.bittorrent.info.name; btName = d.bittorrent.info && d.bittorrent.info.name;
ctx.bittorrent = true; ctx.bittorrent = true;
} }
else { else {
delete ctx.bittorrentName; delete ctx.bittorrent;
} }
ctx.name = btName || utils.getFileName(dlName) || dlName || "Unknown";
// collapse the download details initially // collapse the download details initially
if (ctx.collapsed === undefined) if (ctx.collapsed === undefined) {
ctx.collapsed = true; ctx.collapsed = true;
}
return ctx; return ctx;
}; };
@ -284,17 +356,6 @@ function(
return percentage; 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 // gets the type for the download as classified by the aria2 rpc calls
scope.getType = function(d) { scope.getType = function(d) {
var type = d.status; var type = d.status;
@ -320,7 +381,7 @@ function(
settings[i].val = vals[i] || settings[i].val; 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 = {}; var sets = {};
for (var i in settings) { sets[i] = settings[i].val }; for (var i in settings) { sets[i] = settings[i].val };

View File

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

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 // saves the key value pair in cookies
setCookie: function(key, value) { setCookie: function(key, value) {
var exdate = new Date(); var exdate = new Date();
@ -70,31 +91,29 @@ angular.module('webui.services.utils', [])
}; };
})(), })(),
randStr: function() { randStr: function() {
return this.uuid(); return utils.uuid();
}, },
// maps the array in place to the destination // maps the array in place to the destination
// arr, dest (optional): array // arr, dest (optional): array
// func: a merge mapping func, see ctrls/download.js // func: a merge mapping func, see ctrls/download.js
mergeMap: function(arr, dest, func) { mergeMap: function(arr, dest, func) {
if (!dest) dest = []; 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] = {};
for (var i = 0, e = Math.min(arr.length, dest.length); i < e; ++i) {
func(arr[i], dest[i]); func(arr[i], dest[i]);
} }
// insert newly created downloads // Insert newly created downloads
while (i < arr.length) { while (i < arr.length) {
dest.push(func(arr[i++])); dest.push(func(arr[i++]));
} }
// Truncate if necessary.
dest.length = arr.length;
return dest; return dest;
}, },
// get info title from global statistics // get info title from global statistics
@ -144,4 +163,5 @@ angular.module('webui.services.utils', [])
return chunks; return chunks;
} }
}; };
return utils;
}]); }]);