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