├── .gitignore ├── README.md ├── setup.py └── utorrentimport ├── __init__.py ├── common.py ├── core.py ├── data ├── config.glade └── utorrentimport.js ├── dialogs.py ├── events.py ├── gtkui.py ├── torrent_event_ledger.py ├── translate_meta.py └── webui.py /.gitignore: -------------------------------------------------------------------------------- 1 | # osx 2 | .DS_Store 3 | 4 | # python 5 | .idea/ 6 | build/ 7 | dist/ 8 | *.egg-info/ 9 | *.pyc 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uTorrentImport 2 | ### a cross platform Deluge plugin to import torrents from uTorrent 3 | 4 | ***NOTE: ONLY WORKS FOR DELUGE 1.3.x! NOT YET COMPATABLE FOR DELUGE 2.0*** 5 | 6 | **v2.3.8** 7 | *Download [HERE](https://github.com/Laharah/deluge-uTorrentImport/releases/latest)* 8 | 9 | * supports WINE mappings 10 | * automatically searches for relevant uTorrent sessions 11 | * Advanced support for renamed folder or relocated files. 12 | * Support for importing the bandwidth settings for each torrent 13 | * Supports setting the Added Date to match uTorrent's date added 14 | * Skipped files are carried over from uTorrent 15 | * **ONE BUTTON. NO COMMAND LINE NEEDED!** 16 | 17 | 18 | #### Installation and use: 19 | 20 | 1. Download the appropriate .egg file here (use both if you're not sure): 21 | [DOWNLOADS](https://github.com/Laharah/deluge-uTorrentImport/releases/latest) 22 | 2. Open Deluge, go to Preferences > Plugins > Install Plugin 23 | 3. Add the .egg file(s) 24 | 4. Turn on the plugin (tick the box) 25 | 5. press the button! 26 | 27 | ![Screenshot](http://zippy.gfycat.com/LimpThreadbareAyeaye.gif) 28 | 29 | ### Changelog: 30 | #### v2.3: 31 | - Files skipped in uTorrent will not be downloaded by Deluge 32 | - Some bug fixes for unicode torrent file names 33 | 34 | #### v2.2.6: 35 | - Added an option for preserving the uTorrent added date 36 | - Added options for importing bandwidth settings for uTorrent torrents 37 | - fixed rare bug with event timings that could prevent a torrent being rechecked 38 | - fixed a bug with WINE path torrent files on separate drives. 39 | - fixed a bug where backed up .torrent files could be saved without extension 40 | 41 | #### v2.1.3: 42 | - Added a Dialog to notify the user when the import is finished 43 | - Better error reporting for missing or corrupt resume.dat files. 44 | - Fixed a bug that was causing unnecessary renames 45 | - Fixed a bug that could cause torrents with only one file nested in a folder to be 46 | incorrectly renamed. 47 | - Fixed a bug related to unicode torrent files that could cause the import to halt. 48 | 49 | #### v2.1.0: 50 | * Fixed some renamed torrents not rechecking properly 51 | * added support for correctly redirecting individual files relocated by uTorrent 52 | * Lowered chance of torrents beginning to download when adding large amounts of torrents at once. (adding may take more time now) 53 | 54 | #### v2.0.2: 55 | * Fixed torrent file unicode errors halting import 56 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # setup.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | from setuptools import setup 41 | 42 | __plugin_name__ = "uTorrentImport" 43 | __author__ = "Laharah" 44 | __author_email__ = "laharah22+deluge@gmail.com" 45 | __version__ = "2.3.9" 46 | __url__ = "https://github.com/Laharah/deluge-uTorrentImport" 47 | __license__ = "GPLv3" 48 | __description__ = "Import torrents from uTorrent" 49 | __long_description__ = """ 50 | Searches for a uTorrent resume.dat file, parses it, and loads the torrents into 51 | deluge.""" 52 | __pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]} 53 | 54 | setup( 55 | name=__plugin_name__, 56 | version=__version__, 57 | description=__description__, 58 | author=__author__, 59 | author_email=__author_email__, 60 | url=__url__, 61 | license=__license__, 62 | long_description=__long_description__ if __long_description__ else __description__, 63 | 64 | packages=[__plugin_name__.lower()], 65 | package_data = __pkg_data__, 66 | 67 | entry_points=""" 68 | [deluge.plugin.core] 69 | %s = %s:CorePlugin 70 | [deluge.plugin.gtkui] 71 | %s = %s:GtkUIPlugin 72 | [deluge.plugin.web] 73 | %s = %s:WebUIPlugin 74 | """ % ((__plugin_name__, __plugin_name__.lower())*3) 75 | ) 76 | -------------------------------------------------------------------------------- /utorrentimport/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | from deluge.plugins.init import PluginInitBase 41 | 42 | class CorePlugin(PluginInitBase): 43 | def __init__(self, plugin_name): 44 | from core import Core as _plugin_cls 45 | self._plugin_cls = _plugin_cls 46 | super(CorePlugin, self).__init__(plugin_name) 47 | 48 | class GtkUIPlugin(PluginInitBase): 49 | def __init__(self, plugin_name): 50 | from gtkui import GtkUI as _plugin_cls 51 | self._plugin_cls = _plugin_cls 52 | super(GtkUIPlugin, self).__init__(plugin_name) 53 | 54 | class WebUIPlugin(PluginInitBase): 55 | def __init__(self, plugin_name): 56 | from webui import WebUI as _plugin_cls 57 | self._plugin_cls = _plugin_cls 58 | super(WebUIPlugin, self).__init__(plugin_name) 59 | -------------------------------------------------------------------------------- /utorrentimport/common.py: -------------------------------------------------------------------------------- 1 | # 2 | # common.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | from deluge.log import LOG as delugelog 41 | import deluge.component as component 42 | from events import uTorrentImportLoggingEvent 43 | 44 | 45 | def get_resource(filename): 46 | import pkg_resources, os 47 | return pkg_resources.resource_filename("utorrentimport", os.path.join("data", 48 | filename)) 49 | 50 | 51 | class Log(object): 52 | """ 53 | small wrapper class for formatting log outputs 54 | 55 | Supports transmitting the log events to the user via a custom event. 56 | """ 57 | 58 | def __init__(self): 59 | self.transmitting = False 60 | self.event_manager = component.get('EventManager') 61 | 62 | def __enter__(self): 63 | self.transmitting = True 64 | 65 | def __exit__(self, exc_type, exc_val, exc_tb): 66 | self.transmitting = False 67 | 68 | def error(self, msg): 69 | level = 'error' 70 | delugelog.error(u"[uTorrentImport] {0}".format(msg)) 71 | if self.transmitting: 72 | self.event_manager.emit(uTorrentImportLoggingEvent(level, msg)) 73 | 74 | def info(self, msg): 75 | level = 'info' 76 | delugelog.info(u"[uTorrentImport] {0}".format(msg)) 77 | if self.transmitting: 78 | self.event_manager.emit(uTorrentImportLoggingEvent(level, msg)) 79 | 80 | def debug(self, msg): 81 | level = 'debug' 82 | delugelog.debug(u"[uTorrentImport] {0}".format(msg)) 83 | if self.transmitting: 84 | self.event_manager.emit(uTorrentImportLoggingEvent(level, msg)) 85 | 86 | def critical(self, msg): 87 | level = 'critical' 88 | delugelog.critical(u"[uTorrentImport] {0}".format(msg)) 89 | if self.transmitting: 90 | self.event_manager.emit(uTorrentImportLoggingEvent(level, msg)) 91 | 92 | def warning(self, msg): 93 | level = 'warning' 94 | delugelog.warning(u"[uTorrentImport] {0}".format(msg)) 95 | if self.transmitting: 96 | self.event_manager.emit(uTorrentImportLoggingEvent(level, msg)) 97 | -------------------------------------------------------------------------------- /utorrentimport/core.py: -------------------------------------------------------------------------------- 1 | # 2 | # core.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | import os 41 | import re 42 | import base64 43 | from getpass import getuser 44 | 45 | from deluge.bencode import bdecode 46 | from deluge.plugins.pluginbase import CorePluginBase 47 | import deluge.component as component 48 | from deluge.common import decode_string 49 | import deluge.configmanager 50 | from deluge.core.rpcserver import export 51 | from twisted.internet import defer, reactor 52 | 53 | from utorrentimport.common import Log 54 | import torrent_event_ledger 55 | from utorrentimport import translate_meta 56 | 57 | log = Log() 58 | 59 | DEFAULT_PREFS = { 60 | "torrent_blacklist": ['.fileguard', 'rec'], 61 | "wine_drives": {}, 62 | "use_wine_mappings": False, 63 | "force_recheck": True, 64 | "resume": False, 65 | "previous_resume_dat_path": '', 66 | "transfer_meta": [] 67 | } 68 | 69 | 70 | class Core(CorePluginBase): 71 | def __init__(self, plugin_name): 72 | super(Core, self).__init__(plugin_name) 73 | log.debug("initialized successfully...") 74 | 75 | def enable(self): 76 | self.config = deluge.configmanager.ConfigManager("utorrentimport.conf", 77 | DEFAULT_PREFS) 78 | self.torrent_manager = component.get("TorrentManager") 79 | self.event_ledger = torrent_event_ledger.TorrentEventLedger(timeout=60) 80 | 81 | def disable(self): 82 | pass 83 | 84 | def update(self): 85 | pass 86 | 87 | ######### 88 | # Section: Utilities 89 | ######### 90 | 91 | @export 92 | def get_default_resume_path(self): 93 | """ 94 | Checks the various common paths resume.dat may reside and returns a path to 95 | it if it's found. 96 | """ 97 | log.debug('Getting resume.dat path...') 98 | app_datas = [] 99 | user_home = os.path.expanduser('~') 100 | if os.getenv('APPDATA'): 101 | app_datas.append(os.getenv('APPDATA')) 102 | app_datas.append( 103 | os.path.join(user_home, '.wine/drive_c/users', getuser(), 'Application Data')) 104 | app_datas.append(os.path.join(user_home, 'Library', 'Application Support')) 105 | app_datas.append('/opt') 106 | app_datas.append(user_home) 107 | 108 | for app_data in app_datas: 109 | resume_path = os.path.join(app_data, 'uTorrent', 'resume.dat') 110 | if not os.path.exists(resume_path) or not os.path.isfile(resume_path): 111 | log.debug('no resume.dat found at {0}...'.format(app_data)) 112 | 113 | else: 114 | log.debug('resume.dat found at {0}'.format(resume_path)) 115 | return resume_path 116 | 117 | log.debug('no resume.dat could be found') 118 | return None 119 | 120 | def read_resume_data(self, path): 121 | """given the path to resume.dat, decode and return it""" 122 | if not os.path.exists(path): 123 | er = ("{0} could not be found. Please check the file exists and " 124 | "that you have permission to read it.".format(path)) 125 | log.error(er) 126 | raise AssertionError(er) 127 | if not os.path.isfile(path): 128 | er = '{0} is a folder, "Path to resume.dat" must be a file.'.format(path) 129 | log.error(er) 130 | raise AssertionError(er) 131 | try: 132 | with open(path, 'rb') as f: 133 | raw = f.read() 134 | except (IOError, OSError) as e: 135 | log.error('Could not open {0}. Reason{1}'.format(path, e)) 136 | raise 137 | return bdecode(raw) 138 | 139 | def find_wine_drives(self): 140 | """ 141 | Searches for WINE drives and adds them to a dictionary for 142 | mapping to while importing torrents 143 | """ 144 | drives = os.path.join(os.path.expanduser('~'), '.wine/dosdevices') 145 | if os.path.isdir(drives): 146 | log.info('Found WINE drive mappings:') 147 | for drive in [ 148 | d for d in os.listdir(drives) 149 | if re.match('^[A-Z]:$', d, re.IGNORECASE) 150 | ]: 151 | location = os.path.abspath(os.path.join(drives, drive)) 152 | self.config['wine_drives'][drive.lower()] = location 153 | log.info("{0} => {1}".format(self.config['wine_drives'][drive.lower()], 154 | location)) 155 | self.config.save() 156 | 157 | def wine_path_check(self, path): 158 | """ 159 | Used to check if a path is mapped to a wine drive and returns the corrected path 160 | """ 161 | mapped = path 162 | drive = re.match(r'^([A-Z]:)', path, re.IGNORECASE) 163 | try: 164 | if self.config['wine_drives'] and drive is not None: 165 | mapped = (self.config['wine_drives'][drive.group(1).lower()] + 166 | path[2:].replace('\\', '/')) 167 | except KeyError: 168 | log.debug('No WINE mapping for drive {0}'.format(drive.group(1))) 169 | return mapped 170 | 171 | def resolve_path_renames(self, 172 | torrent_id, 173 | torrent_root, 174 | force_recheck=True, 175 | targets=None): 176 | """ 177 | resolves issues stemming from utorrent renames not encoded into the torrent 178 | torrent_id: torrent_id 179 | torrent_root: what the torrent root should be (according to utorrent) 180 | force_recheck; recheck the torrent regardless of any changes 181 | targets: list of target changes from a utorrent resume.dat 182 | """ 183 | torrent = self.torrent_manager[torrent_id] 184 | files = torrent.get_files() 185 | deferred_list = [] 186 | if '/' in files[0]['path']: 187 | main_folder = files[0]['path'].split('/')[0] + '/' 188 | if main_folder != torrent_root + '/': 189 | try: 190 | log.info(u'Renaming {0} => {1}'.format(main_folder, torrent_root) 191 | .encode('utf-8')) 192 | except UnicodeDecodeError: 193 | pass 194 | d = self.event_ledger.await_folder_rename(torrent_id, main_folder, 195 | torrent_root + '/') 196 | torrent.rename_folder(main_folder, torrent_root) 197 | deferred_list.append(d) 198 | 199 | if targets: 200 | renames = [] 201 | for index, new_path in targets: 202 | new_path = os.path.join(torrent_root, new_path) 203 | deferred_list.append( 204 | self.event_ledger.await_file_rename(torrent_id, index=index)) 205 | renames.append((index, new_path)) 206 | torrent.rename_files(renames) 207 | 208 | else: 209 | main_file = files[0]['path'] 210 | if main_file != torrent_root: 211 | try: 212 | log.info(u'Renaming {0} => {1}'.format(main_file, torrent_root) 213 | .encode('utf-8')) 214 | except UnicodeDecodeError: 215 | pass 216 | d = self.event_ledger.await_file_rename( 217 | torrent_id, index=0, new_name=torrent_root) 218 | torrent.rename_files([(0, torrent_root)]) 219 | deferred_list.append(d) 220 | 221 | if deferred_list: 222 | deferred_list = defer.DeferredList(deferred_list) 223 | deferred_list.addCallback(lambda x: torrent.force_recheck()) 224 | 225 | if force_recheck and not deferred_list: 226 | torrent.force_recheck() 227 | return 228 | 229 | def take_breath(self): 230 | d = defer.Deferred() 231 | reactor.callLater(.5, d.callback, None) 232 | return d 233 | 234 | ######### 235 | # Section: Public API 236 | ######### 237 | 238 | @export 239 | @defer.inlineCallbacks 240 | def begin_import(self, 241 | resume_data=None, 242 | use_wine_mappings=False, 243 | force_recheck=True, 244 | resume=False, 245 | transfer_meta=None): 246 | """ 247 | attempts to add utorrent torrents to deluge and reports the results back 248 | resume_data: path to utorrent resume data 249 | use_wine_mappings: bool to check torrent paths against wine mappings before 250 | import 251 | force_recheck: recheck all torrents after import 252 | resume: Do not add torrents in the paused state 253 | transfer_meta: a list of torrent option tags to transfer to the new torrent 254 | (also support 'time_added') 255 | """ 256 | 257 | self.find_wine_drives() 258 | if not resume_data: 259 | resume_data = self.get_default_resume_path() 260 | try: 261 | data = self.read_resume_data(resume_data) 262 | except Exception as e: 263 | with log: 264 | log.error('Failed to get resume.dat. Reason: {0}'.format(e)) 265 | defer.returnValue((None, None)) 266 | 267 | added = [] 268 | failed = [] 269 | with self.event_ledger: 270 | with log: 271 | counter = 0 272 | for torrent, info in data.iteritems(): 273 | if torrent in self.config["torrent_blacklist"]: 274 | log.debug('skipping {0}'.format(torrent)) 275 | continue 276 | torrent = decode_string(torrent) 277 | counter += 1 278 | if counter > 10: 279 | yield self.take_breath() 280 | counter = 0 281 | if use_wine_mappings: 282 | torrent = os.path.abspath( 283 | os.path.join( 284 | os.path.dirname(resume_data), 285 | self.wine_path_check(torrent))) 286 | else: 287 | torrent = os.path.abspath( 288 | os.path.join(os.path.dirname(resume_data), torrent)) 289 | success, name = self._import_torrent(torrent, info, use_wine_mappings, 290 | force_recheck, resume, 291 | transfer_meta) 292 | if success: 293 | added.append(name) 294 | else: 295 | if not name: 296 | log.debug('blacklisted torrent, skipping') 297 | else: 298 | failed.append(name) 299 | 300 | defer.returnValue((added, failed)) 301 | 302 | def _import_torrent(self, 303 | torrent, 304 | info, 305 | use_wine_mappings=False, 306 | force_recheck=True, 307 | resume=False, 308 | transfer_meta=None): 309 | """handles importing of a single torrent. Same arguments as `begin_import`""" 310 | 311 | try: 312 | with open(torrent, 'rb') as f: 313 | filedump = base64.encodestring(f.read()) 314 | except IOError: 315 | log.error(u'Could not open torrent {0}! skipping...'.format(torrent)) 316 | return False, torrent 317 | 318 | try: 319 | ut_save_path = decode_string(info['path']) 320 | except TypeError: 321 | pass 322 | 323 | if use_wine_mappings: 324 | ut_save_path = self.wine_path_check(ut_save_path) 325 | 326 | torrent_root = os.path.basename(ut_save_path) 327 | deluge_storage_path = os.path.dirname(ut_save_path) 328 | 329 | try: 330 | log.debug(u'Adding {0} to deluge.'.format(torrent_root)) 331 | except UnicodeDecodeError: 332 | log.error('Bad Filename, skipping') 333 | return False, torrent_root 334 | try: 335 | file_map = dict(info['targets']) 336 | except KeyError: 337 | file_map = {} 338 | options = { 339 | 'download_location': deluge_storage_path, 340 | 'add_paused': True if not resume else False, 341 | 'file_priorities': [0 if p == '\x80' else 1 for p in info['prio']], 342 | 'mapped_files': file_map, 343 | } 344 | torrent_id = component.get("Core").add_torrent_file( 345 | os.path.basename(torrent), filedump=filedump, options=options) 346 | 347 | if torrent_id is None: 348 | try: 349 | log.info(u'FAILURE: "{0}" could not be added, may already ' 350 | u'exsist...'.format(torrent_root)) 351 | except UnicodeDecodeError: 352 | log.error(u'FAILURE: Torrent Unicode Error') 353 | 354 | return False, torrent_root 355 | 356 | else: 357 | try: 358 | log.info(u'SUCCESS!: "{0}" added successfully'.format(torrent_root)) 359 | except UnicodeDecodeError: 360 | log.info(u'SUCCESS: added but with UnicodeError') 361 | self.resolve_path_renames( 362 | torrent_id, torrent_root, force_recheck=force_recheck) 363 | if transfer_meta: 364 | translate_meta.transfer(torrent_id, info, transfer_meta) 365 | return True, torrent_root 366 | 367 | @export 368 | def set_config(self, config): 369 | """Sets the config dictionary""" 370 | log.debug('updating config dictionary: {0}'.format(config)) 371 | for key in config.keys(): 372 | self.config[key] = config[key] 373 | self.config.save() 374 | 375 | @export 376 | def get_config(self): 377 | """Returns the config dictionary""" 378 | log.debug('{0}'.format(self.config.config)) 379 | return self.config.config 380 | -------------------------------------------------------------------------------- /utorrentimport/data/config.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 8 | 9 | True 10 | False 11 | 12 | 13 | True 14 | False 15 | <b>***Please Ensure uTorrent Is Not Running!***</b> 16 | True 17 | 18 | 19 | False 20 | True 21 | 0 22 | 23 | 24 | 25 | 26 | True 27 | False 28 | <i>Note: Deluge may be unresponsive while adding a large number of torrents.</i> 29 | True 30 | 31 | 32 | False 33 | False 34 | 1 35 | 36 | 37 | 38 | 39 | True 40 | False 41 | 42 | 43 | True 44 | False 45 | 0 46 | 6 47 | Path to resume.dat: 48 | 49 | 50 | False 51 | True 52 | 0 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | Path where the uTorrent resume.dat file can be found. 60 | Should auto-populate for most setups. 61 | 62 | False 63 | False 64 | True 65 | True 66 | 67 | 68 | True 69 | True 70 | 6 71 | 1 72 | 73 | 74 | 75 | 76 | True 77 | True 78 | 2 79 | 80 | 81 | 82 | 83 | True 84 | False 85 | 6 86 | 6 87 | 88 | 89 | True 90 | False 91 | 92 | 93 | True 94 | False 95 | 96 | 97 | Preserve Date Added 98 | True 99 | True 100 | False 101 | Reset the date added in deluge to match the date each torrent was added in uTorrent 102 | True 103 | 104 | 105 | False 106 | False 107 | 0 108 | 109 | 110 | 111 | 112 | Use WINE Mappings 113 | True 114 | True 115 | False 116 | Enable WINE drive letter mapping when resolving torrent paths. 117 | If you're unsure, you most likley don't need it. 118 | True 119 | 120 | 121 | False 122 | False 123 | 1 124 | 125 | 126 | 127 | 128 | Resume After Adding 129 | True 130 | True 131 | False 132 | Resume Torrents after they are added to deluge. 133 | True 134 | 135 | 136 | 137 | False 138 | False 139 | 2 140 | 141 | 142 | 143 | 144 | Force Recheck 145 | True 146 | True 147 | False 148 | Forces Recheck on all newly added torrents. 149 | True 150 | 151 | 152 | False 153 | False 154 | 3 155 | 156 | 157 | 158 | 159 | True 160 | True 161 | 0 162 | 163 | 164 | 165 | 166 | True 167 | False 168 | These options will import the uTorrent Setting for each torrent. 169 | 1 170 | 0 171 | 172 | 173 | True 174 | False 175 | 12 176 | 177 | 178 | True 179 | False 180 | 181 | 182 | Max Download Speed 183 | True 184 | True 185 | False 186 | Set each torrent's Max Download Speed to what it was in uTorrent 187 | True 188 | 189 | 190 | False 191 | False 192 | 0 193 | 194 | 195 | 196 | 197 | Max Upload Speed 198 | True 199 | True 200 | False 201 | Set each torrent's Max Upload Speed to what it was in uTorrent 202 | True 203 | 204 | 205 | False 206 | False 207 | 1 208 | 209 | 210 | 211 | 212 | Max Upload Slots 213 | True 214 | True 215 | False 216 | Set each torrent's Max Upload Slots to what it was in uTorrent 217 | True 218 | 219 | 220 | False 221 | False 222 | 2 223 | 224 | 225 | 226 | 227 | Max Connections 228 | True 229 | True 230 | False 231 | Set each torrent's Max Connections to what it was in uTorrent 232 | True 233 | 234 | 235 | False 236 | False 237 | 3 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | True 247 | False 248 | Transfer Torrent Settings: 249 | True 250 | 251 | 252 | label_item 253 | 254 | 255 | 256 | 257 | False 258 | False 259 | 1 260 | 261 | 262 | 263 | 264 | 265 | 266 | False 267 | True 268 | 3 269 | 270 | 271 | 272 | 273 | True 274 | True 275 | 5 276 | automatic 277 | automatic 278 | in 279 | 280 | 281 | True 282 | True 283 | False 284 | False 285 | 286 | 287 | 288 | 289 | True 290 | True 291 | 4 292 | 293 | 294 | 295 | 296 | True 297 | False 298 | 299 | 300 | 301 | 302 | 303 | Begin Import 304 | True 305 | True 306 | True 307 | 308 | 309 | 310 | True 311 | False 312 | 1 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | False 321 | False 322 | 5 323 | 324 | 325 | 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /utorrentimport/data/utorrentimport.js: -------------------------------------------------------------------------------- 1 | /* 2 | Script: utorrentimport.js 3 | The client-side javascript code for the uTorrentImport plugin. 4 | 5 | Copyright: 6 | (C) Laharah 2009 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 | uTorrentImportPlugin = Ext.extend(Deluge.Plugin, { 35 | constructor: function(config) { 36 | config = Ext.apply({ 37 | name: "uTorrentImport" 38 | }, config); 39 | uTorrentImportPlugin.superclass.constructor.call(this, config); 40 | }, 41 | 42 | onDisable: function() { 43 | 44 | }, 45 | 46 | onEnable: function() { 47 | 48 | } 49 | }); 50 | new uTorrentImportPlugin(); 51 | -------------------------------------------------------------------------------- /utorrentimport/dialogs.py: -------------------------------------------------------------------------------- 1 | import gtk 2 | 3 | 4 | class AsyncDialog(gtk.Dialog): 5 | """ 6 | A gtk Dialog that does not block. 7 | has these additional arguments: 8 | response_callback: the callback to handle the response signal 9 | destroy_signals: A convince for setting signals that will destroy the dialog. 10 | """ 11 | 12 | def __init__(self, 13 | title=None, 14 | parent=None, 15 | flags=None, 16 | buttons=None, 17 | response_callback=None, 18 | destroy_signals=None): 19 | gtk.Dialog.__init__(self, title, parent, flags, buttons) 20 | 21 | self.response_callback = response_callback 22 | 23 | if not isinstance(destroy_signals, list): 24 | destroy_signals = [destroy_signals] 25 | self.destroy_signals = destroy_signals 26 | 27 | def run(self): 28 | """a version of gtk.Dialog.run that does not block""" 29 | 30 | def dialog_response_cb(dialog, response_id): 31 | if response_id in self.destroy_signals: 32 | self.destroy() 33 | if self.response_callback: 34 | self.response_callback(response_id) 35 | 36 | self.connect('response', dialog_response_cb) 37 | if not self.modal: 38 | self.set_modal(True) 39 | self.show() 40 | -------------------------------------------------------------------------------- /utorrentimport/events.py: -------------------------------------------------------------------------------- 1 | from deluge.event import DelugeEvent 2 | 3 | 4 | class uTorrentImportLoggingEvent(DelugeEvent): 5 | """emmited by uTorrentImport plugin for messaging purposes""" 6 | 7 | def __init__(self, level, message): 8 | """ 9 | :param level: The log level of the message 10 | :param message: The message to transmit to listeners 11 | """ 12 | self._args = [level, message] 13 | -------------------------------------------------------------------------------- /utorrentimport/gtkui.py: -------------------------------------------------------------------------------- 1 | # 2 | # gtkui.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | from twisted.internet import defer 41 | 42 | import gtk 43 | from deluge.log import LOG as log 44 | from deluge.ui.client import client 45 | from deluge.plugins.pluginbase import GtkPluginBase 46 | import deluge.component as component 47 | from common import get_resource 48 | 49 | import dialogs 50 | 51 | 52 | class GtkUI(GtkPluginBase): 53 | def enable(self): 54 | self.glade = gtk.glade.XML(get_resource("config.glade")) 55 | 56 | component.get("Preferences").add_page("uTorrentImport", 57 | self.glade.get_widget("prefs_box")) 58 | 59 | component.get("PluginManager").register_hook("on_apply_prefs", 60 | self.on_apply_prefs) 61 | 62 | component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs) 63 | signal_dictionary = { 64 | 'on_import_button_clicked': self.on_import_button_clicked, 65 | 'on_resume_toggled': self.on_resume_toggled 66 | } 67 | log.debug('utorrentimport: signals hooked!') 68 | self.glade.signal_autoconnect(signal_dictionary) 69 | self.use_wine_mappings = self.glade.get_widget('use_wine_mappings') 70 | self.force_recheck = self.glade.get_widget('force_recheck') 71 | self.resume = self.glade.get_widget('resume') 72 | self.resume_dat_entry = self.glade.get_widget('resume_dat_entry') 73 | self.log_view = self.glade.get_widget('log_view') 74 | 75 | client.register_event_handler('uTorrentImportLoggingEvent', self.log_to_user) 76 | 77 | def disable(self): 78 | component.get("Preferences").remove_page("uTorrentImport") 79 | component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs) 80 | 81 | component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs) 82 | 83 | def on_apply_prefs(self): 84 | log.debug("applying prefs for uTorrentImport") 85 | self.config.update(self.gather_settings()) 86 | client.utorrentimport.set_config(self.config) 87 | 88 | def log_to_user(self, level, message): 89 | """A callback to listen for uTorrentImport log events and display them""" 90 | if level in ('error', 'info',): 91 | buffer = self.log_view.get_buffer() 92 | iter = buffer.get_end_iter() 93 | buffer.insert(iter, message + '\n') 94 | adj = self.log_view.get_parent().get_vadjustment() 95 | adj.set_value(adj.get_upper() - adj.get_page_size()) 96 | 97 | @defer.inlineCallbacks 98 | def on_show_prefs(self): 99 | log.debug("showing utorrentimport prefs") 100 | self.config = yield client.utorrentimport.get_config() 101 | log.debug('got config: {0}'.format(self.config)) 102 | self.populate_config(self.config) 103 | log.debug('config populated') 104 | self.on_resume_toggled(_) # Prevents invalid state 105 | if not self.config['previous_resume_dat_path']: 106 | default_resume = yield client.utorrentimport.get_default_resume_path() 107 | log.debug('utorrentimport: got resume.dat path!') 108 | if default_resume: 109 | self.resume_dat_entry.set_text(default_resume) 110 | 111 | @defer.inlineCallbacks 112 | def on_import_button_clicked(self, button): 113 | self.toggle_button(button) 114 | self.log_view.get_buffer().set_text('') 115 | settings = self.gather_settings() 116 | log.debug('sending import command...') 117 | 118 | result = yield client.utorrentimport.begin_import( 119 | settings['previous_resume_dat_path'], 120 | use_wine_mappings=settings['use_wine_mappings'], 121 | force_recheck=settings['force_recheck'], 122 | resume=settings['resume'], 123 | transfer_meta=settings['transfer_meta']) 124 | 125 | log.debug('recieved result! {0}'.format(result)) 126 | self.toggle_button(button) 127 | self.show_result(result) 128 | 129 | def show_result(self, results): 130 | """displays a dialog to the user with the results of the import""" 131 | successes, failures = results 132 | title = u'uTorrentImport Finished' 133 | dialog = dialogs.AsyncDialog(title, None, True, (gtk.STOCK_OK, gtk.RESPONSE_OK), 134 | destroy_signals=gtk.RESPONSE_OK) 135 | 136 | if successes is failures is None: 137 | message = 'Error Running uTorrentImport! See log in preferences!' 138 | else: 139 | message = u''' 140 | uTorrentImport has finished importing torrents from uTorrent. 141 | 142 | {0} torrents have been added to deluge. 143 | {1} torrents were skipped. 144 | 145 | You may wish to restart the deluge UI to update the status of the added torrents. 146 | '''.format(len(successes), len(failures)) 147 | 148 | label = gtk.Label(message) 149 | dialog.get_content_area().add(label) 150 | dialog.set_position(gtk.WIN_POS_CENTER) 151 | dialog.set_gravity(gtk.gdk.GRAVITY_CENTER) 152 | dialog.show_all() 153 | dialog.run() 154 | 155 | def on_resume_toggled(self, _): 156 | """foreces the 'Force Recheck' checkbox to be checked if the user dosen't 157 | add the torrents in a resumed state""" 158 | if not self.resume.get_active(): 159 | self._previous_force_recheck = self.force_recheck.get_active() 160 | self.force_recheck.set_sensitive(False) 161 | self.force_recheck.set_active(True) 162 | else: 163 | self.force_recheck.set_active(self._previous_force_recheck) 164 | self.force_recheck.set_sensitive(True) 165 | 166 | def toggle_button(self, button): 167 | """used to keep the user from running multiple imports at once""" 168 | if button.get_sensitive(): 169 | button.set_sensitive(False) 170 | else: 171 | button.set_sensitive(True) 172 | 173 | def populate_config(self, config): 174 | """callback for on show_prefs""" 175 | self.use_wine_mappings.set_active(config['use_wine_mappings']) 176 | self.force_recheck.set_active(config['force_recheck']) 177 | self._previous_force_recheck = config['force_recheck'] 178 | self.resume.set_active(config['resume']) 179 | try: 180 | self.glade.get_widget('time_added_checkbox').set_active( 181 | 'time_added' in config['transfer_meta']) 182 | except KeyError: 183 | pass 184 | self.resume_dat_entry.set_text(config['previous_resume_dat_path']) 185 | 186 | def gather_settings(self): 187 | options = [ 188 | 'time_added', 'max_download_speed', 'max_upload_speed', 189 | 'max_connections', 'max_upload_slots' 190 | ] 191 | transfer_meta = [tag for tag in options 192 | if self.glade.get_widget(tag + '_checkbox').get_active()] 193 | return { 194 | 'use_wine_mappings': self.use_wine_mappings.get_active(), 195 | 'force_recheck': self.force_recheck.get_active(), 196 | 'resume': self.resume.get_active(), 197 | 'previous_resume_dat_path': self.resume_dat_entry.get_text(), 198 | 'transfer_meta': transfer_meta 199 | } 200 | -------------------------------------------------------------------------------- /utorrentimport/torrent_event_ledger.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Class for getting deferrds to specific torrent events 3 | """ 4 | __author__ = 'Laharah' 5 | 6 | import time 7 | 8 | from twisted.internet import defer, reactor 9 | import deluge.component as component 10 | from common import Log 11 | 12 | log = Log() 13 | 14 | 15 | class Error(Exception): 16 | """"base exception""" 17 | pass 18 | 19 | 20 | class TorrentEventLedgerNotListening(Error): 21 | """raised when no events are listening""" 22 | 23 | def __init__(self, message=None): 24 | if not message: 25 | message = "The ledger is not currently listening for any events" 26 | super(self.__class__, self).__init__(message) 27 | 28 | 29 | class TorrentEventLedger(object): 30 | """ 31 | keeps track of specific torrent events the plugin is waiting on. 32 | 33 | Holds a dict of torrent events the plugin is watching for and executes 34 | their deferred callbacks when events are complete. 35 | 36 | Contains a context manager to register and deregister its handlers 37 | """ 38 | 39 | def __init__(self, timeout=None): 40 | """If timeout is given, it is used as the ammount of time before forcing 41 | de-register""" 42 | self.timeout = timeout 43 | self.timeout_start = None 44 | self.ledgers = {} 45 | self.event_manager = component.get('EventManager') 46 | self.registered_events = set() 47 | 48 | def start(self, events=None): 49 | """begins listening for given list of (event, cb) pairs""" 50 | self.__enter__(events, defaults=False) 51 | 52 | def stop(self, events=None): 53 | """stops listening for a given event, defaults to all events if none given.""" 54 | events = events if events else self.registered_events 55 | self._force_deregister(events) 56 | 57 | def _force_deregister(self, events): 58 | """forces de-register of give (event, cb) pairs""" 59 | for event, cb in events: 60 | self.event_manager.deregister_event_handler(event, cb) 61 | del self.ledgers[event] 62 | return 63 | 64 | def __enter__(self, events=None, defaults=True): 65 | """ 66 | begins the context manager with optionl (event, cb) pairs 67 | """ 68 | _default_events = [ 69 | ('TorrentFolderRenamedEvent', self._on_folder_renamed), 70 | ('TorrentFileRenamedEvent', self._on_file_renamed) 71 | ] 72 | 73 | events = events if events else [] 74 | if defaults: 75 | events += _default_events 76 | for event, cb in events: 77 | self.event_manager.register_event_handler(event, cb) 78 | self.registered_events.add((event, cb)) 79 | self.ledgers[event] = {} 80 | self.context_events = events 81 | 82 | def __exit__(self, exc_type, exc_val, exc_tb): 83 | """deregisters the context manager listeners""" 84 | if not self.timeout_start: 85 | self.timeout_start = time.time() 86 | ledgers = {} 87 | for event, _ in self.context_events: 88 | ledgers[event] = self.ledgers[event] 89 | for ledger, entries in ledgers.iteritems(): 90 | if self.timeout: 91 | if time.time() - self.timeout_start > self.timeout: 92 | log.debug('timeout reached, turning off listners') 93 | self._force_deregister(self.context_events) 94 | return 95 | 96 | if entries: 97 | log.debug( 98 | 'TorrentEventLedger is still waiting on events: {0}, waiting...'.format( 99 | self.ledgers)) 100 | reactor.callLater(1, self.__exit__, None, None, None) 101 | return 102 | 103 | self._force_deregister(self.context_events) 104 | self.context_events = set() 105 | 106 | def _on_folder_renamed(self, torrent_id, old, new): 107 | """ 108 | default callback for the TorrentFolderRenamedEvent 109 | """ 110 | valid_tuples = [(old, new), (old, None), (None, None)] 111 | self._fire_deferreds('TorrentFolderRenamedEvent', torrent_id, valid_tuples) 112 | 113 | def _on_file_renamed(self, torrent_id, index, new_name): 114 | """ 115 | default callback for the TorrentFileRenamedEvent 116 | """ 117 | valid_tuples = [(index, new_name), (index, None), (None, None)] 118 | 119 | self._fire_deferreds('TorrentFileRenamedEvent', torrent_id, valid_tuples) 120 | 121 | def _fire_deferreds(self, event, torrent_id, valid_tuples): 122 | """fires approprate deferreds for a given event, torrent, and event variables""" 123 | try: 124 | entry = self.ledgers[event][torrent_id] 125 | except KeyError: 126 | return 127 | 128 | for t in valid_tuples: 129 | try: 130 | entry[t].callback(None) 131 | del entry[t] 132 | except KeyError: 133 | pass 134 | 135 | if not entry: 136 | del self.ledgers[event][torrent_id] 137 | 138 | def await_file_rename(self, torrent_id, index=None, new_name=None): 139 | """get a deferred for a specific torrent file rename""" 140 | if not self.registered_events: 141 | raise TorrentEventLedgerNotListening() 142 | 143 | if new_name: 144 | new_name = new_name.replace(u'\\', u'/') 145 | d = defer.Deferred() 146 | try: 147 | self.ledgers['TorrentFileRenamedEvent'][torrent_id][(index, new_name)] = d 148 | except KeyError: 149 | self.ledgers['TorrentFileRenamedEvent'][torrent_id] = {(index, new_name): d} 150 | return d 151 | 152 | def await_folder_rename(self, torrent_id, old=None, new=None): 153 | """get a deferred for a specific torrent folder rename""" 154 | if not self.registered_events: 155 | raise TorrentEventLedgerNotListening() 156 | 157 | for arg in (old, new): 158 | if arg: 159 | if not arg.endswith('/'): 160 | arg = arg + '/' 161 | d = defer.Deferred() 162 | try: 163 | self.ledgers['TorrentFolderRenamedEvent'][torrent_id][(old, new)] = d 164 | except KeyError: 165 | self.ledgers['TorrentFolderRenamedEvent'][torrent_id] = {(old, new): d} 166 | return d 167 | -------------------------------------------------------------------------------- /utorrentimport/translate_meta.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions for extracting and translating metadata from utorrent resume data to deluge 3 | compatable values 4 | """ 5 | 6 | import deluge.component as component 7 | from common import Log 8 | 9 | log = Log() 10 | log.transmitting = True 11 | 12 | # infinite in utorrent is 0, and -1 in deluge 13 | _translate_inf = lambda x: -1 if x == 0 else x 14 | 15 | 16 | def max_download_speed(info): 17 | downspeed = float(info['downspeed']) / 1024 18 | return _translate_inf(downspeed) 19 | 20 | 21 | def max_upload_speed(info): 22 | upspeed = float(info['upspeed']) / 1024 23 | return _translate_inf(upspeed) 24 | 25 | 26 | def max_connections(info): 27 | return info['max_connections'] 28 | 29 | 30 | def max_upload_slots(info): 31 | return _translate_inf(info['ulslots']) 32 | 33 | 34 | def transfer(torrent_id, info, tags): 35 | """ 36 | given a torrents deluge id and utorrent resume data and a list of deluge options, 37 | extract and set each option. 38 | """ 39 | torrent = component.get('TorrentManager')[torrent_id] 40 | tags = set(tags) 41 | if 'time_added' in tags: 42 | log.debug('Setting time_added on torrent {0}'.format(torrent_id)) 43 | torrent.time_added = info['added_on'] 44 | tags.remove('time_added') 45 | translators = { 46 | 'max_download_speed': max_download_speed, 47 | 'max_upload_speed': max_upload_speed, 48 | 'max_connections': max_connections, 49 | 'max_upload_slots': max_upload_slots, 50 | } 51 | 52 | options = {} 53 | for tag in tags: 54 | try: 55 | translate = translators[tag] 56 | except KeyError: 57 | log.error('{0} is not a valid torrent option for transfer'.format(tag)) 58 | continue 59 | value = translate(info) 60 | log.debug('Setting {0} -> {1} on torrent {2}'.format(tag, value, torrent_id)) 61 | options[tag] = value 62 | 63 | torrent.set_options(options) 64 | -------------------------------------------------------------------------------- /utorrentimport/webui.py: -------------------------------------------------------------------------------- 1 | # 2 | # webui.py 3 | # 4 | # Copyright (C) 2009 Laharah 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # 11 | # Deluge is free software. 12 | # 13 | # You may redistribute it and/or modify it under the terms of the 14 | # GNU General Public License, as published by the Free Software 15 | # Foundation; either version 3 of the License, or (at your option) 16 | # any later version. 17 | # 18 | # deluge is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with deluge. If not, write to: 25 | # The Free Software Foundation, Inc., 26 | # 51 Franklin Street, Fifth Floor 27 | # Boston, MA 02110-1301, USA. 28 | # 29 | # In addition, as a special exception, the copyright holders give 30 | # permission to link the code of portions of this program with the OpenSSL 31 | # library. 32 | # You must obey the GNU General Public License in all respects for all of 33 | # the code used other than OpenSSL. If you modify file(s) with this 34 | # exception, you may extend this exception to your version of the file(s), 35 | # but you are not obligated to do so. If you do not wish to do so, delete 36 | # this exception statement from your version. If you delete this exception 37 | # statement from all source files in the program, then also delete it here. 38 | # 39 | 40 | from deluge.log import LOG as log 41 | from deluge.ui.client import client 42 | from deluge import component 43 | from deluge.plugins.pluginbase import WebPluginBase 44 | 45 | from common import get_resource 46 | 47 | class WebUI(WebPluginBase): 48 | 49 | scripts = [get_resource("utorrentimport.js")] 50 | 51 | def enable(self): 52 | pass 53 | 54 | def disable(self): 55 | pass 56 | --------------------------------------------------------------------------------