electorrent
Version:
An Electron/Node/AngularJS remote client app for uTorrent server
391 lines (341 loc) • 12.7 kB
JavaScript
'use strict';
angular.module('torrentApp')
.service('utorrentService', ["$http", "$resource", "$log", "$q", "TorrentU", "notificationService", "Column", function($http, $resource, $log, $q, Torrent, $notify, Column) {
this.name = 'µTorrent';
var data = {
url: null,
username: null,
password: null,
token: null,
cid: 0,
build: -1
};
function build(array) {
var torrent = Object.create(Torrent.prototype);
torrent = (Torrent.apply(torrent, array) || torrent);
return torrent;
}
var updateCidInterceptor = {
response: function(response) {
data.cid = response.data.torrentc;
},
responseError: function(response) {
console.error('error in interceptor', data, arguments, response);
}
};
function extractTokenFromHTML(str) {
var match = str.match(/>([^<]+)</);
if (match) {
data.token = match[match.length - 1];
return true;
}
return false;
}
function url(ip, port) {
return `http://${ip}:${port}/gui/`;
}
function saveConnection(ip, port, username, password) {
data.username = username;
data.password = password;
data.url = url(ip, port);
}
this.connect = function(ip, port, user, pass) {
var loading = $q.defer();
var encoded = new Buffer(`${user}:${pass}`).toString('base64');
$http.defaults.headers.common.Authorization = 'Basic ' + encoded;
$http.get(url(ip, port) + 'token.html?t=' + Date.now(), {
timeout: 5000
}).success(function(str) {
if (extractTokenFromHTML(str)) {
saveConnection(ip, port, user, pass);
loading.resolve(data.token);
} else {
loading.reject('Could not find token');
}
}).error(function(err, status) {
$notify.alertAuth(err, status);
loading.reject('Could not connect to µTorrent');
});
return loading.promise;
}
this.addTorrentUrl = function(url, dir, path) {
return $resource(data.url + '.' + '?token=:token&action=add-url&s=:url&download_dir=:dir&path=:path&t=:t', {
token: data.token,
t: Date.now(),
url: url,
dir: dir,
path: path
}).get().$promise;
}
this.uploadTorrent = function(buffer, filename, dir, path) {
var blob = new Blob([buffer], {type : 'application/x-bittorrent'})
var formData = new FormData();
formData.append('torrent_file', blob, filename);
return $http.post(data.url , formData, {
params: {
token: data.token,
action: 'add-file',
download_dir: dir || 0,
path: path,
},
headers: { 'Content-Type': undefined },
transformRequest: function(data) {
return data;
}
});
}
this.torrents = function() {
var ret = $q.defer();
var torrents = {
labels: [],
all:[],
changed: [],
deleted: []
};
var utorrentRes = _torrents().list(
function() {
torrents.labels = (utorrentRes.label || []).map(labelTransform);
torrents.all = (utorrentRes.torrents || []).map(build);
torrents.changed = (utorrentRes.torrentp || []).map(build);
torrents.deleted = utorrentRes.torrentm;
ret.resolve(torrents);
},
function(err) {
ret.reject(err);
}
);
return ret.promise;
}
function labelTransform(label){
return label[0];
}
function _torrents() {
return $resource(data.url + '.' + '?:action:data&token=:token&cid=:cid:opt&t=:t', {
token: data.token,
cid: data.cid,
t: Date.now()
}, {
list: {
method: 'GET',
params: {
action: 'list=1'
},
transformResponse: function(response) {
return angular.fromJson(response.replace(/[\x00-\x1F]/g, ''));
},
interceptor: updateCidInterceptor,
isArray: false
}
});
}
function doAction(action, torrents) {
var hashes = torrents.map(function(torrent) {
return torrent.hash
})
return $resource(data.url + '.' + '?action=:action&token=:token&t=:t', {
action: action,
hash: hashes,
token: data.token,
cid: data.cid,
t: Date.now()
}).get().$promise;
}
this.start = function(torrents) {
return doAction('start', torrents)
}
this.stop = function(torrents) {
return doAction('stop', torrents)
}
this.pause = function(torrents) {
return doAction('pause', torrents)
}
this.remove = function(torrents) {
return doAction('remove', torrents)
}
this.removedata = function(torrents) {
return doAction('removedata', torrents)
}
this.removetorrent = function(torrents) {
return doAction('removetorrent', torrents)
}
this.removedatatorrent = function(torrents) {
return doAction('removedatatorrent', torrents)
}
this.forcestart = function(torrents) {
return doAction('forcestart', torrents)
}
this.recheck = function(torrents) {
return doAction('recheck', torrents)
}
this.queueup = function(torrents) {
return doAction('queueup', torrents)
}
this.queuedown = function(torrents) {
return doAction('queuedown', torrents)
}
this.getprops = function(torrents) {
return doAction('getprops', torrents)
}
this.getFileDownloadUrl = function(torrent, file) {
if(torrent.streamId && this.settingsMap['webui.uconnect_enable'] && file.size === file.sizeDownloaded) {
return '/proxy?sid=' + torrent.streamId + '&file=' + file.hash + '&disposition=ATTACHMENT&service=DOWNLOAD&qos=0';
}
return undefined;
}
this.setLabel = function(torrents, label) {
var hashes = torrents.map(function(torrent) {
return torrent.hash
})
var encodedQuery = '';
var i;
for (i = 0; i < hashes.length; i++) {
encodedQuery += '&' + ['hash=' + hashes[i], 's=label', 'v=' + encodeURIComponent(label)].join('&');
}
return $http.get(data.url + '?token=' + data.token + '&action=setprops' + encodedQuery, {
params: {
t: Date.now()
}
});
}
this.getSettings = function() {
return this.actions()._getsettings().$promise;
}
this.setSetting = function(setting, value) {
return this.setSettings([
[setting, value]
]);
}
this.setSettings = function(settings) { // [ [setting1,value1], [setting2,value2] ]
var encodedQuery = '';
var i, val;
for (i = 0; i < settings.length; i++) {
val = settings[i][1];
if (val === 'true') {
val = '1';
} else if (val === 'false') {
val = '0';
}
encodedQuery += '&' + ['s=' + settings[i][0], 'v=' + encodeURIComponent(val)].join('&');
}
return $http.get(data.url + '?token=' + data.token + '&action=setsetting' + encodedQuery, {
params: {
t: Date.now()
}
});
}
this.getDownloadDirectories = function() {
return $resource(data.url + '.' + '?token=:token&action=list-dirs&t=:t', {
token: data.token,
t: Date.now()
}, {
get: {
isArray: true,
transformResponse: function(response) {
var responseData = angular.fromJson(response);
return responseData['download-dirs'];
}
}
}).get().$promise;
}
this.filePriority = function() {
return $resource(data.url + '.' + '?token=:token&action=setprio&hash=:hash&t=:t&p=:priority', {
token: data.token,
t: Date.now()
}, {
set: {
method: 'GET'
}
});
}
this.getVersion = function() {
var buildVersionStr = function() {
var prefix = 'μTorrent';
var preBuild = 'build';
return [prefix, preBuild, data.build].join(' ');
};
if (data.build === -1) {
return '';
}
return buildVersionStr();
}
this.columns = [
new Column('Name', 'text', 'decodedName'),
new Column('Size', 'text', 'size', 'bytes'),
new Column('Down', 'text', 'downloadSpeed', 'speed'),
new Column('Progress', 'progress'),
new Column('Label', 'text', 'label'),
new Column('Date Added', 'text', 'dateAdded', 'date')
]
this.actionHeader = [
{
label: 'Start',
type: 'button',
color: 'green',
click: this.start,
icon: 'play'
},
{
label: 'Pause',
type: 'button',
color: 'yellow',
click: this.pause,
icon: 'pause'
},
{
label: 'Stop',
type: 'button',
color: 'red',
click: this.stop,
icon: 'stop'
},
{
label: 'Labels',
click: this.setLabel,
type: 'labels'
}
]
this.contextMenu = [
{
label: 'Recheck',
click: this.recheck,
icon: 'checkmark'
},
{
label: 'Force Start',
click: this.forcestart,
icon: 'flag'
},
{
label: 'Move Up Queue',
click: this.queueup,
icon: 'arrow up'
},
{
label: 'Move Queue Down',
click: this.queuedown,
icon: 'arrow down'
},
{
label: 'Remove',
click: this.remove,
icon: 'remove'
},
{
label: 'Remove And',
menu: [
{
label: 'Delete Torrent',
click: this.removetorrent,
},
{
label: 'Delete Data',
click: this.removedata,
},
{
label: 'Delete All',
click: this.removedatatorrent,
}
]
}
];
}]);