1924 lines
59 KiB
JavaScript
1924 lines
59 KiB
JavaScript
/**
|
|
* Copyright © Charles Kerr, Dave Perrett, Malcolm Jarvis and Bruno Bierbaumer
|
|
*
|
|
* This file is licensed under the GPLv2.
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
*/
|
|
|
|
function Transmission() {
|
|
this.initialize();
|
|
}
|
|
|
|
Transmission.prototype = {
|
|
/****
|
|
*****
|
|
***** STARTUP
|
|
*****
|
|
****/
|
|
|
|
initialize: function () {
|
|
var e;
|
|
|
|
// Initialize the helper classes
|
|
this.remote = new TransmissionRemote(this);
|
|
this.inspector = new Inspector(this, this.remote);
|
|
this.prefsDialog = new PrefsDialog(this.remote);
|
|
$(this.prefsDialog).bind('closed', $.proxy(this.onPrefsDialogClosed, this));
|
|
|
|
this.isMenuEnabled = !isMobileDevice;
|
|
|
|
// Initialize the implementation fields
|
|
this.filterText = '';
|
|
this._torrents = {};
|
|
this._rows = [];
|
|
this.dirtyTorrents = {};
|
|
this.uriCache = {};
|
|
|
|
// Initialize the clutch preferences
|
|
Prefs.getClutchPrefs(this);
|
|
|
|
// Set up user events
|
|
$('#toolbar-pause').click($.proxy(this.stopSelectedClicked, this));
|
|
$('#toolbar-start').click($.proxy(this.startSelectedClicked, this));
|
|
$('#toolbar-pause-all').click($.proxy(this.stopAllClicked, this));
|
|
$('#toolbar-start-all').click($.proxy(this.startAllClicked, this));
|
|
$('#toolbar-remove').click($.proxy(this.removeClicked, this));
|
|
$('#toolbar-open').click($.proxy(this.openTorrentClicked, this));
|
|
|
|
$('#prefs-button').click($.proxy(this.togglePrefsDialogClicked, this));
|
|
|
|
$('#upload_confirm_button').click($.proxy(this.confirmUploadClicked, this));
|
|
$('#upload_cancel_button').click($.proxy(this.hideUploadDialog, this));
|
|
|
|
$('#rename_confirm_button').click($.proxy(this.confirmRenameClicked, this));
|
|
$('#rename_cancel_button').click($.proxy(this.hideRenameDialog, this));
|
|
|
|
$('#move_confirm_button').click($.proxy(this.confirmMoveClicked, this));
|
|
$('#move_cancel_button').click($.proxy(this.hideMoveDialog, this));
|
|
|
|
$('#turtle-button').click($.proxy(this.toggleTurtleClicked, this));
|
|
$('#compact-button').click($.proxy(this.toggleCompactClicked, this));
|
|
|
|
// tell jQuery to copy the dataTransfer property from events over if it exists
|
|
jQuery.event.props.push("dataTransfer");
|
|
|
|
$('#torrent_upload_form').submit(function () {
|
|
$('#upload_confirm_button').click();
|
|
return false;
|
|
});
|
|
|
|
$('#toolbar-inspector').click($.proxy(this.toggleInspector, this));
|
|
|
|
e = $('#filter-mode');
|
|
e.val(this[Prefs._FilterMode]);
|
|
e.change($.proxy(this.onFilterModeClicked, this));
|
|
$('#filter-tracker').change($.proxy(this.onFilterTrackerClicked, this));
|
|
|
|
if (!isMobileDevice) {
|
|
$(document).bind('keydown', $.proxy(this.keyDown, this));
|
|
$(document).bind('keyup', $.proxy(this.keyUp, this));
|
|
$('#torrent_container').click($.proxy(this.deselectAll, this));
|
|
$('#torrent_container').bind('dragover', $.proxy(this.dragenter, this));
|
|
$('#torrent_container').bind('dragenter', $.proxy(this.dragenter, this));
|
|
$('#torrent_container').bind('drop', $.proxy(this.drop, this));
|
|
$('#inspector_link').click($.proxy(this.toggleInspector, this));
|
|
|
|
this.setupSearchBox();
|
|
this.createContextMenu();
|
|
};
|
|
|
|
if (this.isMenuEnabled) {
|
|
this.createSettingsMenu();
|
|
};
|
|
|
|
e = {};
|
|
e.torrent_list = $('#torrent_list')[0];
|
|
e.toolbar_buttons = $('#toolbar ul li');
|
|
e.toolbar_pause_button = $('#toolbar-pause')[0];
|
|
e.toolbar_start_button = $('#toolbar-start')[0];
|
|
e.toolbar_remove_button = $('#toolbar-remove')[0];
|
|
this.elements = e;
|
|
|
|
// Apply the prefs settings to the gui
|
|
this.initializeSettings();
|
|
|
|
// Get preferences & torrents from the daemon
|
|
var async = false;
|
|
this.loadDaemonPrefs(async);
|
|
this.loadDaemonStats(async);
|
|
this.initializeTorrents();
|
|
this.refreshTorrents();
|
|
this.togglePeriodicSessionRefresh(true);
|
|
|
|
this.updateButtonsSoon();
|
|
},
|
|
|
|
loadDaemonPrefs: function (async, callback) {
|
|
this.remote.loadDaemonPrefs(function (data) {
|
|
var o = data['arguments'];
|
|
Prefs.getClutchPrefs(o);
|
|
this.updateGuiFromSession(o);
|
|
this.sessionProperties = o;
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}, this, async);
|
|
},
|
|
|
|
loadImages: function () {
|
|
for (var i = 0, row; row = arguments[i]; ++i) {
|
|
jQuery("<img>").attr("src", row);
|
|
};
|
|
},
|
|
|
|
/*
|
|
* Load the clutch prefs and init the GUI according to those prefs
|
|
*/
|
|
initializeSettings: function () {
|
|
Prefs.getClutchPrefs(this);
|
|
|
|
if (this.isMenuEnabled) {
|
|
$('#sort_by_' + this[Prefs._SortMethod]).selectMenuItem();
|
|
|
|
if (this[Prefs._SortDirection] === Prefs._SortDescending) {
|
|
$('#reverse_sort_order').selectMenuItem();
|
|
};
|
|
}
|
|
|
|
this.initCompactMode();
|
|
},
|
|
|
|
/*
|
|
* Set up the search box
|
|
*/
|
|
setupSearchBox: function () {
|
|
var tr = this;
|
|
var search_box = $('#torrent_search');
|
|
search_box.bind('keyup click', function () {
|
|
tr.setFilterText(this.value);
|
|
});
|
|
if (!$.browser.safari) {
|
|
search_box.addClass('blur');
|
|
search_box[0].value = 'Filter';
|
|
search_box.bind('blur', function () {
|
|
if (this.value === '') {
|
|
$(this).addClass('blur');
|
|
this.value = 'Filter';
|
|
tr.setFilterText(null);
|
|
};
|
|
}).bind('focus', function () {
|
|
if ($(this).is('.blur')) {
|
|
this.value = '';
|
|
$(this).removeClass('blur');
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Create the torrent right-click menu
|
|
*/
|
|
createContextMenu: function () {
|
|
var tr = this;
|
|
var bindings = {
|
|
pause_selected: function () {
|
|
tr.stopSelectedTorrents();
|
|
},
|
|
resume_selected: function () {
|
|
tr.startSelectedTorrents(false);
|
|
},
|
|
resume_now_selected: function () {
|
|
tr.startSelectedTorrents(true);
|
|
},
|
|
move: function () {
|
|
tr.moveSelectedTorrents(false);
|
|
},
|
|
remove: function () {
|
|
tr.removeSelectedTorrents();
|
|
},
|
|
remove_data: function () {
|
|
tr.removeSelectedTorrentsAndData();
|
|
},
|
|
verify: function () {
|
|
tr.verifySelectedTorrents();
|
|
},
|
|
rename: function () {
|
|
tr.renameSelectedTorrents();
|
|
},
|
|
reannounce: function () {
|
|
tr.reannounceSelectedTorrents();
|
|
},
|
|
move_top: function () {
|
|
tr.moveTop();
|
|
},
|
|
move_up: function () {
|
|
tr.moveUp();
|
|
},
|
|
move_down: function () {
|
|
tr.moveDown();
|
|
},
|
|
move_bottom: function () {
|
|
tr.moveBottom();
|
|
},
|
|
select_all: function () {
|
|
tr.selectAll();
|
|
},
|
|
deselect_all: function () {
|
|
tr.deselectAll();
|
|
}
|
|
};
|
|
|
|
// Set up the context menu
|
|
$("ul#torrent_list").contextmenu({
|
|
delegate: ".torrent",
|
|
menu: "#torrent_context_menu",
|
|
preventSelect: true,
|
|
taphold: true,
|
|
show: {
|
|
effect: "none"
|
|
},
|
|
hide: {
|
|
effect: "none"
|
|
},
|
|
select: function (event, ui) {
|
|
bindings[ui.cmd]();
|
|
},
|
|
beforeOpen: $.proxy(function (event, ui) {
|
|
var element = $(event.currentTarget);
|
|
var i = $('#torrent_list > li').index(element);
|
|
if ((i !== -1) && !this._rows[i].isSelected()) {
|
|
this.setSelectedRow(this._rows[i]);
|
|
};
|
|
|
|
this.calculateTorrentStates(function (s) {
|
|
var tl = $(event.target);
|
|
tl.contextmenu("enableEntry", "pause_selected", s.activeSel > 0);
|
|
tl.contextmenu("enableEntry", "resume_selected", s.pausedSel > 0);
|
|
tl.contextmenu("enableEntry", "resume_now_selected", s.pausedSel > 0 || s.queuedSel > 0);
|
|
tl.contextmenu("enableEntry", "rename", s.sel == 1);
|
|
});
|
|
}, this)
|
|
});
|
|
},
|
|
|
|
createSettingsMenu: function () {
|
|
$("#footer_super_menu").transMenu({
|
|
open: function () {
|
|
$("#settings_menu").addClass("selected");
|
|
},
|
|
close: function () {
|
|
$("#settings_menu").removeClass("selected");
|
|
},
|
|
select: $.proxy(this.onMenuClicked, this)
|
|
});
|
|
$("#settings_menu").click(function (event) {
|
|
$("#footer_super_menu").transMenu("open");
|
|
});
|
|
},
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
updateFreeSpaceInAddDialog: function () {
|
|
var formdir = $('input#add-dialog-folder-input').val();
|
|
this.remote.getFreeSpace(formdir, this.onFreeSpaceResponse, this);
|
|
},
|
|
|
|
onFreeSpaceResponse: function (dir, bytes) {
|
|
var e, str, formdir;
|
|
|
|
formdir = $('input#add-dialog-folder-input').val();
|
|
if (formdir == dir) {
|
|
e = $('label#add-dialog-folder-label');
|
|
if (bytes > 0) {
|
|
str = ' <i>(' + Transmission.fmt.size(bytes) + ' Free)</i>';
|
|
} else {
|
|
str = '';
|
|
};
|
|
e.html('Destination folder' + str + ':');
|
|
}
|
|
},
|
|
|
|
/****
|
|
*****
|
|
***** UTILITIES
|
|
*****
|
|
****/
|
|
|
|
getAllTorrents: function () {
|
|
var torrents = [];
|
|
for (var key in this._torrents) {
|
|
torrents.push(this._torrents[key]);
|
|
};
|
|
return torrents;
|
|
},
|
|
|
|
getTorrentIds: function (torrents) {
|
|
return $.map(torrents.slice(0), function (t) {
|
|
return t.getId();
|
|
});
|
|
},
|
|
|
|
scrollToRow: function (row) {
|
|
if (isMobileDevice) {
|
|
// FIXME: why? return
|
|
var list = $('#torrent_container');
|
|
var scrollTop = list.scrollTop();
|
|
var innerHeight = list.innerHeight();
|
|
var offsetTop = row.getElement().offsetTop;
|
|
var offsetHeight = $(row.getElement()).outerHeight();
|
|
|
|
if (offsetTop < scrollTop) {
|
|
list.scrollTop(offsetTop);
|
|
} else if (innerHeight + scrollTop < offsetTop + offsetHeight) {
|
|
list.scrollTop(offsetTop + offsetHeight - innerHeight);
|
|
};
|
|
};
|
|
},
|
|
|
|
seedRatioLimit: function () {
|
|
var p = this.sessionProperties;
|
|
if (p && p.seedRatioLimited) {
|
|
return p.seedRatioLimit;
|
|
};
|
|
return -1;
|
|
},
|
|
|
|
setPref: function (key, val) {
|
|
this[key] = val;
|
|
Prefs.setValue(key, val);
|
|
},
|
|
|
|
/****
|
|
*****
|
|
***** SELECTION
|
|
*****
|
|
****/
|
|
|
|
getSelectedRows: function () {
|
|
return $.grep(this._rows, function (r) {
|
|
return r.isSelected();
|
|
});
|
|
},
|
|
|
|
getSelectedTorrents: function () {
|
|
return $.map(this.getSelectedRows(), function (r) {
|
|
return r.getTorrent();
|
|
});
|
|
},
|
|
|
|
getSelectedTorrentIds: function () {
|
|
return this.getTorrentIds(this.getSelectedTorrents());
|
|
},
|
|
|
|
setSelectedRow: function (row) {
|
|
$(this.elements.torrent_list).children('.selected').removeClass('selected');
|
|
this.selectRow(row);
|
|
},
|
|
|
|
selectRow: function (row) {
|
|
$(row.getElement()).addClass('selected');
|
|
this.callSelectionChangedSoon();
|
|
},
|
|
|
|
deselectRow: function (row) {
|
|
$(row.getElement()).removeClass('selected');
|
|
this.callSelectionChangedSoon();
|
|
},
|
|
|
|
selectAll: function () {
|
|
$(this.elements.torrent_list).children().addClass('selected');
|
|
this.callSelectionChangedSoon();
|
|
},
|
|
deselectAll: function () {
|
|
$(this.elements.torrent_list).children('.selected').removeClass('selected');
|
|
this.callSelectionChangedSoon();
|
|
delete this._last_torrent_clicked;
|
|
},
|
|
|
|
indexOfLastTorrent: function () {
|
|
for (var i = 0, r; r = this._rows[i]; ++i) {
|
|
if (r.getTorrentId() === this._last_torrent_clicked) {
|
|
return i;
|
|
};
|
|
};
|
|
return -1;
|
|
},
|
|
|
|
// Select a range from this row to the last clicked torrent
|
|
selectRange: function (row) {
|
|
var last = this.indexOfLastTorrent();
|
|
|
|
if (last === -1) {
|
|
this.selectRow(row);
|
|
} else { // select the range between the prevous & current
|
|
var next = this._rows.indexOf(row);
|
|
var min = Math.min(last, next);
|
|
var max = Math.max(last, next);
|
|
for (var i = min; i <= max; ++i) {
|
|
this.selectRow(this._rows[i]);
|
|
};
|
|
}
|
|
|
|
this.callSelectionChangedSoon();
|
|
},
|
|
|
|
selectionChanged: function () {
|
|
this.updateButtonStates();
|
|
|
|
this.inspector.setTorrents(this.inspectorIsVisible() ? this.getSelectedTorrents() : []);
|
|
|
|
clearTimeout(this.selectionChangedTimer);
|
|
delete this.selectionChangedTimer;
|
|
|
|
},
|
|
|
|
callSelectionChangedSoon: function () {
|
|
if (!this.selectionChangedTimer) {
|
|
var callback = $.proxy(this.selectionChanged, this),
|
|
msec = 200;
|
|
this.selectionChangedTimer = setTimeout(callback, msec);
|
|
}
|
|
},
|
|
|
|
/*--------------------------------------------
|
|
*
|
|
* E V E N T F U N C T I O N S
|
|
*
|
|
*--------------------------------------------*/
|
|
|
|
/*
|
|
* Process key event
|
|
*/
|
|
keyDown: function (ev) {
|
|
var handled = false;
|
|
var rows = this._rows;
|
|
var isInputFocused = $(ev.target).is('input');
|
|
var isDialogVisible = ($('.dialog_heading:visible').length > 0 || $('.ui-dialog:visible').length > 0);
|
|
|
|
// hotkeys
|
|
var up_key = ev.keyCode === 38; // up key pressed
|
|
var dn_key = ev.keyCode === 40; // down key pressed
|
|
var a_key = ev.keyCode === 65; // a key pressed
|
|
var c_key = ev.keyCode === 67; // c key pressed
|
|
var d_key = ev.keyCode === 68; // d key pressed
|
|
var i_key = ev.keyCode === 73; // i key pressed
|
|
var l_key = ev.keyCode === 76; // l key pressed
|
|
var m_key = ev.keyCode === 77; // m key pressed
|
|
var o_key = ev.keyCode === 79; // o key pressed
|
|
var p_key = ev.keyCode === 80; // p key pressed
|
|
var r_key = ev.keyCode === 82; // r key pressed
|
|
var t_key = ev.keyCode === 84; // t key pressed
|
|
var u_key = ev.keyCode === 85; // u key pressed
|
|
var shift_key = ev.keyCode === 16; // shift key pressed
|
|
var slash_key = ev.keyCode === 191; // slash (/) key pressed
|
|
var backspace_key = ev.keyCode === 8; // backspace key pressed
|
|
var del_key = ev.keyCode === 46; // delete key pressed
|
|
var enter_key = ev.keyCode === 13; // enter key pressed
|
|
var esc_key = ev.keyCode === 27; // esc key pressed
|
|
var comma_key = ev.keyCode === 188; // comma key pressed
|
|
|
|
if (enter_key) {
|
|
// handle other dialogs
|
|
if (dialog && dialog.isVisible()) {
|
|
dialog.executeCallback();
|
|
handled = true;
|
|
}
|
|
|
|
// handle upload dialog
|
|
if ($('#upload_container').is(':visible')) {
|
|
this.confirmUploadClicked();
|
|
handled = true;
|
|
}
|
|
|
|
// handle move dialog
|
|
if ($('#move_container').is(':visible')) {
|
|
this.confirmMoveClicked();
|
|
handled = true;
|
|
}
|
|
|
|
// handle rename dialog
|
|
if ($('#rename_container').is(':visible')) {
|
|
this.confirmRenameClicked();
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
if (esc_key) {
|
|
// handle other dialogs
|
|
if (dialog && dialog.isVisible()) {
|
|
dialog.hideDialog();
|
|
handled = true;
|
|
}
|
|
|
|
// handle upload dialog
|
|
if ($('#upload_container').is(':visible')) {
|
|
this.hideUploadDialog();
|
|
handled = true;
|
|
}
|
|
|
|
// handle move dialog
|
|
if ($('#move_container').is(':visible')) {
|
|
this.hideMoveDialog();
|
|
handled = true;
|
|
}
|
|
|
|
// handle rename dialog
|
|
if ($('#rename_container').is(':visible')) {
|
|
this.hideRenameDialog();
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
// Some hotkeys can only be used if the following conditions are met:
|
|
// 1. when no input fields are focused
|
|
// 2. when no other dialogs are visible
|
|
// 3. when the meta or ctrl key isn't pressed (i.e. opening dev tools shouldn't trigger the info panel)
|
|
if (!isInputFocused && !isDialogVisible && !ev.metaKey && !ev.ctrlKey) {
|
|
if (comma_key) {
|
|
this.togglePrefsDialogClicked();
|
|
handled = true;
|
|
}
|
|
|
|
if (slash_key) {
|
|
this.showHotkeysDialog();
|
|
handled = true;
|
|
}
|
|
|
|
if (a_key) {
|
|
if (ev.shiftKey) {
|
|
this.deselectAll();
|
|
} else {
|
|
this.selectAll();
|
|
}
|
|
handled = true;
|
|
}
|
|
|
|
if (c_key) {
|
|
this.toggleCompactClicked();
|
|
handled = true;
|
|
}
|
|
|
|
if ((backspace_key || del_key || d_key) && rows.length) {
|
|
this.removeSelectedTorrents();
|
|
handled = true;
|
|
}
|
|
|
|
if (i_key) {
|
|
this.toggleInspector();
|
|
handled = true;
|
|
}
|
|
|
|
if (m_key || l_key) {
|
|
this.moveSelectedTorrents()
|
|
handled = true;
|
|
}
|
|
|
|
if (o_key || u_key) {
|
|
this.openTorrentClicked(ev);
|
|
handled = true;
|
|
}
|
|
|
|
if (p_key) {
|
|
this.stopSelectedTorrents();
|
|
handled = true;
|
|
}
|
|
|
|
if (r_key) {
|
|
this.startSelectedTorrents();
|
|
handled = true;
|
|
}
|
|
|
|
if (t_key) {
|
|
this.toggleTurtleClicked();
|
|
handled = true;
|
|
}
|
|
|
|
if ((up_key || dn_key) && rows.length) {
|
|
var last = this.indexOfLastTorrent(),
|
|
i = last,
|
|
anchor = this._shift_index,
|
|
r,
|
|
min = 0,
|
|
max = rows.length - 1;
|
|
|
|
if (dn_key && (i + 1 <= max)) {
|
|
++i;
|
|
} else if (up_key && (i - 1 >= min)) {
|
|
--i;
|
|
};
|
|
|
|
var r = rows[i];
|
|
|
|
if (anchor >= 0) {
|
|
// user is extending the selection
|
|
// with the shift + arrow keys...
|
|
if (((anchor <= last) && (last < i)) || ((anchor >= last) && (last > i))) {
|
|
this.selectRow(r);
|
|
} else if (((anchor >= last) && (i > last)) || ((anchor <= last) && (last > i))) {
|
|
this.deselectRow(rows[last]);
|
|
}
|
|
} else {
|
|
if (ev.shiftKey) {
|
|
this.selectRange(r);
|
|
} else {
|
|
this.setSelectedRow(r);
|
|
};
|
|
}
|
|
this._last_torrent_clicked = r.getTorrentId();
|
|
this.scrollToRow(r);
|
|
handled = true;
|
|
} else if (shift_key) {
|
|
this._shift_index = this.indexOfLastTorrent();
|
|
}
|
|
}
|
|
|
|
return !handled;
|
|
},
|
|
|
|
keyUp: function (ev) {
|
|
if (ev.keyCode === 16) { // shift key pressed
|
|
delete this._shift_index;
|
|
};
|
|
},
|
|
|
|
isButtonEnabled: function (ev) {
|
|
var p = (ev.target || ev.srcElement).parentNode;
|
|
return p.className !== 'disabled' && p.parentNode.className !== 'disabled';
|
|
},
|
|
|
|
stopSelectedClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
this.stopSelectedTorrents();
|
|
this.hideMobileAddressbar();
|
|
}
|
|
},
|
|
|
|
startSelectedClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
this.startSelectedTorrents(false);
|
|
this.hideMobileAddressbar();
|
|
}
|
|
},
|
|
|
|
stopAllClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
this.stopAllTorrents();
|
|
this.hideMobileAddressbar();
|
|
}
|
|
},
|
|
|
|
startAllClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
this.startAllTorrents(false);
|
|
this.hideMobileAddressbar();
|
|
}
|
|
},
|
|
|
|
openTorrentClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
$('body').addClass('open_showing');
|
|
this.uploadTorrentFile();
|
|
this.updateButtonStates();
|
|
}
|
|
},
|
|
|
|
dragenter: function (ev) {
|
|
if (ev.dataTransfer && ev.dataTransfer.types) {
|
|
var types = ["text/uri-list", "text/plain"];
|
|
for (var i = 0; i < types.length; ++i) {
|
|
// it would be better to look at the links here;
|
|
// sadly, with Firefox, trying would throw.
|
|
if (ev.dataTransfer.types.contains(types[i])) {
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
ev.dropEffect = "copy";
|
|
return false;
|
|
}
|
|
}
|
|
} else if (ev.dataTransfer) {
|
|
ev.dataTransfer.dropEffect = "none";
|
|
}
|
|
return true;
|
|
},
|
|
|
|
drop: function (ev) {
|
|
var i, uri;
|
|
var uris = null;
|
|
var types = ["text/uri-list", "text/plain"];
|
|
var paused = this.shouldAddedTorrentsStart();
|
|
|
|
if (!ev.dataTransfer || !ev.dataTransfer.types) {
|
|
return true;
|
|
};
|
|
|
|
for (i = 0; !uris && i < types.length; ++i) {
|
|
if (ev.dataTransfer.types.contains(types[i])) {
|
|
uris = ev.dataTransfer.getData(types[i]).split("\n");
|
|
};
|
|
};
|
|
|
|
for (i = 0; uri = uris[i]; ++i) {
|
|
if (/^#/.test(uri)) { // lines which start with "#" are comments
|
|
continue;
|
|
};
|
|
if (/^[a-z-]+:/i.test(uri)) { // close enough to a url
|
|
this.remote.addTorrentByUrl(uri, paused);
|
|
};
|
|
};
|
|
|
|
ev.preventDefault();
|
|
return false;
|
|
},
|
|
|
|
hideUploadDialog: function () {
|
|
$('body.open_showing').removeClass('open_showing');
|
|
$('#upload_container').hide();
|
|
this.updateButtonStates();
|
|
},
|
|
|
|
confirmUploadClicked: function () {
|
|
this.uploadTorrentFile(true);
|
|
this.hideUploadDialog();
|
|
},
|
|
|
|
hideMoveDialog: function () {
|
|
$('#move_container').hide();
|
|
this.updateButtonStates();
|
|
},
|
|
|
|
confirmMoveClicked: function () {
|
|
this.moveSelectedTorrents(true);
|
|
this.hideUploadDialog();
|
|
},
|
|
|
|
hideRenameDialog: function () {
|
|
$('body.open_showing').removeClass('open_showing');
|
|
$('#rename_container').hide();
|
|
},
|
|
|
|
confirmRenameClicked: function () {
|
|
var torrents = this.getSelectedTorrents();
|
|
this.renameTorrent(torrents[0], $('input#torrent_rename_name').attr('value'));
|
|
this.hideRenameDialog();
|
|
},
|
|
|
|
removeClicked: function (ev) {
|
|
if (this.isButtonEnabled(ev)) {
|
|
this.removeSelectedTorrents();
|
|
this.hideMobileAddressbar();
|
|
};
|
|
},
|
|
|
|
// turn the periodic ajax session refresh on & off
|
|
togglePeriodicSessionRefresh: function (enabled) {
|
|
var that = this,
|
|
msec = 8000;
|
|
|
|
function callback() {
|
|
that.loadDaemonPrefs(undefined, rescheduleTimeout);
|
|
}
|
|
|
|
function rescheduleTimeout() {
|
|
that.sessionTimeout = setTimeout(callback, msec);
|
|
}
|
|
|
|
clearTimeout(this.sessionTimeout);
|
|
delete this.sessionTimeout;
|
|
|
|
if (enabled) {
|
|
rescheduleTimeout();
|
|
}
|
|
},
|
|
|
|
toggleTurtleClicked: function () {
|
|
var o = {};
|
|
o[RPC._TurtleState] = !$('#turtle-button').hasClass('selected');
|
|
this.remote.savePrefs(o);
|
|
},
|
|
|
|
/*--------------------------------------------
|
|
*
|
|
* I N T E R F A C E F U N C T I O N S
|
|
*
|
|
*--------------------------------------------*/
|
|
|
|
onPrefsDialogClosed: function () {
|
|
$('#prefs-button').removeClass('selected');
|
|
},
|
|
|
|
togglePrefsDialogClicked: function (ev) {
|
|
var e = $('#prefs-button');
|
|
|
|
if (e.hasClass('selected'))
|
|
this.prefsDialog.close();
|
|
else {
|
|
e.addClass('selected');
|
|
this.prefsDialog.show();
|
|
}
|
|
},
|
|
|
|
setFilterText: function (search) {
|
|
this.filterText = search ? search.trim() : null;
|
|
this.refilter(true);
|
|
},
|
|
|
|
setSortMethod: function (sort_method) {
|
|
this.setPref(Prefs._SortMethod, sort_method);
|
|
this.refilter(true);
|
|
},
|
|
|
|
setSortDirection: function (direction) {
|
|
this.setPref(Prefs._SortDirection, direction);
|
|
this.refilter(true);
|
|
},
|
|
|
|
onMenuClicked: function (event, ui) {
|
|
var o, dir;
|
|
var id = ui.id;
|
|
var remote = this.remote;
|
|
var element = ui.target;
|
|
|
|
if (ui.group == 'sort-mode') {
|
|
element.selectMenuItem();
|
|
this.setSortMethod(id.replace(/sort_by_/, ''));
|
|
} else if (element.hasClass('upload-speed')) {
|
|
o = {};
|
|
o[RPC._UpSpeedLimit] = parseInt(element.text());
|
|
o[RPC._UpSpeedLimited] = true;
|
|
remote.savePrefs(o);
|
|
} else if (element.hasClass('download-speed')) {
|
|
o = {};
|
|
o[RPC._DownSpeedLimit] = parseInt(element.text());
|
|
o[RPC._DownSpeedLimited] = true;
|
|
remote.savePrefs(o);
|
|
} else {
|
|
switch (id) {
|
|
case 'statistics':
|
|
this.showStatsDialog();
|
|
break;
|
|
|
|
case 'hotkeys':
|
|
this.showHotkeysDialog();
|
|
break;
|
|
|
|
case 'about-button':
|
|
o = 'Transmission ' + this.serverVersion;
|
|
$('#about-dialog #about-title').html(o);
|
|
$('#about-dialog').dialog({
|
|
title: 'About',
|
|
show: 'fade',
|
|
hide: 'fade'
|
|
});
|
|
break;
|
|
|
|
case 'homepage':
|
|
window.open('https://transmissionbt.com/');
|
|
break;
|
|
|
|
case 'tipjar':
|
|
window.open('https://transmissionbt.com/donate/');
|
|
break;
|
|
|
|
case 'unlimited_download_rate':
|
|
o = {};
|
|
o[RPC._DownSpeedLimited] = false;
|
|
remote.savePrefs(o);
|
|
break;
|
|
|
|
case 'limited_download_rate':
|
|
o = {};
|
|
o[RPC._DownSpeedLimited] = true;
|
|
remote.savePrefs(o);
|
|
break;
|
|
|
|
case 'unlimited_upload_rate':
|
|
o = {};
|
|
o[RPC._UpSpeedLimited] = false;
|
|
remote.savePrefs(o);
|
|
break;
|
|
|
|
case 'limited_upload_rate':
|
|
o = {};
|
|
o[RPC._UpSpeedLimited] = true;
|
|
remote.savePrefs(o);
|
|
break;
|
|
|
|
case 'reverse_sort_order':
|
|
if (element.menuItemIsSelected()) {
|
|
dir = Prefs._SortAscending;
|
|
element.deselectMenuItem();
|
|
} else {
|
|
dir = Prefs._SortDescending;
|
|
element.selectMenuItem();
|
|
}
|
|
this.setSortDirection(dir);
|
|
break;
|
|
|
|
case 'toggle_notifications':
|
|
Notifications && Notifications.toggle();
|
|
break;
|
|
|
|
default:
|
|
console.log('unhandled: ' + id);
|
|
break;
|
|
};
|
|
};
|
|
},
|
|
|
|
onTorrentChanged: function (ev, tor) {
|
|
// update our dirty fields
|
|
this.dirtyTorrents[tor.getId()] = true;
|
|
|
|
// enqueue ui refreshes
|
|
this.refilterSoon();
|
|
this.updateButtonsSoon();
|
|
},
|
|
|
|
updateFromTorrentGet: function (updates, removed_ids) {
|
|
var i, o, t, id, needed, callback, fields;
|
|
var needinfo = [];
|
|
|
|
for (i = 0; o = updates[i]; ++i) {
|
|
id = o.id;
|
|
if ((t = this._torrents[id])) {
|
|
needed = t.needsMetaData();
|
|
t.refresh(o);
|
|
if (needed && !t.needsMetaData()) {
|
|
needinfo.push(id);
|
|
};
|
|
} else {
|
|
t = this._torrents[id] = new Torrent(o);
|
|
this.dirtyTorrents[id] = true;
|
|
callback = $.proxy(this.onTorrentChanged, this);
|
|
$(t).bind('dataChanged', callback);
|
|
// do we need more info for this torrent?
|
|
if (!('name' in t.fields) || !('status' in t.fields))
|
|
needinfo.push(id);
|
|
|
|
t.notifyOnFieldChange('status', $.proxy(function (newValue, oldValue) {
|
|
if (oldValue === Torrent._StatusDownload && (newValue == Torrent._StatusSeed || newValue == Torrent._StatusSeedWait)) {
|
|
$(this).trigger('downloadComplete', [t]);
|
|
} else if (oldValue === Torrent._StatusSeed && newValue === Torrent._StatusStopped && t.isFinished()) {
|
|
$(this).trigger('seedingComplete', [t]);
|
|
} else {
|
|
$(this).trigger('statusChange', [t]);
|
|
}
|
|
}, this));
|
|
}
|
|
}
|
|
|
|
if (needinfo.length) {
|
|
// whee, new torrents! get their initial information.
|
|
fields = ['id'].concat(Torrent.Fields.Metadata,
|
|
Torrent.Fields.Stats);
|
|
this.updateTorrents(needinfo, fields);
|
|
this.refilterSoon();
|
|
}
|
|
|
|
if (removed_ids) {
|
|
this.deleteTorrents(removed_ids);
|
|
this.refilterSoon();
|
|
}
|
|
},
|
|
|
|
updateTorrents: function (ids, fields, callback) {
|
|
var that = this;
|
|
|
|
function f(updates, removedIds) {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
|
|
that.updateFromTorrentGet(updates, removedIds);
|
|
}
|
|
|
|
this.remote.updateTorrents(ids, fields, f);
|
|
},
|
|
|
|
refreshTorrents: function () {
|
|
var callback = $.proxy(this.refreshTorrents, this);
|
|
var msec = this[Prefs._RefreshRate] * 1000;
|
|
var fields = ['id'].concat(Torrent.Fields.Stats);
|
|
|
|
// send a request right now
|
|
this.updateTorrents('recently-active', fields);
|
|
|
|
// schedule the next request
|
|
clearTimeout(this.refreshTorrentsTimeout);
|
|
this.refreshTorrentsTimeout = setTimeout(callback, msec);
|
|
},
|
|
|
|
initializeTorrents: function () {
|
|
var fields = ['id'].concat(Torrent.Fields.Metadata, Torrent.Fields.Stats);
|
|
this.updateTorrents(null, fields);
|
|
},
|
|
|
|
onRowClicked: function (ev) {
|
|
var meta_key = ev.metaKey || ev.ctrlKey,
|
|
row = ev.currentTarget.row;
|
|
|
|
// handle the per-row "torrent_resume" button
|
|
if (ev.target.className === 'torrent_resume') {
|
|
this.startTorrent(row.getTorrent());
|
|
return;
|
|
}
|
|
|
|
// handle the per-row "torrent_pause" button
|
|
if (ev.target.className === 'torrent_pause') {
|
|
this.stopTorrent(row.getTorrent());
|
|
return;
|
|
}
|
|
|
|
// Prevents click carrying to parent element
|
|
// which deselects all on click
|
|
ev.stopPropagation();
|
|
|
|
if (isMobileDevice) {
|
|
if (row.isSelected())
|
|
this.setInspectorVisible(true);
|
|
this.setSelectedRow(row);
|
|
|
|
} else if (ev.shiftKey) {
|
|
this.selectRange(row);
|
|
// Need to deselect any selected text
|
|
window.focus();
|
|
|
|
// Apple-Click, not selected
|
|
} else if (!row.isSelected() && meta_key) {
|
|
this.selectRow(row);
|
|
|
|
// Regular Click, not selected
|
|
} else if (!row.isSelected()) {
|
|
this.setSelectedRow(row);
|
|
|
|
// Apple-Click, selected
|
|
} else if (row.isSelected() && meta_key) {
|
|
this.deselectRow(row);
|
|
|
|
// Regular Click, selected
|
|
} else if (row.isSelected()) {
|
|
this.setSelectedRow(row);
|
|
}
|
|
|
|
this._last_torrent_clicked = row.getTorrentId();
|
|
},
|
|
|
|
deleteTorrents: function (ids) {
|
|
var i, id;
|
|
|
|
if (ids && ids.length) {
|
|
for (i = 0; id = ids[i]; ++i) {
|
|
this.dirtyTorrents[id] = true;
|
|
delete this._torrents[id];
|
|
};
|
|
this.refilter();
|
|
};
|
|
},
|
|
|
|
shouldAddedTorrentsStart: function () {
|
|
return this.prefsDialog.shouldAddedTorrentsStart();
|
|
},
|
|
|
|
/*
|
|
* Select a torrent file to upload
|
|
*/
|
|
uploadTorrentFile: function (confirmed) {
|
|
var i, file, reader;
|
|
var fileInput = $('input#torrent_upload_file');
|
|
var folderInput = $('input#add-dialog-folder-input');
|
|
var startInput = $('input#torrent_auto_start');
|
|
var urlInput = $('input#torrent_upload_url');
|
|
|
|
if (!confirmed) {
|
|
// update the upload dialog's fields
|
|
fileInput.attr('value', '');
|
|
urlInput.attr('value', '');
|
|
startInput.attr('checked', this.shouldAddedTorrentsStart());
|
|
folderInput.attr('value', $("#download-dir").val());
|
|
folderInput.change($.proxy(this.updateFreeSpaceInAddDialog, this));
|
|
this.updateFreeSpaceInAddDialog();
|
|
|
|
// show the dialog
|
|
$('#upload_container').show();
|
|
urlInput.focus();
|
|
} else {
|
|
var paused = !startInput.is(':checked');
|
|
var destination = folderInput.val();
|
|
var remote = this.remote;
|
|
|
|
jQuery.each(fileInput[0].files, function (i, file) {
|
|
var reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
var contents = e.target.result;
|
|
var key = "base64,"
|
|
var index = contents.indexOf(key);
|
|
if (index > -1) {
|
|
var metainfo = contents.substring(index + key.length);
|
|
var o = {
|
|
method: 'torrent-add',
|
|
arguments: {
|
|
'paused': paused,
|
|
'download-dir': destination,
|
|
'metainfo': metainfo
|
|
}
|
|
};
|
|
remote.sendRequest(o, function (response) {
|
|
if (response.result != 'success')
|
|
alert('Error adding "' + file.name + '": ' + response.result);
|
|
});
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
|
|
var url = $('#torrent_upload_url').val();
|
|
if (url != '') {
|
|
if (url.match(/^[0-9a-f]{40}$/i)) {
|
|
url = 'magnet:?xt=urn:btih:' + url;
|
|
};
|
|
var o = {
|
|
'method': 'torrent-add',
|
|
arguments: {
|
|
'paused': paused,
|
|
'download-dir': destination,
|
|
'filename': url
|
|
}
|
|
};
|
|
remote.sendRequest(o, function (response) {
|
|
if (response.result != 'success') {
|
|
alert('Error adding "' + url + '": ' + response.result);
|
|
};
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
promptSetLocation: function (confirmed, torrents) {
|
|
if (!confirmed) {
|
|
var path;
|
|
if (torrents.length === 1) {
|
|
path = torrents[0].getDownloadDir();
|
|
} else {
|
|
path = $("#download-dir").val();
|
|
}
|
|
$('input#torrent_path').attr('value', path);
|
|
$('#move_container').show();
|
|
$('#torrent_path').focus();
|
|
} else {
|
|
var ids = this.getTorrentIds(torrents);
|
|
this.remote.moveTorrents(ids, $("input#torrent_path").val(), this.refreshTorrents, this);
|
|
$('#move_container').hide();
|
|
}
|
|
},
|
|
|
|
moveSelectedTorrents: function (confirmed) {
|
|
var torrents = this.getSelectedTorrents();
|
|
if (torrents.length) {
|
|
this.promptSetLocation(confirmed, torrents);
|
|
};
|
|
},
|
|
|
|
removeSelectedTorrents: function () {
|
|
var torrents = this.getSelectedTorrents();
|
|
if (torrents.length) {
|
|
this.promptToRemoveTorrents(torrents);
|
|
};
|
|
},
|
|
|
|
removeSelectedTorrentsAndData: function () {
|
|
var torrents = this.getSelectedTorrents();
|
|
if (torrents.length) {
|
|
this.promptToRemoveTorrentsAndData(torrents);
|
|
};
|
|
},
|
|
|
|
promptToRemoveTorrents: function (torrents) {
|
|
if (torrents.length === 1) {
|
|
var torrent = torrents[0];
|
|
var header = 'Remove ' + torrent.getName() + '?';
|
|
var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
|
|
|
|
dialog.confirm(header, message, 'Remove', function () {
|
|
transmission.removeTorrents(torrents);
|
|
});
|
|
} else {
|
|
var header = 'Remove ' + torrents.length + ' transfers?';
|
|
var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
|
|
|
|
dialog.confirm(header, message, 'Remove', function () {
|
|
transmission.removeTorrents(torrents);
|
|
});
|
|
}
|
|
},
|
|
|
|
promptToRemoveTorrentsAndData: function (torrents) {
|
|
if (torrents.length === 1) {
|
|
var torrent = torrents[0];
|
|
var header = 'Remove ' + torrent.getName() + ' and delete data?';
|
|
var message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
|
|
|
|
dialog.confirm(header, message, 'Remove', function () {
|
|
transmission.removeTorrentsAndData(torrents);
|
|
});
|
|
} else {
|
|
var header = 'Remove ' + torrents.length + ' transfers and delete data?';
|
|
var message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
|
|
|
|
dialog.confirm(header, message, 'Remove', function () {
|
|
transmission.removeTorrentsAndData(torrents);
|
|
});
|
|
}
|
|
},
|
|
|
|
removeTorrents: function (torrents) {
|
|
var ids = this.getTorrentIds(torrents);
|
|
this.remote.removeTorrents(ids, this.refreshTorrents, this);
|
|
},
|
|
|
|
removeTorrentsAndData: function (torrents) {
|
|
this.remote.removeTorrentsAndData(torrents);
|
|
},
|
|
|
|
promptToRenameTorrent: function (torrent) {
|
|
$('body').addClass('open_showing');
|
|
$('input#torrent_rename_name').attr('value', torrent.getName());
|
|
$('#rename_container').show();
|
|
$('#torrent_rename_name').focus();
|
|
},
|
|
|
|
renameSelectedTorrents: function () {
|
|
var torrents = this.getSelectedTorrents();
|
|
if (torrents.length != 1) {
|
|
dialog.alert("Renaming", "You can rename only one torrent at a time.", "Ok");
|
|
} else {
|
|
this.promptToRenameTorrent(torrents[0]);
|
|
};
|
|
},
|
|
|
|
onTorrentRenamed: function (response) {
|
|
var torrent;
|
|
if ((response.result === 'success') && (response.arguments) && ((torrent = this._torrents[response.arguments.id]))) {
|
|
torrent.refresh(response.arguments);
|
|
}
|
|
},
|
|
|
|
renameTorrent: function (torrent, newname) {
|
|
var oldpath = torrent.getName();
|
|
this.remote.renameTorrent([torrent.getId()], oldpath, newname, this.onTorrentRenamed, this);
|
|
},
|
|
|
|
verifySelectedTorrents: function () {
|
|
this.verifyTorrents(this.getSelectedTorrents());
|
|
},
|
|
|
|
reannounceSelectedTorrents: function () {
|
|
this.reannounceTorrents(this.getSelectedTorrents());
|
|
},
|
|
|
|
startAllTorrents: function (force) {
|
|
this.startTorrents(this.getAllTorrents(), force);
|
|
},
|
|
startSelectedTorrents: function (force) {
|
|
this.startTorrents(this.getSelectedTorrents(), force);
|
|
},
|
|
startTorrent: function (torrent) {
|
|
this.startTorrents([torrent], false);
|
|
},
|
|
|
|
startTorrents: function (torrents, force) {
|
|
this.remote.startTorrents(this.getTorrentIds(torrents), force, this.refreshTorrents, this);
|
|
},
|
|
verifyTorrent: function (torrent) {
|
|
this.verifyTorrents([torrent]);
|
|
},
|
|
verifyTorrents: function (torrents) {
|
|
this.remote.verifyTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
|
|
},
|
|
|
|
reannounceTorrent: function (torrent) {
|
|
this.reannounceTorrents([torrent]);
|
|
},
|
|
reannounceTorrents: function (torrents) {
|
|
this.remote.reannounceTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
|
|
},
|
|
|
|
stopAllTorrents: function () {
|
|
this.stopTorrents(this.getAllTorrents());
|
|
},
|
|
stopSelectedTorrents: function () {
|
|
this.stopTorrents(this.getSelectedTorrents());
|
|
},
|
|
stopTorrent: function (torrent) {
|
|
this.stopTorrents([torrent]);
|
|
},
|
|
stopTorrents: function (torrents) {
|
|
this.remote.stopTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
|
|
},
|
|
changeFileCommand: function (torrentId, rowIndices, command) {
|
|
this.remote.changeFileCommand(torrentId, rowIndices, command);
|
|
},
|
|
|
|
hideMobileAddressbar: function (delaySecs) {
|
|
if (isMobileDevice && !scroll_timeout) {
|
|
var callback = $.proxy(this.doToolbarHide, this);
|
|
var msec = delaySecs * 1000 || 150;
|
|
scroll_timeout = setTimeout(callback, msec);
|
|
};
|
|
},
|
|
doToolbarHide: function () {
|
|
window.scrollTo(0, 1);
|
|
scroll_timeout = null;
|
|
},
|
|
|
|
// Queue
|
|
moveTop: function () {
|
|
this.remote.moveTorrentsToTop(this.getSelectedTorrentIds(), this.refreshTorrents, this);
|
|
},
|
|
moveUp: function () {
|
|
this.remote.moveTorrentsUp(this.getSelectedTorrentIds(), this.refreshTorrents, this);
|
|
},
|
|
moveDown: function () {
|
|
this.remote.moveTorrentsDown(this.getSelectedTorrentIds(), this.refreshTorrents, this);
|
|
},
|
|
moveBottom: function () {
|
|
this.remote.moveTorrentsToBottom(this.getSelectedTorrentIds(), this.refreshTorrents, this);
|
|
},
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
updateGuiFromSession: function (o) {
|
|
var limit, limited, e, b, text;
|
|
var fmt = Transmission.fmt;
|
|
var menu = $('#footer_super_menu');
|
|
|
|
this.serverVersion = o.version;
|
|
|
|
this.prefsDialog.set(o);
|
|
|
|
if (RPC._TurtleState in o) {
|
|
b = o[RPC._TurtleState];
|
|
e = $('#turtle-button');
|
|
text = ['Click to ', (b ? 'disable' : 'enable'), ' Temporary Speed Limits (', fmt.speed(o[RPC._TurtleUpSpeedLimit]), ' up,', fmt.speed(o[RPC._TurtleDownSpeedLimit]), ' down)'].join('');
|
|
e.toggleClass('selected', b);
|
|
e.attr('title', text);
|
|
}
|
|
|
|
if (this.isMenuEnabled && (RPC._DownSpeedLimited in o) && (RPC._DownSpeedLimit in o)) {
|
|
limit = o[RPC._DownSpeedLimit];
|
|
limited = o[RPC._DownSpeedLimited];
|
|
|
|
e = menu.find('#limited_download_rate');
|
|
e.html('Limit (' + fmt.speed(limit) + ')');
|
|
|
|
if (!limited) {
|
|
e = menu.find('#unlimited_download_rate');
|
|
};
|
|
e.selectMenuItem();
|
|
}
|
|
|
|
if (this.isMenuEnabled && (RPC._UpSpeedLimited in o) && (RPC._UpSpeedLimit in o)) {
|
|
limit = o[RPC._UpSpeedLimit];
|
|
limited = o[RPC._UpSpeedLimited];
|
|
|
|
e = menu.find('#limited_upload_rate');
|
|
e.html('Limit (' + fmt.speed(limit) + ')');
|
|
|
|
if (!limited) {
|
|
e = menu.find('#unlimited_upload_rate');
|
|
};
|
|
e.selectMenuItem();
|
|
}
|
|
},
|
|
|
|
updateStatusbar: function () {
|
|
var i, row;
|
|
var u = 0;
|
|
var d = 0;
|
|
var fmt = Transmission.fmt;
|
|
var torrents = this.getAllTorrents();
|
|
|
|
// up/down speed
|
|
for (i = 0; row = torrents[i]; ++i) {
|
|
u += row.getUploadSpeed();
|
|
d += row.getDownloadSpeed();
|
|
}
|
|
|
|
$('#speed-up-container').toggleClass('active', u > 0);
|
|
$('#speed-up-label').text(fmt.speedBps(u));
|
|
|
|
$('#speed-dn-container').toggleClass('active', d > 0);
|
|
$('#speed-dn-label').text(fmt.speedBps(d));
|
|
|
|
// visible torrents
|
|
$('#filter-count').text(fmt.countString('Transfer', 'Transfers', this._rows.length));
|
|
},
|
|
|
|
setEnabled: function (key, flag) {
|
|
$(key).toggleClass('disabled', !flag);
|
|
},
|
|
|
|
updateFilterSelect: function () {
|
|
var i, names, name, str, o;
|
|
var e = $('#filter-tracker');
|
|
var trackers = this.getTrackers();
|
|
|
|
// build a sorted list of names
|
|
names = [];
|
|
for (name in trackers) {
|
|
names.push(name);
|
|
};
|
|
names.sort();
|
|
|
|
// build the new html
|
|
if (!this.filterTracker) {
|
|
str = '<option value="all" selected="selected">All</option>';
|
|
} else {
|
|
str = '<option value="all">All</option>';
|
|
};
|
|
for (i = 0; name = names[i]; ++i) {
|
|
o = trackers[name];
|
|
str += '<option value="' + o.domain + '"';
|
|
if (trackers[name].domain === this.filterTracker) {
|
|
str += ' selected="selected"';
|
|
};
|
|
str += '>' + name + '</option>';
|
|
}
|
|
|
|
if (!this.filterTrackersStr || (this.filterTrackersStr !== str)) {
|
|
this.filterTrackersStr = str;
|
|
$('#filter-tracker').html(str);
|
|
}
|
|
},
|
|
|
|
updateButtonsSoon: function () {
|
|
if (!this.buttonRefreshTimer) {
|
|
var callback = $.proxy(this.updateButtonStates, this);
|
|
var msec = 100;
|
|
|
|
this.buttonRefreshTimer = setTimeout(callback, msec);
|
|
}
|
|
},
|
|
|
|
calculateTorrentStates: function (callback) {
|
|
var stats = {
|
|
total: 0,
|
|
active: 0,
|
|
paused: 0,
|
|
sel: 0,
|
|
activeSel: 0,
|
|
pausedSel: 0,
|
|
queuedSel: 0
|
|
};
|
|
|
|
clearTimeout(this.buttonRefreshTimer);
|
|
delete this.buttonRefreshTimer;
|
|
|
|
for (var i = 0, row; row = this._rows[i]; ++i) {
|
|
var isStopped = row.getTorrent().isStopped();
|
|
var isSelected = row.isSelected();
|
|
var isQueued = row.getTorrent().isQueued();
|
|
++stats.total;
|
|
if (!isStopped) {
|
|
++stats.active;
|
|
};
|
|
if (isStopped) {
|
|
++stats.paused;
|
|
};
|
|
if (isSelected) {
|
|
++stats.sel;
|
|
};
|
|
if (isSelected && !isStopped) {
|
|
++stats.activeSel;
|
|
};
|
|
if (isSelected && isStopped) {
|
|
++stats.pausedSel;
|
|
};
|
|
if (isSelected && isQueued) {
|
|
++stats.queuedSel;
|
|
};
|
|
};
|
|
|
|
callback(stats);
|
|
},
|
|
|
|
updateButtonStates: function () {
|
|
var tr = this;
|
|
var e = this.elements;
|
|
|
|
this.calculateTorrentStates(function (s) {
|
|
tr.setEnabled(e.toolbar_pause_button, s.activeSel > 0);
|
|
tr.setEnabled(e.toolbar_start_button, s.pausedSel > 0);
|
|
tr.setEnabled(e.toolbar_remove_button, s.sel > 0);
|
|
});
|
|
},
|
|
|
|
/****
|
|
*****
|
|
***** INSPECTOR
|
|
*****
|
|
****/
|
|
|
|
inspectorIsVisible: function () {
|
|
return $('#torrent_inspector').is(':visible');
|
|
},
|
|
toggleInspector: function () {
|
|
this.setInspectorVisible(!this.inspectorIsVisible());
|
|
},
|
|
setInspectorVisible: function (visible) {
|
|
if (visible) {
|
|
this.inspector.setTorrents(this.getSelectedTorrents());
|
|
};
|
|
|
|
// update the ui widgetry
|
|
$('#torrent_inspector').toggle(visible);
|
|
$('#toolbar-inspector').toggleClass('selected', visible);
|
|
this.hideMobileAddressbar();
|
|
if (isMobileDevice) {
|
|
$('body').toggleClass('inspector_showing', visible);
|
|
} else {
|
|
var w = visible ? $('#torrent_inspector').outerWidth() + 1 + 'px' : '0px';
|
|
$('#torrent_container')[0].style.right = w;
|
|
}
|
|
},
|
|
|
|
/****
|
|
*****
|
|
***** FILTER
|
|
*****
|
|
****/
|
|
|
|
refilterSoon: function () {
|
|
if (!this.refilterTimer) {
|
|
var tr = this,
|
|
callback = function () {
|
|
tr.refilter(false);
|
|
},
|
|
msec = 100;
|
|
this.refilterTimer = setTimeout(callback, msec);
|
|
}
|
|
},
|
|
|
|
sortRows: function (rows) {
|
|
var i, tor, row,
|
|
id2row = {},
|
|
torrents = [];
|
|
|
|
for (i = 0; row = rows[i]; ++i) {
|
|
tor = row.getTorrent();
|
|
torrents.push(tor);
|
|
id2row[tor.getId()] = row;
|
|
}
|
|
|
|
Torrent.sortTorrents(torrents, this[Prefs._SortMethod],
|
|
this[Prefs._SortDirection]);
|
|
|
|
for (i = 0; tor = torrents[i]; ++i) {
|
|
rows[i] = id2row[tor.getId()];
|
|
};
|
|
},
|
|
|
|
refilter: function (rebuildEverything) {
|
|
var i, e, id, t, row, tmp, rows, clean_rows, dirty_rows, frag;
|
|
var sort_mode = this[Prefs._SortMethod];
|
|
var sort_direction = this[Prefs._SortDirection];
|
|
var filter_mode = this[Prefs._FilterMode];
|
|
var filter_text = this.filterText;
|
|
var filter_tracker = this.filterTracker;
|
|
var renderer = this.torrentRenderer;
|
|
var list = this.elements.torrent_list;
|
|
|
|
old_sel_count = $(list).children('.selected').length;
|
|
|
|
this.updateFilterSelect();
|
|
|
|
clearTimeout(this.refilterTimer);
|
|
delete this.refilterTimer;
|
|
|
|
if (rebuildEverything) {
|
|
$(list).empty();
|
|
this._rows = [];
|
|
for (id in this._torrents) {
|
|
this.dirtyTorrents[id] = true;
|
|
};
|
|
}
|
|
|
|
// rows that overlap with dirtyTorrents need to be refiltered.
|
|
// those that don't are 'clean' and don't need refiltering.
|
|
clean_rows = [];
|
|
dirty_rows = [];
|
|
for (i = 0; row = this._rows[i]; ++i) {
|
|
if (row.getTorrentId() in this.dirtyTorrents) {
|
|
dirty_rows.push(row);
|
|
} else {
|
|
clean_rows.push(row);
|
|
};
|
|
}
|
|
|
|
// remove the dirty rows from the dom
|
|
e = [];
|
|
for (i = 0; row = dirty_rows[i]; ++i) {
|
|
e.push(row.getElement());
|
|
};
|
|
$(e).detach();
|
|
|
|
// drop any dirty rows that don't pass the filter test
|
|
tmp = [];
|
|
for (i = 0; row = dirty_rows[i]; ++i) {
|
|
id = row.getTorrentId();
|
|
t = this._torrents[id];
|
|
if (t && t.test(filter_mode, filter_text, filter_tracker)) {
|
|
tmp.push(row);
|
|
};
|
|
delete this.dirtyTorrents[id];
|
|
}
|
|
dirty_rows = tmp;
|
|
|
|
// make new rows for dirty torrents that pass the filter test
|
|
// but don't already have a row
|
|
for (id in this.dirtyTorrents) {
|
|
t = this._torrents[id];
|
|
if (t && t.test(filter_mode, filter_text, filter_tracker)) {
|
|
row = new TorrentRow(renderer, this, t);
|
|
e = row.getElement();
|
|
e.row = row;
|
|
dirty_rows.push(row);
|
|
$(e).click($.proxy(this.onRowClicked, this));
|
|
$(e).dblclick($.proxy(this.toggleInspector, this));
|
|
}
|
|
}
|
|
|
|
// sort the dirty rows
|
|
this.sortRows(dirty_rows);
|
|
|
|
// now we have two sorted arrays of rows
|
|
// and can do a simple two-way sorted merge.
|
|
rows = [];
|
|
var ci = 0,
|
|
cmax = clean_rows.length;
|
|
var di = 0,
|
|
dmax = dirty_rows.length;
|
|
frag = document.createDocumentFragment();
|
|
while (ci != cmax || di != dmax) {
|
|
var push_clean;
|
|
|
|
if (ci == cmax) {
|
|
push_clean = false;
|
|
} else if (di == dmax) {
|
|
push_clean = true;
|
|
} else {
|
|
var c = Torrent.compareTorrents(clean_rows[ci].getTorrent(), dirty_rows[di].getTorrent(), sort_mode, sort_direction);
|
|
push_clean = (c < 0);
|
|
}
|
|
|
|
if (push_clean) {
|
|
rows.push(clean_rows[ci++]);
|
|
} else {
|
|
row = dirty_rows[di++];
|
|
e = row.getElement();
|
|
|
|
if (ci !== cmax) {
|
|
list.insertBefore(e, clean_rows[ci].getElement());
|
|
} else {
|
|
frag.appendChild(e);
|
|
};
|
|
|
|
rows.push(row);
|
|
}
|
|
}
|
|
list.appendChild(frag);
|
|
|
|
// update our implementation fields
|
|
this._rows = rows;
|
|
this.dirtyTorrents = {};
|
|
|
|
// jquery's even/odd starts with 1 not 0, so invert its logic
|
|
e = []
|
|
for (i = 0; row = rows[i]; ++i) {
|
|
e.push(row.getElement());
|
|
};
|
|
$(e).filter(":odd").addClass('even');
|
|
$(e).filter(":even").removeClass('even');
|
|
|
|
// sync gui
|
|
this.updateStatusbar();
|
|
if (old_sel_count !== $(list).children('.selected').length) {
|
|
this.selectionChanged();
|
|
};
|
|
},
|
|
|
|
setFilterMode: function (mode) {
|
|
// set the state
|
|
this.setPref(Prefs._FilterMode, mode);
|
|
|
|
// refilter
|
|
this.refilter(true);
|
|
},
|
|
|
|
onFilterModeClicked: function (ev) {
|
|
this.setFilterMode($('#filter-mode').val());
|
|
},
|
|
|
|
onFilterTrackerClicked: function (ev) {
|
|
var tracker = $('#filter-tracker').val();
|
|
this.setFilterTracker(tracker === 'all' ? null : tracker);
|
|
},
|
|
|
|
setFilterTracker: function (domain) {
|
|
// update which tracker is selected in the popup
|
|
var key = domain ? this.getReadableDomain(domain) : 'all';
|
|
var id = '#show-tracker-' + key;
|
|
|
|
$(id).addClass('selected').siblings().removeClass('selected');
|
|
|
|
this.filterTracker = domain;
|
|
this.refilter(true);
|
|
},
|
|
|
|
// example: "tracker.ubuntu.com" returns "ubuntu.com"
|
|
getDomainName: function (host) {
|
|
var dot = host.indexOf('.');
|
|
if (dot !== host.lastIndexOf('.')) {
|
|
host = host.slice(dot + 1);
|
|
};
|
|
|
|
return host;
|
|
},
|
|
|
|
// example: "ubuntu.com" returns "Ubuntu"
|
|
getReadableDomain: function (name) {
|
|
if (name.length) {
|
|
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
};
|
|
var dot = name.indexOf('.');
|
|
if (dot !== -1) {
|
|
name = name.slice(0, dot);
|
|
};
|
|
return name;
|
|
},
|
|
|
|
getTrackers: function () {
|
|
var ret = {};
|
|
|
|
var torrents = this.getAllTorrents();
|
|
for (var i = 0, torrent; torrent = torrents[i]; ++i) {
|
|
var names = [];
|
|
var trackers = torrent.getTrackers();
|
|
|
|
for (var j = 0, tracker; tracker = trackers[j]; ++j) {
|
|
var uri, announce = tracker.announce;
|
|
|
|
if (announce in this.uriCache) {
|
|
uri = this.uriCache[announce];
|
|
} else {
|
|
uri = this.uriCache[announce] = parseUri(announce);
|
|
uri.domain = this.getDomainName(uri.host);
|
|
uri.name = this.getReadableDomain(uri.domain);
|
|
};
|
|
|
|
if (!(uri.name in ret)) {
|
|
ret[uri.name] = {
|
|
'uri': uri,
|
|
'domain': uri.domain,
|
|
'count': 0
|
|
};
|
|
};
|
|
|
|
if (names.indexOf(uri.name) === -1) {
|
|
names.push(uri.name);
|
|
};
|
|
}
|
|
|
|
for (var j = 0, name; name = names[j]; ++j) {
|
|
ret[name].count++;
|
|
};
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
/***
|
|
****
|
|
**** Compact Mode
|
|
****
|
|
***/
|
|
|
|
toggleCompactClicked: function () {
|
|
this.setCompactMode(!this[Prefs._CompactDisplayState]);
|
|
},
|
|
setCompactMode: function (is_compact) {
|
|
var key = Prefs._CompactDisplayState;
|
|
var was_compact = this[key];
|
|
|
|
if (was_compact !== is_compact) {
|
|
this.setPref(key, is_compact);
|
|
this.onCompactModeChanged();
|
|
};
|
|
},
|
|
initCompactMode: function () {
|
|
this.onCompactModeChanged();
|
|
},
|
|
onCompactModeChanged: function () {
|
|
var compact = this[Prefs._CompactDisplayState];
|
|
|
|
// update the ui: footer button
|
|
$("#compact-button").toggleClass('selected', compact);
|
|
|
|
// update the ui: torrent list
|
|
this.torrentRenderer = compact ? new TorrentRendererCompact() : new TorrentRendererFull();
|
|
this.refilter(true);
|
|
},
|
|
|
|
/***
|
|
****
|
|
**** Statistics
|
|
****
|
|
***/
|
|
|
|
// turn the periodic ajax stats refresh on & off
|
|
togglePeriodicStatsRefresh: function (enabled) {
|
|
var that = this,
|
|
msec = 5000;
|
|
|
|
function callback() {
|
|
that.loadDaemonStats(undefined, rescheduleTimeout);
|
|
}
|
|
|
|
function rescheduleTimeout() {
|
|
that.statsTimeout = setTimeout(callback, msec);
|
|
}
|
|
|
|
clearTimeout(this.statsTimeout);
|
|
delete this.statsTimeout;
|
|
|
|
if (enabled) {
|
|
rescheduleTimeout();
|
|
}
|
|
},
|
|
|
|
loadDaemonStats: function (async, callback) {
|
|
this.remote.loadDaemonStats(function (data) {
|
|
this.updateStats(data['arguments']);
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}, this, async);
|
|
},
|
|
|
|
// Process new session stats from the server
|
|
updateStats: function (stats) {
|
|
var s, ratio;
|
|
var fmt = Transmission.fmt;
|
|
|
|
s = stats["current-stats"];
|
|
ratio = Math.ratio(s.uploadedBytes, s.downloadedBytes);
|
|
$('#stats-session-uploaded').html(fmt.size(s.uploadedBytes));
|
|
$('#stats-session-downloaded').html(fmt.size(s.downloadedBytes));
|
|
$('#stats-session-ratio').html(fmt.ratioString(ratio));
|
|
$('#stats-session-duration').html(fmt.timeInterval(s.secondsActive));
|
|
|
|
s = stats["cumulative-stats"];
|
|
ratio = Math.ratio(s.uploadedBytes, s.downloadedBytes);
|
|
$('#stats-total-count').html(s.sessionCount + " times");
|
|
$('#stats-total-uploaded').html(fmt.size(s.uploadedBytes));
|
|
$('#stats-total-downloaded').html(fmt.size(s.downloadedBytes));
|
|
$('#stats-total-ratio').html(fmt.ratioString(ratio));
|
|
$('#stats-total-duration').html(fmt.timeInterval(s.secondsActive));
|
|
},
|
|
|
|
showStatsDialog: function () {
|
|
this.loadDaemonStats();
|
|
this.hideMobileAddressbar();
|
|
this.togglePeriodicStatsRefresh(true);
|
|
$('#stats-dialog').dialog({
|
|
close: $.proxy(this.onStatsDialogClosed, this),
|
|
show: 'fade',
|
|
hide: 'fade',
|
|
title: 'Statistics'
|
|
});
|
|
},
|
|
|
|
onStatsDialogClosed: function () {
|
|
this.hideMobileAddressbar();
|
|
this.togglePeriodicStatsRefresh(false);
|
|
},
|
|
|
|
/***
|
|
****
|
|
**** Hotkeys
|
|
****
|
|
***/
|
|
showHotkeysDialog: function () {
|
|
$('#hotkeys-dialog').dialog({
|
|
title: 'Hotkeys',
|
|
show: 'fade',
|
|
hide: 'fade'
|
|
});
|
|
}
|
|
};
|