refactor code

master
MaysWind 2016-05-31 22:51:12 +08:00
parent a6ea0c9629
commit 26e25830e8
23 changed files with 668 additions and 590 deletions

View File

@ -44,13 +44,13 @@
</a>
</li>
<li class="divider"></li>
<li ng-class="{'disabled': !isStartableTaskSelected()}">
<a class="toolbar" title="{{'Start' | translate}}" ng-click="startTask()">
<li ng-class="{'disabled': !isSpecifiedTaskSelected('paused')}">
<a class="toolbar" title="{{'Start' | translate}}" ng-click="changeTasksState('start')">
<i class="fa fa-play"></i>
</a>
</li>
<li ng-class="{'disabled': !isPausableTaskSelected()}">
<a class="toolbar" title="{{'Pause' | translate}}" ng-click="pauseTask()">
<li ng-class="{'disabled': !isSpecifiedTaskSelected('active')}">
<a class="toolbar" title="{{'Pause' | translate}}" ng-click="changeTasksState('pause')">
<i class="fa fa-pause"></i>
</a>
</li>
@ -61,7 +61,7 @@
</a>
<ul class="dropdown-menu" role="menu">
<li ng-if="isTaskSelected()">
<a class="pointer-cursor" ng-click="removeTask()">
<a class="pointer-cursor" ng-click="removeTasks()">
<span translate>Remove Task</span>
</a>
</li>
@ -73,7 +73,7 @@
</ul>
</li>
<li class="divider"></li>
<li ng-class="{'disabled': !taskContext.list || taskContext.list.length < 1}">
<li ng-class="{'disabled': !taskContext.enableSelectAll || !taskContext.list || taskContext.list.length < 1}">
<a class="toolbar" title="{{'Select All' | translate}}" ng-click="selectAllTasks()">
<i class="fa fa-th-large"></i>
</a>
@ -259,31 +259,34 @@
<script src="scripts/core/__core.js"></script>
<script src="scripts/core/__fix.js"></script>
<script src="scripts/core/app.js"></script>
<script src="scripts/core/aria2options.js"></script>
<script src="scripts/core/config.js"></script>
<script src="scripts/core/constants.js"></script>
<script src="scripts/core/lang-confs.js"></script>
<script src="scripts/core/lang-default.js"></script>
<script src="scripts/core/router.js"></script>
<script src="scripts/core/utils.js"></script>
<script src="scripts/controllers/list.js"></script>
<script src="scripts/core/root.js"></script>
<script src="scripts/config/constants.js"></script>
<script src="scripts/config/configuration.js"></script>
<script src="scripts/config/initiator.js"></script>
<script src="scripts/config/languages.js"></script>
<script src="scripts/config/language-default.js"></script>
<script src="scripts/config/aria2options.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/controllers/settings-aria2.js"></script>
<script src="scripts/controllers/settings-ariang.js"></script>
<script src="scripts/controllers/status.js"></script>
<script src="scripts/controllers/list.js"></script>
<script src="scripts/controllers/task-detail.js"></script>
<script src="scripts/controllers/settings-ariang.js"></script>
<script src="scripts/controllers/settings-aria2.js"></script>
<script src="scripts/controllers/status.js"></script>
<script src="scripts/directives/placeholder.js"></script>
<script src="scripts/filters/dateDuration.js"></script>
<script src="scripts/filters/filename.js"></script>
<script src="scripts/filters/fileOrderBy.js"></script>
<script src="scripts/filters/percent.js"></script>
<script src="scripts/filters/taskOrderBy.js"></script>
<script src="scripts/filters/taskStatus.js"></script>
<script src="scripts/filters/volumn.js"></script>
<script src="scripts/services/aria2HttpRpcService.js"></script>
<script src="scripts/services/aria2RpcService.js"></script>
<script src="scripts/services/aria2WebSocketRpcService.js"></script>
<script src="scripts/services/ariaNgCommonService.js"></script>
<script src="scripts/services/ariaNgSettingService.js"></script>
<script src="scripts/services/ariaNgTaskService.js"></script>
<script src="scripts/services/aria2SettingService.js"></script>
<script src="scripts/services/aria2RpcService.js"></script>
<script src="scripts/services/aria2HttpRpcService.js"></script>
<script src="scripts/services/aria2WebSocketRpcService.js"></script>
<!-- endbuild -->
</body>
</html>

View File

@ -0,0 +1,18 @@
(function () {
'use strict';
angular.module('ariaNg').config(['$translateProvider', 'localStorageServiceProvider', 'ariaNgConstants', function ($translateProvider, localStorageServiceProvider, ariaNgConstants) {
localStorageServiceProvider
.setPrefix(ariaNgConstants.appPrefix)
.setStorageType('localStorage')
.setStorageCookie(365, '/');
$translateProvider.useStaticFilesLoader({
prefix: 'langs/',
suffix: '.json'
}).useLoaderCache(true)
.preferredLanguage('en-US')
.fallbackLanguage('en-US')
.useSanitizeValueStrategy('escape');
}]);
})();

View File

@ -15,6 +15,7 @@
downloadTaskRefreshInterval: 1000
}).constant('aria2RpcConstants', {
rpcServiceVersion: '2.0',
rpcServiceName: 'aria2'
rpcServiceName: 'aria2',
rpcSystemServiceName: 'system'
});
})();

View File

@ -0,0 +1,14 @@
(function () {
'use strict';
angular.module('ariaNg').run(['amMoment', 'moment', 'ariaNgSettingService', function (amMoment, moment, ariaNgSettingService) {
var language = ariaNgSettingService.getLanguage();
moment.updateLocale('zh-cn', {
week: null
});
amMoment.changeLocale(language);
ariaNgSettingService.applyLanguage(language);
}]);
})();

View File

@ -1,48 +1,30 @@
(function () {
'use strict';
angular.module('ariaNg').controller('DownloadListController', ['$rootScope', '$scope', '$window', '$location', '$interval', 'aria2RpcService', 'ariaNgSettingService', 'utils', function ($rootScope, $scope, $window, $location, $interval, aria2RpcService, ariaNgSettingService, utils) {
angular.module('ariaNg').controller('DownloadListController', ['$rootScope', '$scope', '$window', '$location', '$interval', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgTaskService', function ($rootScope, $scope, $window, $location, $interval, ariaNgCommonService, ariaNgSettingService, ariaNgTaskService) {
var location = $location.path().substring(1);
var downloadTaskRefreshPromise = null;
var needRequestWholeInfo = true;
var refreshDownloadTask = function () {
var invokeMethod = null;
if (location == 'downloading') {
invokeMethod = aria2RpcService.tellActive;
} else if (location == 'waiting') {
invokeMethod = aria2RpcService.tellWaiting;
} else if (location == 'stopped') {
invokeMethod = aria2RpcService.tellStopped;
}
if (invokeMethod) {
return invokeMethod({
requestParams: needRequestWholeInfo ? aria2RpcService.getFullTaskParams() : aria2RpcService.getBasicTaskParams(),
callback: function (result) {
if (!utils.extendArray(result, $rootScope.taskContext.list, 'gid')) {
if (needRequestWholeInfo) {
$rootScope.taskContext.list = result;
needRequestWholeInfo = false;
} else {
needRequestWholeInfo = true;
}
} else {
needRequestWholeInfo = false;
}
if ($rootScope.taskContext.list && $rootScope.taskContext.list.length > 0) {
for (var i = 0; i < $rootScope.taskContext.list.length; i++) {
utils.processDownloadTask($rootScope.taskContext.list[i]);
}
}
return ariaNgTaskService.getTaskList(location, needRequestWholeInfo, function (result) {
if (!ariaNgCommonService.extendArray(result, $rootScope.taskContext.list, 'gid')) {
if (needRequestWholeInfo) {
$rootScope.taskContext.list = result;
needRequestWholeInfo = false;
} else {
needRequestWholeInfo = true;
}
});
}
};
} else {
needRequestWholeInfo = false;
}
$rootScope.loadPromise = refreshDownloadTask();
if ($rootScope.taskContext.list) {
ariaNgTaskService.processDownloadTasks($rootScope.taskContext.list);
$rootScope.taskContext.enableSelectAll = $rootScope.taskContext.list.length > 1;
}
});
};
$scope.filterByTaskName = function (task) {
if (!task || !angular.isString(task.taskName)) {
@ -71,5 +53,7 @@
$interval.cancel(downloadTaskRefreshPromise);
}
});
$rootScope.loadPromise = refreshDownloadTask();
}]);
})();

View File

@ -1,76 +1,12 @@
(function () {
'use strict';
angular.module('ariaNg').controller('MainController', ['$rootScope', '$scope', '$route', '$interval', 'aria2RpcService', 'ariaNgSettingService', 'utils', function ($rootScope, $scope, $route, $interval, aria2RpcService, ariaNgSettingService, utils) {
angular.module('ariaNg').controller('MainController', ['$rootScope', '$scope', '$route', '$interval', 'aria2SettingService', 'ariaNgCommonService', 'ariaNgTaskService', 'ariaNgSettingService', function ($rootScope, $scope, $route, $interval, aria2SettingService, ariaNgCommonService, ariaNgTaskService, ariaNgSettingService) {
var globalStatRefreshPromise = null;
var processStatResult = function (stat) {
var activeCount = parseInt(stat.numActive);
var waitingCount = parseInt(stat.numWaiting);
var totalRunningCount = activeCount + waitingCount;
stat.totalRunningCount = totalRunningCount;
};
var refreshGlobalStat = function () {
aria2RpcService.getGlobalStat({
callback: function (result) {
if (result) {
processStatResult(result);
}
$scope.globalStat = result;
}
});
};
refreshGlobalStat();
$scope.startTask = function () {
var gids = $rootScope.taskContext.getSelectedTaskIds();
if (!gids || gids.length < 1) {
return;
}
$rootScope.loadPromise = aria2RpcService.unpauseMulti({
gids: gids,
callback: function (result) {
$route.reload();
}
});
};
$scope.pauseTask = function () {
var gids = $rootScope.taskContext.getSelectedTaskIds();
if (!gids || gids.length < 1) {
return;
}
$rootScope.loadPromise = aria2RpcService.forcePauseMulti({
gids: gids,
callback: function (result) {
$route.reload();
}
});
};
$scope.removeTask = function () {
var gids = $rootScope.taskContext.getSelectedTaskIds();
if (!gids || gids.length < 1) {
return;
}
utils.confirm('Confirm Remove', 'Are you sure you want to remove the selected task?', 'warning', function () {
});
};
$scope.clearFinishedTasks = function () {
utils.confirm('Confirm Clear', 'Are you sure you want to clear finished tasks?', 'warning', function () {
return aria2SettingService.getGlobalStat(function (result) {
$scope.globalStat = result;
});
};
@ -78,7 +14,7 @@
return $rootScope.taskContext.getSelectedTaskIds().length > 0;
};
$scope.isStartableTaskSelected = function () {
$scope.isSpecifiedTaskSelected = function (status) {
var selectedTasks = $rootScope.taskContext.getSelectedTasks();
if (selectedTasks.length < 1) {
@ -86,7 +22,7 @@
}
for (var i = 0; i < selectedTasks.length; i++) {
if (selectedTasks[i].status == 'paused') {
if (selectedTasks[i].status == status) {
return true;
}
}
@ -94,20 +30,44 @@
return false;
};
$scope.isPausableTaskSelected = function () {
var selectedTasks = $rootScope.taskContext.getSelectedTasks();
$scope.changeTasksState = function (state) {
var gids = $rootScope.taskContext.getSelectedTaskIds();
if (selectedTasks.length < 1) {
return false;
if (!gids || gids.length < 1) {
return;
}
for (var i = 0; i < selectedTasks.length; i++) {
if (selectedTasks[i].status == 'active') {
return true;
}
var invoke = null;
if (state == 'start') {
invoke = ariaNgTaskService.startTasks;
} else if (state == 'pause') {
invoke = ariaNgTaskService.pauseTasks;
} else {
return;
}
return false;
$rootScope.loadPromise = invoke(gids, function (result) {
$route.reload();
});
};
$scope.removeTasks = function () {
var gids = $rootScope.taskContext.getSelectedTaskIds();
if (!gids || gids.length < 1) {
return;
}
ariaNgCommonService.confirm('Confirm Remove', 'Are you sure you want to remove the selected task?', 'warning', function () {
});
};
$scope.clearFinishedTasks = function () {
ariaNgCommonService.confirm('Confirm Clear', 'Are you sure you want to clear finished tasks?', 'warning', function () {
});
};
$scope.selectAllTasks = function () {
@ -115,8 +75,8 @@
};
$scope.changeDisplayOrder = function (type, autoSetReverse) {
var oldType = utils.parseOrderType(ariaNgSettingService.getDisplayOrder());
var newType = utils.parseOrderType(type);
var oldType = ariaNgCommonService.parseOrderType(ariaNgSettingService.getDisplayOrder());
var newType = ariaNgCommonService.parseOrderType(type);
if (autoSetReverse && newType.type == oldType.type) {
newType.reverse = !oldType.reverse;
@ -126,8 +86,8 @@
};
$scope.isSetDisplayOrder = function (type) {
var orderType = utils.parseOrderType(ariaNgSettingService.getDisplayOrder());
var targetType = utils.parseOrderType(type);
var orderType = ariaNgCommonService.parseOrderType(ariaNgSettingService.getDisplayOrder());
var targetType = ariaNgCommonService.parseOrderType(type);
return orderType.equals(targetType);
};
@ -143,5 +103,7 @@
$interval.cancel(globalStatRefreshPromise);
}
});
refreshGlobalStat();
}]);
})();

View File

@ -1,45 +1,24 @@
(function () {
'use strict';
angular.module('ariaNg').controller('Aria2SettingsController', ['$rootScope', '$scope', '$location', '$timeout', 'ariaNgConstants', 'aria2GlobalAvailableOptions', 'aria2RpcService', 'utils', function ($rootScope, $scope, $location, $timeout, ariaNgConstants, aria2GlobalAvailableOptions, aria2RpcService, utils) {
angular.module('ariaNg').controller('Aria2SettingsController', ['$rootScope', '$scope', '$location', '$timeout', 'ariaNgConstants', 'ariaNgCommonService', 'aria2SettingService', function ($rootScope, $scope, $location, $timeout, ariaNgConstants, ariaNgCommonService, aria2SettingService) {
var location = $location.path().substring($location.path().lastIndexOf('/') + 1);
var pendingSaveRequest = {};
var pendingSaveRequests = {};
var getAvailableOptionsKeys = function (location) {
if (location == 'basic') {
return aria2GlobalAvailableOptions.basicOptions;
} else if (location == 'http-ftp-sftp') {
return aria2GlobalAvailableOptions.httpFtpSFtpOptions;
} else if (location == 'http') {
return aria2GlobalAvailableOptions.httpOptions;
} else if (location == 'ftp-sftp') {
return aria2GlobalAvailableOptions.ftpSFtpOptions;
} else if (location == 'bt') {
return aria2GlobalAvailableOptions.btOptions;
} else if (location == 'metalink') {
return aria2GlobalAvailableOptions.metalinkOptions;
} else if (location == 'rpc') {
return aria2GlobalAvailableOptions.rpcOptions;
} else if (location == 'advanced') {
return aria2GlobalAvailableOptions.advancedOptions;
} else {
utils.alert('Type is illegal!');
return false;
}
};
var getAvailableOptions = function (location) {
var keys = getAvailableOptionsKeys(location);
var getAvailableOptions = function (type) {
var keys = aria2SettingService.getAvailableOptionsKeys(type);
if (!keys) {
ariaNgCommonService.alert('Type is illegal!');
return;
}
return utils.getOptions(keys);
return aria2SettingService.getSpecifiedOptions(keys);
};
$scope.optionStatus = {};
$scope.availableOptions = getAvailableOptions(location);
$scope.setGlobalOption = function (option, value, lazySave) {
if (!option || !option.key || option.readonly) {
return;
@ -47,27 +26,21 @@
var key = option.key;
var invoke = function () {
var data = {};
data[key] = value;
$scope.optionStatus[key] = 'saving';
return aria2RpcService.changeGlobalOption({
options: data,
callback: function () {
$scope.optionStatus[key] = 'saved';
}
return aria2SettingService.setGlobalOption(key, value, function (result) {
$scope.optionStatus[key] = 'saved';
});
};
delete $scope.optionStatus[key];
if (lazySave) {
if (pendingSaveRequest[key]) {
$timeout.cancel(pendingSaveRequest[key]);
if (pendingSaveRequests[key]) {
$timeout.cancel(pendingSaveRequests[key]);
}
pendingSaveRequest[key] = $timeout(function () {
pendingSaveRequests[key] = $timeout(function () {
invoke();
}, ariaNgConstants.lazySaveTimeout);
} else {
@ -76,10 +49,8 @@
};
$rootScope.loadPromise = (function () {
return aria2RpcService.getGlobalOption({
callback: function (result) {
$scope.globalOptions = result;
}
return aria2SettingService.getGlobalOption(function (result) {
$scope.globalOptions = result;
});
})();
}]);

View File

@ -1,13 +1,11 @@
(function () {
'use strict';
angular.module('ariaNg').controller('Aria2StatusController', ['$rootScope', '$scope', 'aria2RpcService', function ($rootScope, $scope, aria2RpcService) {
angular.module('ariaNg').controller('Aria2StatusController', ['$rootScope', '$scope', 'aria2SettingService', function ($rootScope, $scope, aria2SettingService) {
$rootScope.loadPromise = (function () {
return aria2RpcService.getVersion({
callback: function (result) {
$scope.serverStatus = result;
}
})
return aria2SettingService.getServerStatus(function (result) {
$scope.serverStatus = result;
});
})();
}]);
})();

View File

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').controller('TaskDetailController', ['$rootScope', '$scope', '$routeParams', '$interval', 'aria2RpcService', 'ariaNgSettingService', 'utils', function ($rootScope, $scope, $routeParams, $interval, aria2RpcService, ariaNgSettingService, utils) {
angular.module('ariaNg').controller('TaskDetailController', ['$rootScope', '$scope', '$routeParams', '$interval', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgTaskService', function ($rootScope, $scope, $routeParams, $interval, ariaNgCommonService, ariaNgSettingService, ariaNgTaskService) {
var tabOrders = ['overview', 'blocks', 'filelist', 'btpeers'];
var downloadTaskRefreshPromise = null;
@ -11,45 +11,32 @@
$scope.healthPercent = 0;
var refreshPeers = function (task) {
return aria2RpcService.getPeers({
gid: task.gid,
callback: function (result) {
if (!utils.extendArray(result, $scope.peers, 'peerId')) {
$scope.peers = result;
}
for (var i = 0; i < $scope.peers.length; i++) {
var peer = $scope.peers[i];
peer.completePercent = utils.estimateCompletedPercentFromBitField(peer.bitfield) * 100;
}
$scope.healthPercent = utils.estimateHealthPercentFromPeers(task, $scope.peers);
var refreshBtPeers = function (task) {
return ariaNgTaskService.getBtTaskPeers(task.gid, function (result) {
if (!ariaNgCommonService.extendArray(result, $scope.peers, 'peerId')) {
$scope.peers = result;
}
})
$scope.healthPercent = ariaNgTaskService.estimateHealthPercentFromPeers(task, $scope.peers);
});
};
var refreshDownloadTask = function () {
return aria2RpcService.tellStatus({
gid: $routeParams.gid,
callback: function (result) {
var task = utils.processDownloadTask(result);
if (task.status == 'active' && task.bittorrent) {
refreshPeers(task);
} else {
if (tabOrders.indexOf('btpeers') >= 0) {
tabOrders.splice(tabOrders.indexOf('btpeers'), 1);
}
return ariaNgTaskService.getTaskStatus($routeParams.gid, function (result) {
if (result.status == 'active' && result.bittorrent) {
refreshBtPeers(result);
} else {
if (tabOrders.indexOf('btpeers') >= 0) {
tabOrders.splice(tabOrders.indexOf('btpeers'), 1);
}
$scope.task = utils.copyObjectTo(task, $scope.task);
$rootScope.taskContext.list = [$scope.task];
$rootScope.taskContext.selected = {};
$rootScope.taskContext.selected[$scope.task.gid] = true;
}
})
$scope.task = ariaNgCommonService.copyObjectTo(result, $scope.task);
$rootScope.taskContext.list = [$scope.task];
$rootScope.taskContext.selected = {};
$rootScope.taskContext.selected[$scope.task.gid] = true;
});
};
$rootScope.swipeActions.extentLeftSwipe = function () {
@ -75,17 +62,14 @@
};
$scope.loadTaskOption = function (task) {
$rootScope.loadPromise = aria2RpcService.getOption({
gid: task.gid,
callback: function (result) {
$scope.options = result;
}
$rootScope.loadPromise = ariaNgTaskService.getTaskOption(task.gid, function (result) {
$scope.options = result;
});
};
$scope.changeFileListDisplayOrder = function (type, autoSetReverse) {
var oldType = utils.parseOrderType(ariaNgSettingService.getFileListDisplayOrder());
var newType = utils.parseOrderType(type);
var oldType = ariaNgCommonService.parseOrderType(ariaNgSettingService.getFileListDisplayOrder());
var newType = ariaNgCommonService.parseOrderType(type);
if (autoSetReverse && newType.type == oldType.type) {
newType.reverse = !oldType.reverse;
@ -95,8 +79,8 @@
};
$scope.isSetFileListDisplayOrder = function (type) {
var orderType = utils.parseOrderType(ariaNgSettingService.getFileListDisplayOrder());
var targetType = utils.parseOrderType(type);
var orderType = ariaNgCommonService.parseOrderType(ariaNgSettingService.getFileListDisplayOrder());
var targetType = ariaNgCommonService.parseOrderType(type);
return orderType.equals(targetType);
};
@ -105,8 +89,6 @@
return ariaNgSettingService.getFileListDisplayOrder();
};
$rootScope.loadPromise = refreshDownloadTask();
if (ariaNgSettingService.getDownloadTaskRefreshInterval() > 0) {
downloadTaskRefreshPromise = $interval(function () {
refreshDownloadTask();
@ -118,5 +100,7 @@
$interval.cancel(downloadTaskRefreshPromise);
}
});
$rootScope.loadPromise = refreshDownloadTask();
}]);
})();

View File

@ -1,34 +1,33 @@
(function () {
'use strict';
angular.module('ariaNg').config(['$translateProvider', 'localStorageServiceProvider', 'ariaNgConstants', function ($translateProvider, localStorageServiceProvider, ariaNgConstants) {
localStorageServiceProvider
.setPrefix(ariaNgConstants.appPrefix)
.setStorageType('localStorage')
.setStorageCookie(365, '/');
angular.module('ariaNg').run(['$rootScope', '$location', '$document', 'SweetAlert', function ($rootScope, $location, $document, SweetAlert) {
var isUrlMatchUrl2 = function (url, url2) {
if (url === url2) {
return true;
}
$translateProvider.useStaticFilesLoader({
prefix: 'langs/',
suffix: '.json'
}).useLoaderCache(true)
.preferredLanguage('en-US')
.fallbackLanguage('en-US')
.useSanitizeValueStrategy('escape');
}]).run(['$translate', 'amMoment', 'moment', 'ariaNgConstants', 'ariaNgSettingService', function ($translate, amMoment, moment, ariaNgConstants, ariaNgSettingService) {
ariaNgSettingService.applyLanguage(ariaNgSettingService.getLanguage());
var index = url2.indexOf(url);
moment.updateLocale('zh-cn', {
week: null
});
if (index !== 0) {
return false;
}
var lastPart = url2.substring(url.length);
if (lastPart.indexOf('/') == 0) {
return true;
}
return false;
};
amMoment.changeLocale(ariaNgSettingService.getLanguage());
}]).run(['$rootScope', '$location', '$document', 'SweetAlert', 'ariaNgConstants', 'utils', function ($rootScope, $location, $document, SweetAlert, ariaNgConstants, utils) {
var setNavbarSelected = function (location) {
angular.element('section.sidebar > ul li').removeClass('active');
angular.element('section.sidebar > ul > li[data-href-match]').each(function (index, element) {
var match = angular.element(element).attr('data-href-match');
if (utils.isUrlMatchUrl2(match, location)) {
if (isUrlMatchUrl2(match, location)) {
angular.element(element).addClass('active');
}
});
@ -36,7 +35,7 @@
angular.element('section.sidebar > ul > li.treeview > ul.treeview-menu > li[data-href-match]').each(function (index, element) {
var match = angular.element(element).attr('data-href-match');
if (utils.isUrlMatchUrl2(match, location)) {
if (isUrlMatchUrl2(match, location)) {
angular.element(element).addClass('active').parent().parent().addClass('active');
}
});
@ -61,6 +60,7 @@
$rootScope.taskContext = {
list: [],
selected: {},
enableSelectAll: false,
getSelectedTaskIds: function () {
var result = [];
@ -150,6 +150,8 @@
$rootScope.taskContext.selected = {};
}
$rootScope.taskContext.enableSelectAll = false;
SweetAlert.close();
});

View File

@ -1,310 +0,0 @@
(function () {
'use strict';
angular.module('ariaNg').factory('utils', ['$location', '$timeout', '$base64', 'SweetAlert', '$translate', 'ariaNgConstants', 'aria2AllOptions', function ($location, $timeout, $base64, SweetAlert, $translate, ariaNgConstants, aria2AllOptions) {
var calculateDownloadRemainTime = function (remainBytes, downloadSpeed) {
if (downloadSpeed == 0) {
return 0;
}
return remainBytes / downloadSpeed;
};
return {
generateUniqueId: function () {
var sourceId = ariaNgConstants.appPrefix + '_' + Math.round(new Date().getTime() / 1000) + '_' + Math.random();
var hashedId = $base64.encode(sourceId);
return hashedId;
},
alert: function (text) {
$timeout(function () {
SweetAlert.swal({
title: $translate.instant('Error'),
text: $translate.instant(text),
type: 'error',
confirmButtonText: $translate.instant('OK')
});
}, 100);
},
confirm: function (title, text, type, callback) {
var options = {
title: $translate.instant(title),
text: $translate.instant(text),
type: type,
showCancelButton: true,
confirmButtonText: $translate.instant('OK'),
cancelButtonText: $translate.instant('Cancel')
};
if (type == 'warning') {
options.confirmButtonColor = '#F39C12';
}
SweetAlert.swal(options, function (isConfirm) {
if (!isConfirm) {
return;
}
if (callback) {
callback();
}
});
},
extendArray: function (sourceArray, targetArray, keyProperty) {
if (!targetArray || !sourceArray || sourceArray.length != targetArray.length) {
return false;
}
for (var i = 0; i < targetArray.length; i++) {
if (targetArray[i][keyProperty] == sourceArray[i][keyProperty]) {
angular.extend(targetArray[i], sourceArray[i]);
} else {
return false;
}
}
return true;
},
copyObjectTo: function (from, to) {
if (!to) {
return from;
}
for (var name in from) {
if (!from.hasOwnProperty(name)) {
continue;
}
var fromValue = from[name];
var toValue = to[name];
if (angular.isObject(fromValue) || angular.isArray(fromValue)) {
to[name] = this.copyObjectTo(from[name], to[name]);
} else {
if (fromValue != toValue) {
to[name] = fromValue;
}
}
}
return to;
},
getFileNameFromPath: function (path) {
if (!path) {
return path;
}
var index = path.lastIndexOf('/');
if (index <= 0 || index == path.length) {
return path;
}
return path.substring(index + 1);
},
isUrlMatchUrl2: function (url, url2) {
if (url === url2) {
return true;
}
var index = url2.indexOf(url);
if (index !== 0) {
return false;
}
var lastPart = url2.substring(url.length);
if (lastPart.indexOf('/') == 0) {
return true;
}
return false;
},
getOptions: function (keys) {
var options = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var option = aria2AllOptions[key];
if (!option) {
continue;
}
option = angular.extend({
key: key,
nameKey: 'options.' + key + '.name',
descriptionKey: 'options.' + key + '.description'
}, option);
options.push(option);
}
return options;
},
getTaskName: function (task) {
var taskName = "";
if (task.bittorrent && task.bittorrent.info) {
taskName = task.bittorrent.info.name;
}
if (!taskName && task.files && task.files.length >= 1) {
taskName = this.getFileNameFromPath(task.files[0].path);
}
if (!taskName && task.files && task.files.length >= 1 && task.files[0].uris && task.files[0].uris.length >= 1) {
taskName = this.getFileNameFromPath(task.files[0].uris[0].uri);
}
if (!taskName) {
taskName = $translate.instant('Unknown');
}
return taskName;
},
processDownloadTask: function (task) {
if (!task) {
return task;
}
task.totalLength = parseInt(task.totalLength);
task.completedLength = parseInt(task.completedLength);
task.uploadSpeed = parseInt(task.uploadSpeed);
task.downloadSpeed = parseInt(task.downloadSpeed);
task.completePercent = (task.totalLength > 0 ? task.completedLength / task.totalLength * 100 : 0);
task.remainPercent = 100 - task.completePercent;
task.taskName = this.getTaskName(task);
task.idle = task.downloadSpeed == 0;
var remainLength = task.totalLength - task.completedLength;
task.remainTime = calculateDownloadRemainTime(remainLength, task.downloadSpeed);
if (task.files) {
for (var i = 0; i < task.files.length; i++) {
var file = task.files[i];
file.fileName = this.getFileNameFromPath(file.path);
file.length = parseInt(file.length);
file.completedLength = parseInt(file.completedLength);
file.completePercent = (file.length > 0 ? file.completedLength / file.length * 100 : 0);
}
}
return task;
},
parseOrderType: function (value) {
var values = value.split(':');
var obj = {
type: values[0],
order: values[1],
equals: function (obj) {
if (angular.isUndefined(obj.order)) {
return this.type === obj.type;
} else {
return this.type === obj.type && this.order === obj.order;
}
},
getValue: function () {
return this.type + ":" + this.order;
}
};
Object.defineProperty(obj, 'reverse', {
get: function () {
return this.order === 'desc';
},
set: function (value) {
this.order = (value ? 'desc' : 'asc');
}
});
return obj;
},
estimateCompletedPercentFromBitField: function (bitfield) {
var totalLength = bitfield.length * 0xf;
var completedLength = 0;
if (totalLength == 0) {
return 0;
}
for (var i = 0; i < bitfield.length; i++) {
var num = parseInt(bitfield[i], 16);
completedLength += num;
}
return completedLength / totalLength;
},
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;
}
}
for (var i = 0; i < peers.length; i++) {
var peer = peers[i];
var bitfield = peer.bitfield;
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 i = 0; i < bitfieldCompletedArr.length; i++) {
bitfieldCompletedArr[i] += bitfieldPieceArr[i];
}
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) {
completed = false;
}
}
if (!completed) {
break;
}
}
var healthPercent = healthBitCount / totalLength * 100;
if (healthPercent < task.completePercent) {
healthPercent = task.completePercent;
}
return healthPercent;
}
};
}]);
})();

View File

@ -1,13 +1,13 @@
(function () {
'use strict';
angular.module("ariaNg").filter('fileOrderBy', ['orderByFilter', 'utils', function (orderByFilter, utils) {
angular.module("ariaNg").filter('fileOrderBy', ['orderByFilter', 'ariaNgCommonService', function (orderByFilter, ariaNgCommonService) {
return function (array, type) {
if (!angular.isArray(array)) {
return array;
}
var orderType = utils.parseOrderType(type);
var orderType = ariaNgCommonService.parseOrderType(type);
if (orderType == null) {
return array;

View File

@ -1,9 +0,0 @@
(function () {
'use strict';
angular.module("ariaNg").filter('filename', ['utils', function (utils) {
return function (path) {
return utils.getFileNameFromPath(path);
}
}]);
})();

View File

@ -1,13 +1,13 @@
(function () {
'use strict';
angular.module("ariaNg").filter('taskOrderBy', ['orderByFilter', 'utils', function (orderByFilter, utils) {
angular.module("ariaNg").filter('taskOrderBy', ['orderByFilter', 'ariaNgCommonService', function (orderByFilter, ariaNgCommonService) {
return function (array, type) {
if (!angular.isArray(array)) {
return array;
}
var orderType = utils.parseOrderType(type);
var orderType = ariaNgCommonService.parseOrderType(type);
if (orderType == null) {
return array;

View File

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2RpcService', ['$q', 'aria2RpcConstants', 'ariaNgSettingService', 'aria2HttpRpcService', 'aria2WebSocketRpcService', 'utils', function ($q, aria2RpcConstants, ariaNgSettingService, aria2HttpRpcService, aria2WebSocketRpcService, utils) {
angular.module('ariaNg').factory('aria2RpcService', ['$q', 'aria2RpcConstants', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2HttpRpcService', 'aria2WebSocketRpcService', function ($q, aria2RpcConstants, ariaNgCommonService, ariaNgSettingService, aria2HttpRpcService, aria2WebSocketRpcService) {
var protocol = ariaNgSettingService.getProtocol();
var getAria2MethodFullName = function (methodName) {
@ -9,10 +9,10 @@
};
var invoke = function (method, context) {
context.uniqueId = utils.generateUniqueId();
context.uniqueId = ariaNgCommonService.generateUniqueId();
context.requestBody = {
jsonrpc: aria2RpcConstants.rpcServiceVersion,
method: (method.indexOf('system.') != 0 ? getAria2MethodFullName(method) : method),
method: (method.indexOf(aria2RpcConstants.rpcSystemServiceName + '.') != 0 ? getAria2MethodFullName(method) : method),
id: context.uniqueId,
params: context.params
};

View File

@ -0,0 +1,96 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2SettingService', ['aria2AllOptions', 'aria2GlobalAvailableOptions', 'aria2RpcService', function (aria2AllOptions, aria2GlobalAvailableOptions, aria2RpcService) {
var processStatResult = function (stat) {
if (!stat) {
return stat;
}
var activeCount = parseInt(stat.numActive);
var waitingCount = parseInt(stat.numWaiting);
var totalRunningCount = activeCount + waitingCount;
stat.totalRunningCount = totalRunningCount;
return stat;
};
return {
getAvailableOptionsKeys: function (type) {
if (type == 'basic') {
return aria2GlobalAvailableOptions.basicOptions;
} else if (type == 'http-ftp-sftp') {
return aria2GlobalAvailableOptions.httpFtpSFtpOptions;
} else if (type == 'http') {
return aria2GlobalAvailableOptions.httpOptions;
} else if (type == 'ftp-sftp') {
return aria2GlobalAvailableOptions.ftpSFtpOptions;
} else if (type == 'bt') {
return aria2GlobalAvailableOptions.btOptions;
} else if (type == 'metalink') {
return aria2GlobalAvailableOptions.metalinkOptions;
} else if (type == 'rpc') {
return aria2GlobalAvailableOptions.rpcOptions;
} else if (type == 'advanced') {
return aria2GlobalAvailableOptions.advancedOptions;
} else {
return false;
}
},
getSpecifiedOptions: function (keys) {
var options = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var option = aria2AllOptions[key];
if (!option) {
continue;
}
option = angular.extend({
key: key,
nameKey: 'options.' + key + '.name',
descriptionKey: 'options.' + key + '.description'
}, option);
options.push(option);
}
return options;
},
getGlobalOption: function (callback) {
return aria2RpcService.getGlobalOption({
callback: callback
});
},
setGlobalOption: function (key, value, callback) {
var data = {};
data[key] = value;
return aria2RpcService.changeGlobalOption({
options: data,
callback: callback
});
},
getServerStatus: function (callback) {
return aria2RpcService.getVersion({
callback: callback
})
},
getGlobalStat: function (callback) {
return aria2RpcService.getGlobalStat({
callback: function (result) {
if (!callback) {
return;
}
var stat = processStatResult(result);
callback(stat);
}
});
}
};
}]);
})();

View File

@ -0,0 +1,116 @@
(function () {
'use strict';
angular.module('ariaNg').factory('ariaNgCommonService', ['$location', '$timeout', '$base64', 'SweetAlert', '$translate', 'ariaNgConstants', function ($location, $timeout, $base64, SweetAlert, $translate, ariaNgConstants) {
return {
generateUniqueId: function () {
var sourceId = ariaNgConstants.appPrefix + '_' + Math.round(new Date().getTime() / 1000) + '_' + Math.random();
var hashedId = $base64.encode(sourceId);
return hashedId;
},
alert: function (text) {
$timeout(function () {
SweetAlert.swal({
title: $translate.instant('Error'),
text: $translate.instant(text),
type: 'error',
confirmButtonText: $translate.instant('OK')
});
}, 100);
},
confirm: function (title, text, type, callback) {
var options = {
title: $translate.instant(title),
text: $translate.instant(text),
type: type,
showCancelButton: true,
confirmButtonText: $translate.instant('OK'),
cancelButtonText: $translate.instant('Cancel')
};
if (type == 'warning') {
options.confirmButtonColor = '#F39C12';
}
SweetAlert.swal(options, function (isConfirm) {
if (!isConfirm) {
return;
}
if (callback) {
callback();
}
});
},
extendArray: function (sourceArray, targetArray, keyProperty) {
if (!targetArray || !sourceArray || sourceArray.length != targetArray.length) {
return false;
}
for (var i = 0; i < targetArray.length; i++) {
if (targetArray[i][keyProperty] == sourceArray[i][keyProperty]) {
angular.extend(targetArray[i], sourceArray[i]);
} else {
return false;
}
}
return true;
},
copyObjectTo: function (from, to) {
if (!to) {
return from;
}
for (var name in from) {
if (!from.hasOwnProperty(name)) {
continue;
}
var fromValue = from[name];
var toValue = to[name];
if (angular.isObject(fromValue) || angular.isArray(fromValue)) {
to[name] = this.copyObjectTo(from[name], to[name]);
} else {
if (fromValue != toValue) {
to[name] = fromValue;
}
}
}
return to;
},
parseOrderType: function (value) {
var values = value.split(':');
var obj = {
type: values[0],
order: values[1],
equals: function (obj) {
if (angular.isUndefined(obj.order)) {
return this.type === obj.type;
} else {
return this.type === obj.type && this.order === obj.order;
}
},
getValue: function () {
return this.type + ":" + this.order;
}
};
Object.defineProperty(obj, 'reverse', {
get: function () {
return this.order === 'desc';
},
set: function (value) {
this.order = (value ? 'desc' : 'asc');
}
});
return obj;
}
};
}]);
})();

View File

@ -59,7 +59,7 @@
$translate.use(lang);
amMoment.changeLocale(lang);
return true;
},
getLanguage: function () {

View File

@ -0,0 +1,248 @@
(function () {
'use strict';
angular.module('ariaNg').factory('ariaNgTaskService', ['$translate', 'aria2RpcService', function ($translate, aria2RpcService) {
var getFileNameFromPath = function (path) {
if (!path) {
return path;
}
var index = path.lastIndexOf('/');
if (index <= 0 || index == path.length) {
return path;
}
return path.substring(index + 1);
};
var calculateDownloadRemainTime = function (remainBytes, downloadSpeed) {
if (downloadSpeed == 0) {
return 0;
}
return remainBytes / downloadSpeed;
};
var getTaskName = function (task) {
var taskName = "";
if (task.bittorrent && task.bittorrent.info) {
taskName = task.bittorrent.info.name;
}
if (!taskName && task.files && task.files.length >= 1) {
taskName = getFileNameFromPath(task.files[0].path);
}
if (!taskName && task.files && task.files.length >= 1 && task.files[0].uris && task.files[0].uris.length >= 1) {
taskName = getFileNameFromPath(task.files[0].uris[0].uri);
}
if (!taskName) {
taskName = $translate.instant('Unknown');
}
return taskName;
};
var processDownloadTask = function (task) {
if (!task) {
return task;
}
task.totalLength = parseInt(task.totalLength);
task.completedLength = parseInt(task.completedLength);
task.uploadSpeed = parseInt(task.uploadSpeed);
task.downloadSpeed = parseInt(task.downloadSpeed);
task.completePercent = (task.totalLength > 0 ? task.completedLength / task.totalLength * 100 : 0);
task.remainPercent = 100 - task.completePercent;
task.taskName = getTaskName(task);
task.idle = task.downloadSpeed == 0;
var remainLength = task.totalLength - task.completedLength;
task.remainTime = calculateDownloadRemainTime(remainLength, task.downloadSpeed);
if (task.files) {
for (var i = 0; i < task.files.length; i++) {
var file = task.files[i];
file.fileName = getFileNameFromPath(file.path);
file.length = parseInt(file.length);
file.completedLength = parseInt(file.completedLength);
file.completePercent = (file.length > 0 ? file.completedLength / file.length * 100 : 0);
}
}
return task;
};
var estimateCompletedPercentFromBitField = function (bitfield) {
var totalLength = bitfield.length * 0xf;
var completedLength = 0;
if (totalLength == 0) {
return 0;
}
for (var i = 0; i < bitfield.length; i++) {
var num = parseInt(bitfield[i], 16);
completedLength += num;
}
return completedLength / totalLength;
};
return {
getTaskList: function (type, full, callback) {
var invokeMethod = null;
if (type == 'downloading') {
invokeMethod = aria2RpcService.tellActive;
} else if (type == 'waiting') {
invokeMethod = aria2RpcService.tellWaiting;
} else if (type == 'stopped') {
invokeMethod = aria2RpcService.tellStopped;
} else {
return;
}
return invokeMethod({
requestParams: full ? aria2RpcService.getFullTaskParams() : aria2RpcService.getBasicTaskParams(),
callback: function (result) {
if (!callback) {
return;
}
callback(result);
}
});
},
getTaskStatus: function (gid, callback) {
return aria2RpcService.tellStatus({
gid: gid,
callback: function (result) {
if (!callback) {
return;
}
var task = processDownloadTask(result);
callback(task);
}
});
},
getTaskOption: function (gid, callback) {
return aria2RpcService.getOption({
gid: gid,
callback: callback
});
},
getBtTaskPeers: function (gid, callback) {
return aria2RpcService.getPeers({
gid: gid,
callback: function (result) {
if (!callback) {
return;
}
if (result) {
for (var i = 0; i < result.length; i++) {
var peer = result[i];
peer.completePercent = estimateCompletedPercentFromBitField(peer.bitfield) * 100;
}
}
callback(result);
}
});
},
startTasks: function (gids, callback) {
return aria2RpcService.unpauseMulti({
gids: gids,
callback: callback
});
},
pauseTasks: function (gids, callback) {
return aria2RpcService.forcePauseMulti({
gids: gids,
callback: callback
});
},
processDownloadTasks: function (tasks) {
if (!angular.isArray(tasks)) {
return;
}
for (var i = 0; i < tasks.length; i++) {
processDownloadTask(tasks[i]);
}
},
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;
}
}
for (var i = 0; i < peers.length; i++) {
var peer = peers[i];
var bitfield = peer.bitfield;
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 i = 0; i < bitfieldCompletedArr.length; i++) {
bitfieldCompletedArr[i] += bitfieldPieceArr[i];
}
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) {
completed = false;
}
}
if (!completed) {
break;
}
}
var healthPercent = healthBitCount / totalLength * 100;
if (healthPercent < task.completePercent) {
healthPercent = task.completePercent;
}
return healthPercent;
}
};
}]);
})();

View File

@ -31,7 +31,7 @@
<span ng-bind="task.taskName"></span>
</div>
</div>
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>File Size</span>
</div>
@ -40,7 +40,7 @@
<span ng-bind="'(' + task.files.length + ' ' + ('Files' | translate) + ')'"></span>
</div>
</div>
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>Task Status</span>
</div>
@ -48,7 +48,7 @@
<span ng-bind="task | taskStatus"></span>
</div>
</div>
<div class="row" ng-if="task.status == 'error' && task.errorMessage">
<div class="row" ng-if="task && task.status == 'error' && task.errorMessage">
<div class="setting-key col-sm-4">
<span translate>Error Description</span>
</div>
@ -56,7 +56,7 @@
<span ng-bind="task.errorMessage"></span>
</div>
</div>
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span ng-bind="('Completed Percent' | translate) + (task.status == 'active' && task.bittorrent ? ' (' + ('Health Percent' | translate) + ')' : '')"></span>
</div>
@ -64,7 +64,7 @@
<span ng-bind="(task.completePercent | percent: 2) + '%' + (task.status == 'active' && task.bittorrent ? ' (' + (healthPercent | percent: 2) + '%' + ')' : '')"></span>
</div>
</div>
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>Download</span>
</div>
@ -72,7 +72,7 @@
<span ng-bind="(task.completedLength | readableVolumn) + (task.status == 'active' ? ' @ ' + (task.downloadSpeed | readableVolumn) + '/s' : '')"></span>
</div>
</div>
<div class="row" ng-if="task.bittorrent">
<div class="row" ng-if="task && task.bittorrent">
<div class="setting-key col-sm-4">
<span translate>Upload</span>
</div>
@ -80,7 +80,7 @@
<span ng-bind="(task.uploadLength | readableVolumn) + (task.status == 'active' ? ' @ ' + (task.uploadSpeed | readableVolumn) + '/s' : '')"></span>
</div>
</div>
<div class="row" ng-if="task.status == 'active' && task.completedLength < task.totalLength">
<div class="row" ng-if="task && task.status == 'active' && task.completedLength < task.totalLength">
<div class="setting-key col-sm-4">
<span translate>Remain Time</span>
</div>
@ -88,7 +88,7 @@
<span ng-bind="0 <= task.remainTime && task.remainTime < 86400? (task.remainTime | dateDuration: 'second': 'HH:mm:ss') : ('More Than One Day' | translate)"></span>
</div>
</div>
<div class="row" ng-if="task.infoHash">
<div class="row" ng-if="task && task.infoHash">
<div class="setting-key col-sm-4">
<span translate>Info Hash</span>
</div>
@ -96,7 +96,7 @@
<span ng-bind="task.infoHash"></span>
</div>
</div>
<div class="row" ng-if="task.status == 'active'">
<div class="row" ng-if="task && task.status == 'active'">
<div class="setting-key col-sm-4">
<span ng-bind="(task.bittorrent ? ('Seeders' | translate) + ' / ' : '') + ('Connections' | translate)">Connections</span>
</div>
@ -104,7 +104,7 @@
<span ng-bind="(task.numSeeders ? (task.numSeeders + ' / ') : '') + task.connections"></span>
</div>
</div>
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>Download Dir</span>
</div>