      1 /*
      2 Script: Deluge.Add.js
      3     Contains the Add Torrent window.
      4
      5 Copyright:
      6 	(C) Damien Churchill 2009 <damoxc@gmail.com>
      7 	This program is free software; you can redistribute it and/or modify
      8 	it under the terms of the GNU General Public License as published by
      9 	the Free Software Foundation; either version 3, or (at your option)
     10 	any later version.
     11
     12 	This program is distributed in the hope that it will be useful,
     13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 	GNU General Public License for more details.
     16
     17 	You should have received a copy of the GNU General Public License
     18 	along with this program.  If not, write to:
     19 		The Free Software Foundation, Inc.,
     20 		51 Franklin Street, Fifth Floor
     21 		Boston, MA  02110-1301, USA.
     22
     23     In addition, as a special exception, the copyright holders give
     24     permission to link the code of portions of this program with the OpenSSL
     25     library.
     26     You must obey the GNU General Public License in all respects for all of
     27     the code used other than OpenSSL. If you modify file(s) with this
     28     exception, you may extend this exception to your version of the file(s),
     29     but you are not obligated to do so. If you do not wish to do so, delete
     30     this exception statement from your version. If you delete this exception
     31     statement from all source files in the program, then also delete it here.
     32
     33 */
     34
     35 Ext.namespace('Ext.deluge.add');
     36 Ext.deluge.add.OptionsPanel = Ext.extend(Ext.TabPanel, {
     37
     38 	torrents: {},
     39
     40 	constructor: function(config) {
     41 		config = Ext.apply({
     42 			region: 'south',
     43 			margins: '5 5 5 5',
     44 			activeTab: 0,
     45 			height: 220
     46 		}, config);
     47 		Ext.deluge.add.OptionsPanel.superclass.constructor.call(this, config);
     48 	},
     49
     50 	initComponent: function() {
     51 		Ext.deluge.add.OptionsPanel.superclass.initComponent.call(this);
     52 		this.files = this.add(new Ext.tree.ColumnTree({
     53 			layout: 'fit',
     54 			title: _('Files'),
     55 			rootVisible: false,
     56 			autoScroll: true,
     57 			height: 170,
     58 			border: false,
     59 			animate: false,
     60
     61 			columns: [{
     62 				header: _('Filename'),
     63 				width: 275,
     64 				dataIndex: 'filename'
     65 			},{
     66 				header: _('Size'),
     67 				width: 80,
     68 				dataIndex: 'size'
     69 			}],
     70
     71 			root: new Ext.tree.AsyncTreeNode({
     72 				text: 'Files'
     73 			})
     74 		}));
     75 		new Ext.tree.TreeSorter(this.files, {
     76 			folderSort: true
     77 		});
     78
     79 		this.optionsManager = new Deluge.OptionsManager({
     80 			defaults: {
     81 				'add_paused': false,
     82 				'compact_allocation': false,
     83 				'download_location': '',
     84 				'max_connections_per_torrent': -1,
     85 				'max_download_speed_per_torrent': -1,
     86 				'max_upload_slots_per_torrent': -1,
     87 				'max_upload_speed_per_torrent': -1,
     88 				'prioritize_first_last_pieces': false,
     89 				'file_priorities': []
     90 			}
     91 		});
     92
     93 		this.form = this.add({
     94 			xtype: 'form',
     95 			labelWidth: 1,
     96 			title: _('Options'),
     97 			bodyStyle: 'padding: 5px;',
     98 			border: false,
     99 			height: 170
    100 		});
    101
    102 		var fieldset = this.form.add({
    103 			xtype: 'fieldset',
    104 			title: _('Download Location'),
    105 			border: false,
    106 			autoHeight: true,
    107 			defaultType: 'textfield',
    108 			labelWidth: 1,
    109 			fieldLabel: ''
    110 		});
    111 		this.optionsManager.bind('download_location', fieldset.add({
    112 			fieldLabel: '',
    113 			name: 'download_location',
    114 			width: 400,
    115 			labelSeparator: ''
    116 		}));
    117
    118 		var panel = this.form.add({
    119 			border: false,
    120 			layout: 'column',
    121 			defaultType: 'fieldset'
    122 		});
    123 		fieldset = panel.add({
    124 			title: _('Allocation'),
    125 			border: false,
    126 			autoHeight: true,
    127 			defaultType: 'radio',
    128 			width: 100
    129 		});
    130
    131 		this.optionsManager.bind('compact_allocation', fieldset.add({
    132 			xtype: 'radiogroup',
    133 			columns: 1,
    134 			vertical: true,
    135 			labelSeparator: '',
    136 			items: [{
    137 				name: 'compact_allocation',
    138 				value: false,
    139 				inputValue: false,
    140 				boxLabel: _('Full'),
    141 				fieldLabel: '',
    142 				labelSeparator: ''
    143 			}, {
    144 				name: 'compact_allocation',
    145 				value: true,
    146 				inputValue: true,
    147 				boxLabel: _('Compact'),
    148 				fieldLabel: '',
    149 				labelSeparator: '',
    150 			}]
    151 		}));
    152
    153 		fieldset = panel.add({
    154 			title: _('Bandwidth'),
    155 			border: false,
    156 			autoHeight: true,
    157 			labelWidth: 100,
    158 			width: 200,
    159 			defaultType: 'uxspinner'
    160 		});
    161 		this.optionsManager.bind('max_download_speed_per_torrent', fieldset.add({
    162 			fieldLabel: _('Max Down Speed'),
    163 			/*labelStyle: 'margin-left: 10px',*/
    164 			name: 'max_download_speed_per_torrent',
    165 			width: 60
    166 		}));
    167 		this.optionsManager.bind('max_upload_speed_per_torrent', fieldset.add({
    168 			fieldLabel: _('Max Up Speed'),
    169 			/*labelStyle: 'margin-left: 10px',*/
    170 			name: 'max_upload_speed_per_torrent',
    171 			width: 60
    172 		}));
    173 		this.optionsManager.bind('max_connections_per_torrent', fieldset.add({
    174 			fieldLabel: _('Max Connections'),
    175 			/*labelStyle: 'margin-left: 10px',*/
    176 			name: 'max_connections_per_torrent',
    177 			width: 60
    178 		}));
    179 		this.optionsManager.bind('max_upload_slots_per_torrent', fieldset.add({
    180 			fieldLabel: _('Max Upload Slots'),
    181 			/*labelStyle: 'margin-left: 10px',*/
    182 			name: 'max_upload_slots_per_torrent',
    183 			width: 60
    184 		}));
    185
    186 		fieldset = panel.add({
    187 			title: _('General'),
    188 			border: false,
    189 			autoHeight: true,
    190 			defaultType: 'checkbox'
    191 		});
    192 		this.optionsManager.bind('add_paused', fieldset.add({
    193 			name: 'add_paused',
    194 			boxLabel: _('Add In Paused State'),
    195 			fieldLabel: '',
    196 			labelSeparator: '',
    197 		}));
    198 		this.optionsManager.bind('prioritize_first_last_pieces', fieldset.add({
    199 			name: 'prioritize_first_last_pieces',
    200 			boxLabel: _('Prioritize First/Last Pieces'),
    201 			fieldLabel: '',
    202 			labelSeparator: '',
    203 		}));
    204
    205 		this.form.on('render', this.onFormRender, this);
    206 		this.form.disable();
    207 	},
    208
    209 	onFormRender: function(form) {
    210 		form.layout = new Ext.layout.FormLayout();
    211 		form.layout.setContainer(form);
    212 		form.doLayout();
    213
    214 		this.optionsManager.changeId(null);
    215 	},
    216
    217 	addTorrent: function(torrent) {
    218 		this.torrents[torrent['info_hash']] = torrent;
    219 		var fileIndexes = {};
    220 		this.walkFileTree(torrent['files_tree'], function(filename, type, entry, parent) {
    221 			if (type != 'file') return;
    222 			fileIndexes[entry[0]] = entry[2];
    223 		}, this);
    224
    225 		var priorities = [];
    226 		Ext.each(Ext.keys(fileIndexes), function(index) {
    227 			priorities[index] = fileIndexes[index];
    228 		});
    229 		this.optionsManager.set(torrent['info_hash'], 'file_priorities', priorities);
    230 	},
    231
    232 	clear: function() {
    233 		this.clearFiles();
    234 	},
    235
    236 	clearFiles: function() {
    237 		var root = this.files.getRootNode();
    238 		if (!root.hasChildNodes()) return;
    239 		root.cascade(function(node) {
    240 			if (!node.parentNode || !node.getOwnerTree()) return;
    241 			node.remove();
    242 		});
    243 	},
    244
    245 	getDefaults: function() {
    246 		var keys = ['add_paused','compact_allocation','download_location',
    247 		'max_connections_per_torrent','max_download_speed_per_torrent',
    248 		'max_upload_slots_per_torrent','max_upload_speed_per_torrent',
    249 		'prioritize_first_last_pieces'];
    250
    251 		Deluge.Client.core.get_config_values(keys, {
    252 			success: function(config) {
    253 				config['file_priorities'] = [];
    254 				this.optionsManager.defaults = config;
    255 			},
    256 			scope: this
    257 		});
    258 	},
    259
    260 	getFilename: function(torrentId) {
    261 		return this.torrents[torrentId]['filename'];
    262 	},
    263
    264 	getOptions: function(torrentId) {
    265 		var options = this.optionsManager.get(torrentId);
    266 		Ext.each(options['file_priorities'], function(priority, index) {
    267 			options['file_priorities'][index] = (priority) ? 1 : 0;
    268 		});
    269 		return options;
    270 	},
    271
    272 	setTorrent: function(torrentId) {
    273 		this.torrentId = torrentId;
    274 		this.optionsManager.changeId(torrentId);
    275
    276 		this.clearFiles();
    277 		var root = this.files.getRootNode();
    278 		var priorities = this.optionsManager.get(this.torrentId, 'file_priorities');
    279
    280 		this.walkFileTree(this.torrents[torrentId]['files_tree'], function(filename, type, entry, parent) {
    281 			if (type == 'dir') {
    282 				var folder = new Ext.tree.TreeNode({
    283 					text: filename,
    284 					checked: true
    285 				});
    286 				folder.on('checkchange', this.onFolderCheck, this);
    287 				parent.appendChild(folder);
    288 				return folder;
    289 			} else {
    290 				var node = new Ext.tree.TreeNode({
    291 					filename: filename,
    292 					fileindex: entry[0],
    293 					text: filename, // this needs to be here for sorting reasons
    294 					size: fsize(entry[1]),
    295 					leaf: true,
    296 					checked: priorities[entry[0]],
    297 					iconCls: 'x-deluge-file',
    298 					uiProvider: Ext.tree.ColumnNodeUI
    299 				});
    300 				node.on('checkchange', this.onNodeCheck, this);
    301 				parent.appendChild(node);
    302 			}
    303 		}, this, root);
    304 		root.firstChild.expand();
    305 	},
    306
    307 	walkFileTree: function(files, callback, scope, parent) {
    308 		for (var filename in files) {
    309 			var entry = files[filename];
    310 			var type = (Ext.type(entry) == 'object') ? 'dir' : 'file';
    311
    312 			if (scope) {
    313 				var ret = callback.apply(scope, [filename, type, entry, parent]);
    314 			} else {
    315 				var ret = callback(filename, type, entry, parent);
    316 			}
    317
    318 			if (type == 'dir') this.walkFileTree(entry, callback, scope, ret);
    319 		}
    320 	},
    321
    322 	onFolderCheck: function(node, checked) {
    323 		var priorities = this.optionsManager.get(this.torrentId, 'file_priorities');
    324 		node.cascade(function(child) {
    325 			if (!child.ui.checkbox) {
    326 				child.attributes.checked = checked;
    327 			} else {
    328 				child.ui.checkbox.checked = checked;
    329 			}
    330 			priorities[child.attributes.fileindex] = checked;
    331 		}, this);
    332 		this.optionsManager.update(this.torrentId, 'file_priorities', priorities);
    333 	},
    334
    335 	onNodeCheck: function(node, checked) {
    336 		var priorities = this.optionsManager.get(this.torrentId, 'file_priorities');
    337 		priorities[node.attributes.fileindex] = checked;
    338 		this.optionsManager.update(this.torrentId, 'file_priorities', priorities);
    339 	}
    340 });
    341
    342 Ext.deluge.add.Window = Ext.extend(Ext.Window, {
    343 	initComponent: function() {
    344 		Ext.deluge.add.Window.superclass.initComponent.call(this);
    345 		this.addEvents(
    346 			'beforeadd',
    347 			'add'
    348 		);
    349 	},
    350
    351 	createTorrentId: function() {
    352 		return new Date().getTime();
    353 	}
    354 });
    355
    356 Ext.deluge.add.AddWindow = Ext.extend(Ext.deluge.add.Window, {
    357
    358 	constructor: function(config) {
    359 		config = Ext.apply({
    360 			title: _('Add Torrents'),
    361 			layout: 'border',
    362 			width: 470,
    363 			height: 450,
    364 			bodyStyle: 'padding: 10px 5px;',
    365 			buttonAlign: 'right',
    366 			closeAction: 'hide',
    367 			closable: true,
    368 			plain: true,
    369 			iconCls: 'x-deluge-add-window-icon'
    370 		}, config);
    371 		Ext.deluge.add.AddWindow.superclass.constructor.call(this, config);
    372 	},
    373
    374 	initComponent: function() {
    375 		Ext.deluge.add.AddWindow.superclass.initComponent.call(this);
    376
    377 		this.addButton(_('Cancel'), this.onCancel, this);
    378 		this.addButton(_('Add'), this.onAdd, this);
    379
    380 		function torrentRenderer(value, p, r) {
    381 			if (r.data['info_hash']) {
    382 				return String.format('<div class="x-deluge-add-torrent-name">{0}</div>', value);
    383 			} else {
    384 				return String.format('<div class="x-deluge-add-torrent-name-loading">{0}</div>', value);
    385 			}
    386 		}
    387
    388 		this.grid = this.add({
    389 			xtype: 'grid',
    390 			region: 'center',
    391 			store: new Ext.data.SimpleStore({
    392 				fields: [
    393 					{name: 'info_hash', mapping: 1},
    394 					{name: 'text', mapping: 2}
    395 				],
    396 				id: 0
    397 			}),
    398 			columns: [{
    399 					id: 'torrent',
    400 					width: 150,
    401 					sortable: true,
    402 					renderer: torrentRenderer,
    403 					dataIndex: 'text'
    404 			}],
    405 			stripeRows: true,
    406 			selModel: new Ext.grid.RowSelectionModel({
    407 				singleSelect: true,
    408 				listeners: {
    409 					'rowselect': {
    410 						fn: this.onSelect,
    411 						scope: this
    412 					}
    413 				}
    414 			}),
    415 			hideHeaders: true,
    416 			autoExpandColumn: 'torrent',
    417 			deferredRender: false,
    418 			autoScroll: true,
    419 			margins: '5 5 5 5',
    420 			bbar: new Ext.Toolbar({
    421 				items: [{
    422 					id: 'file',
    423 					cls: 'x-btn-text-icon',
    424 					iconCls: 'x-deluge-add-file',
    425 					text: _('File'),
    426 					handler: this.onFile,
    427 					scope: this
    428 				}, {
    429 					id: 'url',
    430 					cls: 'x-btn-text-icon',
    431 					text: _('Url'),
    432 					icon: '/icons/add_url.png',
    433 					handler: this.onUrl,
    434 					scope: this
    435 				}, {
    436 					id: 'infohash',
    437 					cls: 'x-btn-text-icon',
    438 					text: _('Infohash'),
    439 					icon: '/icons/add_magnet.png',
    440 					disabled: true
    441 				}, '->', {
    442 					id: 'remove',
    443 					cls: 'x-btn-text-icon',
    444 					text: _('Remove'),
    445 					icon: '/icons/remove.png',
    446 					handler: this.onRemove,
    447 					scope: this
    448 				}]
    449 			})
    450 		});
    451
    452 		this.optionsPanel = this.add(new Ext.deluge.add.OptionsPanel());
    453 		this.on('show', this.onShow, this);
    454 	},
    455
    456 	clear: function() {
    457 		this.torrents = {};
    458 		this.grid.getStore().removeAll();
    459 	},
    460
    461 	onAdd: function() {
    462 		var torrents = [];
    463 		this.grid.getStore().each(function(r) {
    464 			var id = r.get('info_hash');
    465 			torrents.push({
    466 				path: this.optionsPanel.getFilename(id),
    467 				options: this.optionsPanel.getOptions(id)
    468 			});
    469 		}, this);
    470
    471 		Deluge.Client.web.add_torrents(torrents, {
    472 			success: function(result) {
    473 			}
    474 		})
    475 		this.clear();
    476 		this.hide();
    477 	},
    478
    479 	onCancel: function() {
    480 		this.clear();
    481 		this.hide();
    482 	},
    483
    484 	onFile: function() {
    485 		this.file.show();
    486 	},
    487
    488 	onRemove: function() {
    489 		var selection = this.grid.getSelectionModel();
    490 		if (!selection.hasSelection()) return;
    491 		var torrent = selection.getSelected();
    492
    493 		delete this.torrents[torrent.id];
    494 		this.grid.getStore().remove(torrent);
    495 		this.options.clear();
    496 	},
    497
    498 	onSelect: function(selModel, rowIndex, record) {
    499 		this.optionsPanel.setTorrent(record.get('info_hash'));
    500 	},
    501
    502 	onShow: function() {
    503 		if (!this.url) {
    504 			this.url = new Ext.deluge.add.UrlWindow();
    505 			this.url.on('beforeadd', this.onTorrentBeforeAdd, this);
    506 			this.url.on('add', this.onTorrentAdd, this);
    507 		}
    508
    509 		if (!this.file) {
    510 			this.file = new Ext.deluge.add.FileWindow();
    511 			this.file.on('beforeadd', this.onTorrentBeforeAdd, this);
    512 			this.file.on('add', this.onTorrentAdd, this);
    513 		}
    514
    515 		this.optionsPanel.getDefaults();
    516 	},
    517
    518 	onTorrentBeforeAdd: function(torrentId, text) {
    519 		var store = this.grid.getStore();
    520 		store.loadData([[torrentId, null, text]], true);
    521 	},
    522
    523 	onTorrentAdd: function(torrentId, info) {
    524 		if (!info) {
    525 			Ext.MessageBox.show({
    526 				title: _('Error'),
    527 				msg: _('Not a valid torrent'),
    528 				buttons: Ext.MessageBox.OK,
    529 				modal: false,
    530 				icon: Ext.MessageBox.ERROR,
    531 				iconCls: 'x-deluge-icon-error'
    532 			});
    533 			return;
    534 		}
    535
    536 		var r = this.grid.getStore().getById(torrentId);
    537 		r.set('info_hash', info['info_hash']);
    538 		r.set('text', info['name']);
    539 		this.grid.getStore().commitChanges();
    540 		this.optionsPanel.addTorrent(info);
    541 	},
    542
    543 	onUrl: function(button, event) {
    544 		this.url.show();
    545 	}
    546 });
    547 Deluge.Add = new Ext.deluge.add.AddWindow();
    548