<commit_before>(function () {
    'use strict';

    angular.module('ariaNg').factory('aria2TaskService', ['$q', '$translate', 'aria2RpcService', 'ariaNgCommonService', function ($q, $translate, aria2RpcService, ariaNgCommonService) {
        var getFileName = function (file) {
            if (!file) {
                return '';
            }

            var path = file.path;

            if (!path && file.uris && file.uris.length > 0) {
                path = file.uris[0].uri;
            }

            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 = "";
            var success = true;

            if (task.bittorrent && task.bittorrent.info) {
                taskName = task.bittorrent.info.name;
            }

            if (!taskName && task.files && task.files.length > 0) {
                taskName = getFileName(task.files[0]);
            }

            if (!taskName) {
                taskName = $translate.instant('Unknown');
                success = false;
            }

            return {
                name: taskName,
                success: success
            };
        };

        var processDownloadTask = function (task) {
            if (!task) {
                return task;
            }

            task.totalLength = parseInt(task.totalLength);
            task.completedLength = parseInt(task.completedLength);
            task.completePercent = (task.totalLength > 0 ? task.completedLength / task.totalLength * 100 : 0);
            task.remainLength = task.totalLength - task.completedLength;
            task.remainPercent = 100 - task.completePercent;
            task.uploadLength = (task.uploadLength ? parseInt(task.uploadLength) : 0);
            task.shareRatio = (task.completedLength > 0 ? task.uploadLength / task.completedLength : 0);

            task.uploadSpeed = parseInt(task.uploadSpeed);
            task.downloadSpeed = parseInt(task.downloadSpeed);

            task.idle = task.downloadSpeed == 0;
            task.remainTime = calculateDownloadRemainTime(task.remainLength, task.downloadSpeed);
            task.seeder = (task.seeder === true || task.seeder === 'true');

            var taskNameResult = getTaskName(task);
            task.taskName = taskNameResult.name;
            task.hasTaskName = taskNameResult.success;

            if (task.files) {
                var selectedFileCount = 0;

                for (var i = 0; i < task.files.length; i++) {
                    var file = task.files[i];
                    file.fileName = getFileName(file);
                    file.length = parseInt(file.length);
                    file.selected = (file.selected === true || file.selected === 'true');
                    file.completedLength = parseInt(file.completedLength);
                    file.completePercent = (file.length > 0 ? file.completedLength / file.length * 100 : 0);

                    selectedFileCount += file.selected ? 1 : 0;
                }

                task.selectedFileCount = selectedFileCount;
            }

            return task;
        };

        var processBtPeers = function (peers, task, includeLocalPeer) {
            if (!peers) {
                return peers;
            }

            var localTaskCompletedPieces = getPieceStatus(task.bitfield, task.numPieces);
            var localTaskCompletedPieceCount = ariaNgCommonService.countArray(localTaskCompletedPieces, true);
            var localTaskCompletedPercent = task.completePercent;

            for (var i = 0; i < peers.length; i++) {
                var peer = peers[i];
                var upstreamToSpeed = peer.uploadSpeed;
                var downstreamFromSpeed = peer.downloadSpeed;
                var completedPieces = getPieceStatus(peer.bitfield, task.numPieces);
                var completedPieceCount = ariaNgCommonService.countArray(completedPieces, true);

                peer.name = peer.ip + ':' + peer.port;
                peer.completePercent = completedPieceCount / task.numPieces * 100;
                peer.downloadSpeed = upstreamToSpeed;
                peer.uploadSpeed = downstreamFromSpeed;
                peer.seeder = (peer.seeder === true || peer.seeder === 'true');

                if (completedPieceCount == localTaskCompletedPieceCount && peer.completePercent != localTaskCompletedPercent) {
                    peer.completePercent = localTaskCompletedPercent;
                }
            }

            if (includeLocalPeer) {
                peers.push(createLocalPeerFromTask(task));
            }

            return peers;
        };

        var getPieceStatus = function (bitField, pieceCount) {
            var pieces = [];

            for (var i = 0; i < pieceCount; i++) {
                pieces.push(false);
            }

            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[pieceIndex++] = isCompleted;

                    if (pieceIndex >= pieceCount) {
                        return pieces;
                    }
                }
            }

            return pieces;
        };

        var getCombinedPieces = function (bitField, pieceCount) {
            var pieces = getPieceStatus(bitField, pieceCount);
            var combinedPieces = [];

            for (var i = 0; i < pieces.length; i++) {
                var isCompleted = pieces[i];

                if (combinedPieces.length > 0 && combinedPieces[combinedPieces.length - 1].isCompleted == isCompleted) {
                    combinedPieces[combinedPieces.length - 1].count++;
                } else {
                    combinedPieces.push({
                        isCompleted: isCompleted,
                        count: 1
                    });
                }
            }

            return combinedPieces;
        };

        var createEventCallback = function (getTaskStatusFunc, callback, type) {
            return function (event) {
                var context = {
                    type: type,
                    task: null
                };

                if (event && event.gid) {
                    getTaskStatusFunc(event.gid, function (response) {
                        context.task = (response.success ? response.data : null);
                        callback(context);
                    }, true);
                } else {
                    callback(context);
                }
            }
        };

        var createLocalPeerFromTask = function (task) {
            return {
                local: true,
                bitfield: task.bitfield,
                completePercent: task.completePercent,
                downloadSpeed: task.downloadSpeed,
                name: '(local)',
                seeder: task.seeder,
                uploadSpeed: task.uploadSpeed
            };
        };

        return {
            getTaskList: function (type, full, callback, silent) {
                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({
                    requestWholeInfo: full,
                    requestParams: full ? aria2RpcService.getFullTaskParams() : aria2RpcService.getBasicTaskParams(),
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        callback(response);
                    }
                });
            },
            getTaskStatus: function (gid, callback, silent) {
                return aria2RpcService.tellStatus({
                    gid: gid,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        if (response.success) {
                            processDownloadTask(response.data);
                        }

                        callback(response);
                    }
                });
            },
            getTaskOptions: function (gid, callback, silent) {
                return aria2RpcService.getOption({
                    gid: gid,
                    silent: !!silent,
                    callback: callback
                });
            },
            setTaskOption: function (gid, key, value, callback, silent) {
                var data = {};
                data[key] = value;

                return aria2RpcService.changeOption({
                    gid: gid,
                    options: data,
                    silent: !!silent,
                    callback: callback
                });
            },
            selectTaskFile: function (gid, selectedFileIndexArr, callback, silent) {
                var selectedFileIndex = '';

                for (var i = 0; i < selectedFileIndexArr.length; i++) {
                    if (selectedFileIndex.length > 0) {
                        selectedFileIndex += ',';
                    }

                    selectedFileIndex += selectedFileIndexArr[i];
                }

                return this.setTaskOption(gid, 'select-file', selectedFileIndex, callback, silent);
            },
            getBtTaskPeers: function (task, callback, silent, includeLocalPeer) {
                return aria2RpcService.getPeers({
                    gid: task.gid,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        if (response.success) {
                            processBtPeers(response.data, task, includeLocalPeer);
                        }

                        callback(response);
                    }
                });
            },
            getTaskStatusAndBtPeers: function (gid, callback, silent, requirePeers, includeLocalPeer) {
                var methods = [
                    aria2RpcService.tellStatus({ gid: gid }, true)
                ];

                if (requirePeers) {
                    methods.push(aria2RpcService.getPeers({ gid: gid }, true));
                }

                return aria2RpcService.multicall({
                    methods: methods,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        response.task = {};

                        if (response.success && response.data.length > 0) {
                            response.task = response.data[0][0];
                            processDownloadTask(response.task);
                        }

                        if (response.success && response.task.bittorrent && response.data.length > 1) {
                            response.peers = response.data[1][0];
                            processBtPeers(response.peers, response.task, includeLocalPeer);
                        }

                        callback(response);
                    }
                });
            },
            newUriTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addUri({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newUriTasks: function (tasks, pauseOnAdded, callback, silent) {
                return aria2RpcService.addUriMulti({
                    tasks: tasks,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newTorrentTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addTorrent({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newMetalinkTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addMetalink({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            startTasks: function (gids, callback, silent) {
                return aria2RpcService.unpauseMulti({
                    gids: gids,
                    silent: !!silent,
                    callback: callback
                });
            },
            pauseTasks: function (gids, callback, silent) {
                return aria2RpcService.forcePauseMulti({
                    gids: gids,
                    silent: !!silent,
                    callback: callback
                });
            },
            removeTasks: function (tasks, callback, silent) {
                var runningTaskGids = [];
                var stoppedTaskGids = [];

                for (var i = 0; i < tasks.length; i++) {
                    if (tasks[i].status == 'complete' || tasks[i].status == 'error' || tasks[i].status == 'removed') {
                        stoppedTaskGids.push(tasks[i].gid);
                    } else {
                        runningTaskGids.push(tasks[i].gid);
                    }
                }

                var promises = [];

                var hasSuccess = false;
                var hasError = false;
                var results = [];

                if (runningTaskGids.length > 0) {
                    promises.push(aria2RpcService.forceRemoveMulti({
                        gids: runningTaskGids,
                        silent: !!silent,
                        callback: function (response) {
                            ariaNgCommonService.pushArrayTo(results, response.results);
                            hasSuccess = hasSuccess | response.hasSuccess;
                            hasError = hasError | response.hasError;
                        }
                    }));
                }

                if (stoppedTaskGids.length > 0) {
                    promises.push(aria2RpcService.removeDownloadResultMulti({
                        gids: stoppedTaskGids,
                        silent: !!silent,
                        callback: function (response) {
                            ariaNgCommonService.pushArrayTo(results, response.results);
                            hasSuccess = hasSuccess | response.hasSuccess;
                            hasError = hasError | response.hasError;
                        }
                    }));
                }

                return $q.all(promises).then(function () {
                    if (callback) {
                        callback({
                            hasSuccess: !!hasSuccess,
                            hasError: !!hasError,
                            results: results
                        });
                    }
                });
            },
            changeTaskPosition: function (gid, position, callback, silent) {
                return aria2RpcService.changePosition({
                    gid: gid,
                    pos: position,
                    how: 'POS_SET',
                    silent: !!silent,
                    callback: callback
                })
            },
            clearStoppedTasks: function (callback, silent) {
                return aria2RpcService.purgeDownloadResult({
                    silent: !!silent,
                    callback: callback
                });
            },
            onTaskCompleted: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onDownloadComplete({
                    callback: createEventCallback(this.getTaskStatus, callback, 'completed')
                });
            },
            onBtTaskCompleted: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onBtDownloadComplete({
                    callback: createEventCallback(this.getTaskStatus, callback, 'btcompleted')
                });
            },
            onTaskErrorOccur: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onDownloadError({
                    callback: createEventCallback(this.getTaskStatus, callback, 'error')
                });
            },
            processDownloadTasks: function (tasks) {
                if (!angular.isArray(tasks)) {
                    return;
                }

                for (var i = 0; i < tasks.length; i++) {
                    processDownloadTask(tasks[i]);
                }
            },
            getPieceStatus: function (bitField, pieceCount) {
                return getPieceStatus(bitField, pieceCount);
            },
            getCombinedPieces: function (bitField, pieceCount) {
                return getCombinedPieces(bitField, pieceCount);
            },
            estimateHealthPercentFromPeers: function (task, peers) {
                if (!task || task.numPieces < 1 || peers.length < 1) {
                    return task.completePercent;
                }

                var totalPieces = [];
                var currentPieces = getPieceStatus(task.bitfield, task.numPieces);
                var maxCompletedPieceCount = 0;
                var maxCompletedPercent = task.completePercent;

                for (var i = 0; i < currentPieces.length; i++) {
                    var count = currentPieces[i] ? 1 : 0;
                    totalPieces.push(count);
                    maxCompletedPieceCount += count;
                }

                for (var i = 0; i < peers.length; i++) {
                    var peer = peers[i];
                    var peerPieces = getPieceStatus(peer.bitfield, task.numPieces);
                    var completedPieceCount = 0;

                    for (var j = 0; j < peerPieces.length; j++) {
                        var count = (peerPieces[j] ? 1 : 0);
                        totalPieces[j] += count;
                        completedPieceCount += count;
                    }

                    if (completedPieceCount > maxCompletedPieceCount) {
                        maxCompletedPieceCount = completedPieceCount;
                        maxCompletedPercent = peer.completePercent;
                    } else if (completedPieceCount == maxCompletedPieceCount && peer.completePercent > maxCompletedPercent) {
                        maxCompletedPercent = peer.completePercent;
                    }
                }

                var totalCompletedPieceCount = 0;

                if (totalPieces.length > 0) {
                    while (true) {
                        var completed = true;

                        for (var i = 0; i < totalPieces.length; i++) {
                            if (totalPieces[i] > 0) {
                                totalCompletedPieceCount++;
                                totalPieces[i]--;
                            } else {
                                completed = false;
                            }
                        }

                        if (!completed) {
                            break;
                        }
                    }
                }

                if (totalCompletedPieceCount <= maxCompletedPieceCount) {
                    return maxCompletedPercent;
                }

                var healthPercent = totalCompletedPieceCount / task.numPieces * 100;

                if (healthPercent <= maxCompletedPercent) {
                    return maxCompletedPercent;
                }

                return healthPercent;
            }
        };
    }]);
})();
<commit_msg>fix sometimes health percentage is incorrect<commit_after>(function () {
    'use strict';

    angular.module('ariaNg').factory('aria2TaskService', ['$q', '$translate', 'aria2RpcService', 'ariaNgCommonService', function ($q, $translate, aria2RpcService, ariaNgCommonService) {
        var getFileName = function (file) {
            if (!file) {
                return '';
            }

            var path = file.path;

            if (!path && file.uris && file.uris.length > 0) {
                path = file.uris[0].uri;
            }

            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 = "";
            var success = true;

            if (task.bittorrent && task.bittorrent.info) {
                taskName = task.bittorrent.info.name;
            }

            if (!taskName && task.files && task.files.length > 0) {
                taskName = getFileName(task.files[0]);
            }

            if (!taskName) {
                taskName = $translate.instant('Unknown');
                success = false;
            }

            return {
                name: taskName,
                success: success
            };
        };

        var processDownloadTask = function (task) {
            if (!task) {
                return task;
            }

            task.totalLength = parseInt(task.totalLength);
            task.completedLength = parseInt(task.completedLength);
            task.completePercent = (task.totalLength > 0 ? task.completedLength / task.totalLength * 100 : 0);
            task.remainLength = task.totalLength - task.completedLength;
            task.remainPercent = 100 - task.completePercent;
            task.uploadLength = (task.uploadLength ? parseInt(task.uploadLength) : 0);
            task.shareRatio = (task.completedLength > 0 ? task.uploadLength / task.completedLength : 0);

            task.uploadSpeed = parseInt(task.uploadSpeed);
            task.downloadSpeed = parseInt(task.downloadSpeed);

            task.idle = task.downloadSpeed == 0;
            task.remainTime = calculateDownloadRemainTime(task.remainLength, task.downloadSpeed);
            task.seeder = (task.seeder === true || task.seeder === 'true');

            var taskNameResult = getTaskName(task);
            task.taskName = taskNameResult.name;
            task.hasTaskName = taskNameResult.success;

            if (task.files) {
                var selectedFileCount = 0;

                for (var i = 0; i < task.files.length; i++) {
                    var file = task.files[i];
                    file.fileName = getFileName(file);
                    file.length = parseInt(file.length);
                    file.selected = (file.selected === true || file.selected === 'true');
                    file.completedLength = parseInt(file.completedLength);
                    file.completePercent = (file.length > 0 ? file.completedLength / file.length * 100 : 0);

                    selectedFileCount += file.selected ? 1 : 0;
                }

                task.selectedFileCount = selectedFileCount;
            }

            return task;
        };

        var processBtPeers = function (peers, task, includeLocalPeer) {
            if (!peers) {
                return peers;
            }

            var localTaskCompletedPieces = getPieceStatus(task.bitfield, task.numPieces);
            var localTaskCompletedPieceCount = ariaNgCommonService.countArray(localTaskCompletedPieces, true);
            var localTaskCompletedPercent = task.completePercent;

            for (var i = 0; i < peers.length; i++) {
                var peer = peers[i];
                var upstreamToSpeed = peer.uploadSpeed;
                var downstreamFromSpeed = peer.downloadSpeed;
                var completedPieces = getPieceStatus(peer.bitfield, task.numPieces);
                var completedPieceCount = ariaNgCommonService.countArray(completedPieces, true);

                peer.name = peer.ip + ':' + peer.port;
                peer.completePercent = completedPieceCount / task.numPieces * 100;
                peer.downloadSpeed = upstreamToSpeed;
                peer.uploadSpeed = downstreamFromSpeed;
                peer.seeder = (peer.seeder === true || peer.seeder === 'true');

                if (completedPieceCount == localTaskCompletedPieceCount && peer.completePercent != localTaskCompletedPercent) {
                    peer.completePercent = localTaskCompletedPercent;
                }
            }

            if (includeLocalPeer) {
                peers.push(createLocalPeerFromTask(task));
            }

            return peers;
        };

        var getPieceStatus = function (bitField, pieceCount) {
            var pieces = [];

            for (var i = 0; i < pieceCount; i++) {
                pieces.push(false);
            }

            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[pieceIndex++] = isCompleted;

                    if (pieceIndex >= pieceCount) {
                        return pieces;
                    }
                }
            }

            return pieces;
        };

        var getCombinedPieces = function (bitField, pieceCount) {
            var pieces = getPieceStatus(bitField, pieceCount);
            var combinedPieces = [];

            for (var i = 0; i < pieces.length; i++) {
                var isCompleted = pieces[i];

                if (combinedPieces.length > 0 && combinedPieces[combinedPieces.length - 1].isCompleted == isCompleted) {
                    combinedPieces[combinedPieces.length - 1].count++;
                } else {
                    combinedPieces.push({
                        isCompleted: isCompleted,
                        count: 1
                    });
                }
            }

            return combinedPieces;
        };

        var createEventCallback = function (getTaskStatusFunc, callback, type) {
            return function (event) {
                var context = {
                    type: type,
                    task: null
                };

                if (event && event.gid) {
                    getTaskStatusFunc(event.gid, function (response) {
                        context.task = (response.success ? response.data : null);
                        callback(context);
                    }, true);
                } else {
                    callback(context);
                }
            }
        };

        var createLocalPeerFromTask = function (task) {
            return {
                local: true,
                bitfield: task.bitfield,
                completePercent: task.completePercent,
                downloadSpeed: task.downloadSpeed,
                name: '(local)',
                seeder: task.seeder,
                uploadSpeed: task.uploadSpeed
            };
        };

        return {
            getTaskList: function (type, full, callback, silent) {
                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({
                    requestWholeInfo: full,
                    requestParams: full ? aria2RpcService.getFullTaskParams() : aria2RpcService.getBasicTaskParams(),
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        callback(response);
                    }
                });
            },
            getTaskStatus: function (gid, callback, silent) {
                return aria2RpcService.tellStatus({
                    gid: gid,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        if (response.success) {
                            processDownloadTask(response.data);
                        }

                        callback(response);
                    }
                });
            },
            getTaskOptions: function (gid, callback, silent) {
                return aria2RpcService.getOption({
                    gid: gid,
                    silent: !!silent,
                    callback: callback
                });
            },
            setTaskOption: function (gid, key, value, callback, silent) {
                var data = {};
                data[key] = value;

                return aria2RpcService.changeOption({
                    gid: gid,
                    options: data,
                    silent: !!silent,
                    callback: callback
                });
            },
            selectTaskFile: function (gid, selectedFileIndexArr, callback, silent) {
                var selectedFileIndex = '';

                for (var i = 0; i < selectedFileIndexArr.length; i++) {
                    if (selectedFileIndex.length > 0) {
                        selectedFileIndex += ',';
                    }

                    selectedFileIndex += selectedFileIndexArr[i];
                }

                return this.setTaskOption(gid, 'select-file', selectedFileIndex, callback, silent);
            },
            getBtTaskPeers: function (task, callback, silent, includeLocalPeer) {
                return aria2RpcService.getPeers({
                    gid: task.gid,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        if (response.success) {
                            processBtPeers(response.data, task, includeLocalPeer);
                        }

                        callback(response);
                    }
                });
            },
            getTaskStatusAndBtPeers: function (gid, callback, silent, requirePeers, includeLocalPeer) {
                var methods = [
                    aria2RpcService.tellStatus({ gid: gid }, true)
                ];

                if (requirePeers) {
                    methods.push(aria2RpcService.getPeers({ gid: gid }, true));
                }

                return aria2RpcService.multicall({
                    methods: methods,
                    silent: !!silent,
                    callback: function (response) {
                        if (!callback) {
                            return;
                        }

                        response.task = {};

                        if (response.success && response.data.length > 0) {
                            response.task = response.data[0][0];
                            processDownloadTask(response.task);
                        }

                        if (response.success && response.task.bittorrent && response.data.length > 1) {
                            response.peers = response.data[1][0];
                            processBtPeers(response.peers, response.task, includeLocalPeer);
                        }

                        callback(response);
                    }
                });
            },
            newUriTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addUri({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newUriTasks: function (tasks, pauseOnAdded, callback, silent) {
                return aria2RpcService.addUriMulti({
                    tasks: tasks,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newTorrentTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addTorrent({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            newMetalinkTask: function (task, pauseOnAdded, callback, silent) {
                return aria2RpcService.addMetalink({
                    task: task,
                    pauseOnAdded: !!pauseOnAdded,
                    silent: !!silent,
                    callback: callback
                });
            },
            startTasks: function (gids, callback, silent) {
                return aria2RpcService.unpauseMulti({
                    gids: gids,
                    silent: !!silent,
                    callback: callback
                });
            },
            pauseTasks: function (gids, callback, silent) {
                return aria2RpcService.forcePauseMulti({
                    gids: gids,
                    silent: !!silent,
                    callback: callback
                });
            },
            removeTasks: function (tasks, callback, silent) {
                var runningTaskGids = [];
                var stoppedTaskGids = [];

                for (var i = 0; i < tasks.length; i++) {
                    if (tasks[i].status == 'complete' || tasks[i].status == 'error' || tasks[i].status == 'removed') {
                        stoppedTaskGids.push(tasks[i].gid);
                    } else {
                        runningTaskGids.push(tasks[i].gid);
                    }
                }

                var promises = [];

                var hasSuccess = false;
                var hasError = false;
                var results = [];

                if (runningTaskGids.length > 0) {
                    promises.push(aria2RpcService.forceRemoveMulti({
                        gids: runningTaskGids,
                        silent: !!silent,
                        callback: function (response) {
                            ariaNgCommonService.pushArrayTo(results, response.results);
                            hasSuccess = hasSuccess | response.hasSuccess;
                            hasError = hasError | response.hasError;
                        }
                    }));
                }

                if (stoppedTaskGids.length > 0) {
                    promises.push(aria2RpcService.removeDownloadResultMulti({
                        gids: stoppedTaskGids,
                        silent: !!silent,
                        callback: function (response) {
                            ariaNgCommonService.pushArrayTo(results, response.results);
                            hasSuccess = hasSuccess | response.hasSuccess;
                            hasError = hasError | response.hasError;
                        }
                    }));
                }

                return $q.all(promises).then(function () {
                    if (callback) {
                        callback({
                            hasSuccess: !!hasSuccess,
                            hasError: !!hasError,
                            results: results
                        });
                    }
                });
            },
            changeTaskPosition: function (gid, position, callback, silent) {
                return aria2RpcService.changePosition({
                    gid: gid,
                    pos: position,
                    how: 'POS_SET',
                    silent: !!silent,
                    callback: callback
                })
            },
            clearStoppedTasks: function (callback, silent) {
                return aria2RpcService.purgeDownloadResult({
                    silent: !!silent,
                    callback: callback
                });
            },
            onTaskCompleted: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onDownloadComplete({
                    callback: createEventCallback(this.getTaskStatus, callback, 'completed')
                });
            },
            onBtTaskCompleted: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onBtDownloadComplete({
                    callback: createEventCallback(this.getTaskStatus, callback, 'btcompleted')
                });
            },
            onTaskErrorOccur: function (callback) {
                if (!callback) {
                    return;
                }

                aria2RpcService.onDownloadError({
                    callback: createEventCallback(this.getTaskStatus, callback, 'error')
                });
            },
            processDownloadTasks: function (tasks) {
                if (!angular.isArray(tasks)) {
                    return;
                }

                for (var i = 0; i < tasks.length; i++) {
                    processDownloadTask(tasks[i]);
                }
            },
            getPieceStatus: function (bitField, pieceCount) {
                return getPieceStatus(bitField, pieceCount);
            },
            getCombinedPieces: function (bitField, pieceCount) {
                return getCombinedPieces(bitField, pieceCount);
            },
            estimateHealthPercentFromPeers: function (task, peers) {
                if (!task || task.numPieces < 1 || peers.length < 1) {
                    return task.completePercent;
                }

                var totalPieces = [];
                var maxCompletedPieceCount = 0;
                var maxCompletedPercent = task.completePercent;

                for (var i = 0; i < task.numPieces; i++) {
                    totalPieces.push(0);
                }

                for (var i = 0; i < peers.length; i++) {
                    var peer = peers[i];
                    var peerPieces = getPieceStatus(peer.bitfield, task.numPieces);
                    var completedPieceCount = 0;

                    for (var j = 0; j < peerPieces.length; j++) {
                        var count = (peerPieces[j] ? 1 : 0);
                        totalPieces[j] += count;
                        completedPieceCount += count;
                    }

                    if (completedPieceCount > maxCompletedPieceCount) {
                        maxCompletedPieceCount = completedPieceCount;
                        maxCompletedPercent = peer.completePercent;
                    } else if (completedPieceCount == maxCompletedPieceCount && peer.completePercent > maxCompletedPercent) {
                        maxCompletedPercent = peer.completePercent;
                    }
                }

                var totalCompletedPieceCount = 0;

                if (totalPieces.length > 0) {
                    while (true) {
                        var completed = true;

                        for (var i = 0; i < totalPieces.length; i++) {
                            if (totalPieces[i] > 0) {
                                totalCompletedPieceCount++;
                                totalPieces[i]--;
                            } else {
                                completed = false;
                            }
                        }

                        if (!completed) {
                            break;
                        }
                    }
                }

                if (totalCompletedPieceCount <= maxCompletedPieceCount) {
                    return maxCompletedPercent;
                }

                var healthPercent = totalCompletedPieceCount / task.numPieces * 100;

                if (healthPercent <= maxCompletedPercent) {
                    return maxCompletedPercent;
                }

                return healthPercent;
            }
        };
    }]);
})();

{'fraction_non_alphanumeric': 0.08990532516156727, 'fraction_numerical': 0.004984141368373357, 'mean_word_length': 1.1119057211925867, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 48, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2480563', 'n_tokens_mistral': 10289, 'n_tokens_neox': 9341, 'n_words': 1998}