diff --git a/app/index.html b/app/index.html index 9778ae0..6a40d20 100644 --- a/app/index.html +++ b/app/index.html @@ -286,6 +286,8 @@ + + diff --git a/app/langs/zh_CN.json b/app/langs/zh_CN.json index 2de719f..8d7c9ec 100644 --- a/app/langs/zh_CN.json +++ b/app/langs/zh_CN.json @@ -30,6 +30,7 @@ "Error Occurred": "发生错误", "Removed": "已删除", "Downloaded / Stopped": "已完成 / 已停止", + "Uncompleted": "未完成", "Settings": "系统设置", "AriaNg Settings": "AriaNg 设置", "Aria2 Settings": "Aria2 设置", diff --git a/app/scripts/config/defaultLanguage.js b/app/scripts/config/defaultLanguage.js index bd8fc84..fc69afd 100644 --- a/app/scripts/config/defaultLanguage.js +++ b/app/scripts/config/defaultLanguage.js @@ -34,6 +34,7 @@ 'Error Occurred': 'Error Occurred', 'Removed': 'Removed', 'Downloaded / Stopped': 'Downloaded / Stopped', + 'Uncompleted': 'Uncompleted', 'Settings': 'Settings', 'AriaNg Settings': 'AriaNg Settings', 'Aria2 Settings': 'Aria2 Settings', diff --git a/app/scripts/directives/pieceBar.js b/app/scripts/directives/pieceBar.js new file mode 100644 index 0000000..14e354b --- /dev/null +++ b/app/scripts/directives/pieceBar.js @@ -0,0 +1,48 @@ +(function () { + 'use strict'; + + angular.module('ariaNg').directive('ngPieceBar', ['aria2TaskService', function (aria2TaskService) { + return { + restrict: 'E', + template: '', + replace: true, + scope: { + bitField: '=', + pieceCount: '=', + color: '@' + }, + link: function (scope, element) { + var redraw = function () { + var canvas = element[0]; + var combinedPieces = aria2TaskService.getCombinedPieces(scope.bitField, scope.pieceCount); + var context = canvas.getContext('2d'); + context.fillStyle = scope.color || '#000'; + context.clearRect(0, 0, canvas.width, canvas.height); + + var posX = 0; + var width = canvas.width; + var height = canvas.height; + + for (var i = 0; i < combinedPieces.length; i++) { + var piece = combinedPieces[i]; + var pieceWidth = piece.count / scope.pieceCount * width; + + if (piece.isCompleted) { + context.fillRect(posX, 0, pieceWidth, height); + } + + posX += pieceWidth; + } + }; + + scope.$watch('bitField', function () { + redraw(); + }); + + scope.$watch('pieceNumber', function () { + redraw(); + }); + } + }; + }]); +})(); diff --git a/app/scripts/directives/pieceMap.js b/app/scripts/directives/pieceMap.js new file mode 100644 index 0000000..65f00f0 --- /dev/null +++ b/app/scripts/directives/pieceMap.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + angular.module('ariaNg').directive('ngPieceMap', ['aria2TaskService', function (aria2TaskService) { + return { + restrict: 'E', + template: '
', + replace: true, + scope: { + bitField: '=', + pieceCount: '=' + }, + link: function (scope, element) { + var redraw = function () { + var pieces = aria2TaskService.getPieceStatus(scope.bitField, scope.pieceCount); + element.empty(); + + for (var i = 0; i < pieces.length; i++) { + element.append(''); + } + }; + + scope.$watch('bitField', function () { + redraw(); + }); + + scope.$watch('pieceNumber', function () { + redraw(); + }); + } + }; + }]); +})(); diff --git a/app/scripts/services/aria2TaskService.js b/app/scripts/services/aria2TaskService.js index 3d85f0f..bee42f7 100644 --- a/app/scripts/services/aria2TaskService.js +++ b/app/scripts/services/aria2TaskService.js @@ -283,56 +283,78 @@ processDownloadTask(tasks[i]); } }, + getPieceStatus: function (bitField, pieceCount) { + var pieces = []; + + if (!bitField) { + return pieces; + } + + var pieceIndex = 0; + + for (var i = 0; i < bitField.length; i++) { + var bitSet = parseInt(bitField[i], 16); + + for (var j = 1; j <= 4; j++) { + var bit = (1 << (4 - j)); + var isCompleted = (bitSet & bit) == bit; + + pieces.push(isCompleted ? 1 : 0); + pieceIndex++; + + if (pieceIndex >= pieceCount) { + break; + } + } + } + + return pieces; + }, + getCombinedPieces: function (bitField, pieceCount) { + var pieces = this.getPieceStatus(bitField, pieceCount); + var combinedPieces = []; + + for (var i = 0; i < pieces.length; i++) { + var isCompleted = (pieces[i] == 1); + + if (combinedPieces.length > 0 && combinedPieces[combinedPieces.length - 1].isCompleted == isCompleted) { + combinedPieces[combinedPieces.length - 1].count++; + } else { + combinedPieces.push({ + isCompleted: isCompleted, + count: 1 + }); + } + } + + return combinedPieces; + }, estimateHealthPercentFromPeers: function (task, peers) { if (peers.length < 1) { return task.completePercent; } - var bitfieldCompletedArr = new Array(task.bitfield.length); - var bitfieldPieceArr = new Array(task.bitfield.length); - var totalLength = task.bitfield.length * 0xf; - var healthBitCount = 0; - - for (var i = 0; i < task.bitfield.length; i++) { - var num = parseInt(task.bitfield[i], 16); - bitfieldCompletedArr[i] = 0; - bitfieldPieceArr[i] = 0; - - if (num == 0xf) { - bitfieldCompletedArr[i] = num; - } else { - bitfieldPieceArr[i] = num; - } - } + var pieces = this.getPieceStatus(task.bitfield, task.numPieces); for (var i = 0; i < peers.length; i++) { var peer = peers[i]; - var bitfield = peer.bitfield; + var peerPieces = this.getPieceStatus(peer.bitfield, task.numPieces); - for (var j = 0; j < bitfield.length; j++) { - var num = parseInt(bitfield[j], 16); - - if (num == 0xf) { - bitfieldCompletedArr[j] += num; - } else { - bitfieldPieceArr[j] = Math.max(bitfieldPieceArr[j], num); - } + for (var j = 0; j < peerPieces.length; j++) { + pieces[j] += peerPieces[j]; } } - for (var i = 0; i < bitfieldCompletedArr.length; i++) { - bitfieldCompletedArr[i] += bitfieldPieceArr[i]; - } + var competedPieceCount = 0; while (true) { var completed = true; - for (var i = 0; i < bitfieldCompletedArr.length; i++) { - var bitCount = Math.min(bitfieldCompletedArr[i], 0xf); - healthBitCount += bitCount; - bitfieldCompletedArr[i] -= bitCount; - - if (bitCount < 0xf) { + for (var i = 0; i < pieces.length; i++) { + if (pieces[i] > 0) { + competedPieceCount++; + pieces[i]--; + } else { completed = false; } } @@ -342,7 +364,7 @@ } } - var healthPercent = healthBitCount / totalLength * 100; + var healthPercent = competedPieceCount / task.numPieces * 100; if (healthPercent < task.completePercent) { healthPercent = task.completePercent; diff --git a/app/styles/aria-ng.css b/app/styles/aria-ng.css index 1791d37..eeb7bdc 100644 --- a/app/styles/aria-ng.css +++ b/app/styles/aria-ng.css @@ -623,6 +623,46 @@ td { overflow-x: hidden; } +/* piece-bar / piece-map */ +.piece-bar-wrapper { + height: 20px; +} + +.piece-bar { + width: 100%; +} + +.piece-map { + padding-left: 6px; + padding-right: 2px; + line-height: 11px; +} + +.piece-legends { + text-align: center; + margin-top: 4px; + margin-bottom: 4px; +} + +.piece-legend { + display: inline-block; + margin-right: 4px; +} + +.piece-map .piece, .piece-legend .piece { + width: 10px; + height: 10px; + background-color: #eef2f4; + border: #dee2e5 solid 1px; + display: inline-block; + margin-right: 1px; +} + +.piece-map .piece.piece-completed, .piece-legend .piece.piece-completed { + background-color: #b8dd69; + border-color: #b8dd69; +} + /* task-table */ .task-table { margin-left: 15px; @@ -831,3 +871,4 @@ td { } } +/* miscellaneous */ diff --git a/app/views/task-detail.html b/app/views/task-detail.html index 9033a21..9642a11 100644 --- a/app/views/task-detail.html +++ b/app/views/task-detail.html @@ -130,7 +130,17 @@