1 | #
|
---|
2 | # core.py
|
---|
3 | #
|
---|
4 | # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
---|
5 | # Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
|
---|
6 | #
|
---|
7 | # Deluge is free software.
|
---|
8 | #
|
---|
9 | # You may redistribute it and/or modify it under the terms of the
|
---|
10 | # GNU General Public License, as published by the Free Software
|
---|
11 | # Foundation; either version 3 of the License, or (at your option)
|
---|
12 | # any later version.
|
---|
13 | #
|
---|
14 | # deluge is distributed in the hope that it will be useful,
|
---|
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
17 | # See the GNU General Public License for more details.
|
---|
18 | #
|
---|
19 | # You should have received a copy of the GNU General Public License
|
---|
20 | # along with deluge. If not, write to:
|
---|
21 | # The Free Software Foundation, Inc.,
|
---|
22 | # 51 Franklin Street, Fifth Floor
|
---|
23 | # Boston, MA 02110-1301, USA.
|
---|
24 | #
|
---|
25 | # In addition, as a special exception, the copyright holders give
|
---|
26 | # permission to link the code of portions of this program with the OpenSSL
|
---|
27 | # library.
|
---|
28 | # You must obey the GNU General Public License in all respects for all of
|
---|
29 | # the code used other than OpenSSL. If you modify file(s) with this
|
---|
30 | # exception, you may extend this exception to your version of the file(s),
|
---|
31 | # but you are not obligated to do so. If you do not wish to do so, delete
|
---|
32 | # this exception statement from your version. If you delete this exception
|
---|
33 | # statement from all source files in the program, then also delete it here.
|
---|
34 | #
|
---|
35 | #
|
---|
36 |
|
---|
37 | from deluge._libtorrent import lt
|
---|
38 |
|
---|
39 | import os
|
---|
40 | import glob
|
---|
41 | import base64
|
---|
42 | import logging
|
---|
43 | import threading
|
---|
44 | import tempfile
|
---|
45 | from urlparse import urljoin
|
---|
46 |
|
---|
47 | import twisted.web.client
|
---|
48 | import twisted.web.error
|
---|
49 |
|
---|
50 | from deluge.httpdownloader import download_file
|
---|
51 |
|
---|
52 | import deluge.configmanager
|
---|
53 | import deluge.common
|
---|
54 | import deluge.component as component
|
---|
55 | from deluge.event import *
|
---|
56 | from deluge.error import *
|
---|
57 | from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE
|
---|
58 | from deluge.core.authmanager import AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE
|
---|
59 | from deluge.core.torrentmanager import TorrentManager
|
---|
60 | from deluge.core.pluginmanager import PluginManager
|
---|
61 | from deluge.core.alertmanager import AlertManager
|
---|
62 | from deluge.core.filtermanager import FilterManager
|
---|
63 | from deluge.core.preferencesmanager import PreferencesManager
|
---|
64 | from deluge.core.authmanager import AuthManager
|
---|
65 | from deluge.core.eventmanager import EventManager
|
---|
66 | from deluge.core.rpcserver import export
|
---|
67 |
|
---|
68 | log = logging.getLogger(__name__)
|
---|
69 |
|
---|
70 | class Core(component.Component):
|
---|
71 | def __init__(self, listen_interface=None):
|
---|
72 | log.debug("Core init..")
|
---|
73 | component.Component.__init__(self, "Core")
|
---|
74 |
|
---|
75 | # Start the libtorrent session
|
---|
76 | log.info("Starting libtorrent %s session..", lt.version)
|
---|
77 |
|
---|
78 | # Create the client fingerprint
|
---|
79 | version = deluge.common.VersionSplit(deluge.common.get_version()).version
|
---|
80 | while len(version) < 4:
|
---|
81 | version.append(0)
|
---|
82 |
|
---|
83 | self.session = lt.session(lt.fingerprint("DE", *version), flags=0)
|
---|
84 |
|
---|
85 | # Load the session state if available
|
---|
86 | self.__load_session_state()
|
---|
87 |
|
---|
88 | # Set the user agent
|
---|
89 | self.settings = lt.session_settings()
|
---|
90 | self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \
|
---|
91 | { 'deluge_version': deluge.common.get_version(),
|
---|
92 | 'lt_version': self.get_libtorrent_version().rpartition(".")[0] }
|
---|
93 | # Increase the alert queue size so that alerts don't get lost
|
---|
94 | self.settings.alert_queue_size = 10000
|
---|
95 |
|
---|
96 | # Set session settings
|
---|
97 | self.settings.send_redundant_have = True
|
---|
98 | if deluge.common.windows_check():
|
---|
99 | self.settings.disk_io_write_mode = \
|
---|
100 | lt.io_buffer_mode_t.disable_os_cache
|
---|
101 | self.settings.disk_io_read_mode = \
|
---|
102 | lt.io_buffer_mode_t.disable_os_cache
|
---|
103 | self.session.set_settings(self.settings)
|
---|
104 |
|
---|
105 | self.session.add_extension("metadata_transfer")
|
---|
106 | self.session.add_extension("ut_metadata")
|
---|
107 | self.session.add_extension("smart_ban")
|
---|
108 |
|
---|
109 | # Create the components
|
---|
110 | self.eventmanager = EventManager()
|
---|
111 | self.preferencesmanager = PreferencesManager()
|
---|
112 | self.alertmanager = AlertManager()
|
---|
113 | self.pluginmanager = PluginManager(self)
|
---|
114 | self.torrentmanager = TorrentManager()
|
---|
115 | self.filtermanager = FilterManager(self)
|
---|
116 | self.authmanager = AuthManager()
|
---|
117 |
|
---|
118 | # New release check information
|
---|
119 | self.new_release = None
|
---|
120 |
|
---|
121 | # Get the core config
|
---|
122 | self.config = deluge.configmanager.ConfigManager("core.conf")
|
---|
123 | self.config.save()
|
---|
124 |
|
---|
125 | # If there was an interface value from the command line, use it, but
|
---|
126 | # store the one in the config so we can restore it on shutdown
|
---|
127 | self.__old_interface = None
|
---|
128 | if listen_interface:
|
---|
129 | self.__old_interface = self.config["listen_interface"]
|
---|
130 | self.config["listen_interface"] = listen_interface
|
---|
131 |
|
---|
132 | def start(self):
|
---|
133 | """Starts the core"""
|
---|
134 | # New release check information
|
---|
135 | self.__new_release = None
|
---|
136 |
|
---|
137 | def stop(self):
|
---|
138 | log.debug("Core stopping...")
|
---|
139 |
|
---|
140 | # Save the DHT state if necessary
|
---|
141 | if self.config["dht"]:
|
---|
142 | self.save_dht_state()
|
---|
143 |
|
---|
144 | # Save the libtorrent session state
|
---|
145 | self.__save_session_state()
|
---|
146 |
|
---|
147 | # We stored a copy of the old interface value
|
---|
148 | if self.__old_interface:
|
---|
149 | self.config["listen_interface"] = self.__old_interface
|
---|
150 |
|
---|
151 | # Make sure the config file has been saved
|
---|
152 | self.config.save()
|
---|
153 |
|
---|
154 | def shutdown(self):
|
---|
155 | pass
|
---|
156 |
|
---|
157 | def __save_session_state(self):
|
---|
158 | """Saves the libtorrent session state"""
|
---|
159 | try:
|
---|
160 | session_state = deluge.configmanager.get_config_dir("session.state")
|
---|
161 | open(session_state, "wb").write(lt.bencode(self.session.save_state()))
|
---|
162 | except Exception, e:
|
---|
163 | log.warning("Failed to save lt state: %s", e)
|
---|
164 |
|
---|
165 | def __load_session_state(self):
|
---|
166 | """Loads the libtorrent session state"""
|
---|
167 | try:
|
---|
168 | session_state = deluge.configmanager.get_config_dir("session.state")
|
---|
169 | self.session.load_state(lt.bdecode(open(session_state, "rb").read()))
|
---|
170 | except Exception, e:
|
---|
171 | log.warning("Failed to load lt state: %s", e)
|
---|
172 |
|
---|
173 | def save_dht_state(self):
|
---|
174 | """Saves the dht state to a file"""
|
---|
175 | try:
|
---|
176 | dht_data = open(deluge.configmanager.get_config_dir("dht.state"), "wb")
|
---|
177 | dht_data.write(lt.bencode(self.session.dht_state()))
|
---|
178 | dht_data.close()
|
---|
179 | except Exception, e:
|
---|
180 | log.warning("Failed to save dht state: %s", e)
|
---|
181 |
|
---|
182 | def get_new_release(self):
|
---|
183 | log.debug("get_new_release")
|
---|
184 | from urllib2 import urlopen
|
---|
185 | try:
|
---|
186 | self.new_release = urlopen(
|
---|
187 | "http://download.deluge-torrent.org/version-1.0").read().strip()
|
---|
188 | except Exception, e:
|
---|
189 | log.debug("Unable to get release info from website: %s", e)
|
---|
190 | return
|
---|
191 | self.check_new_release()
|
---|
192 |
|
---|
193 | def check_new_release(self):
|
---|
194 | if self.new_release:
|
---|
195 | log.debug("new_release: %s", self.new_release)
|
---|
196 | if deluge.common.VersionSplit(self.new_release) > deluge.common.VersionSplit(deluge.common.get_version()):
|
---|
197 | component.get("EventManager").emit(NewVersionAvailableEvent(self.new_release))
|
---|
198 | return self.new_release
|
---|
199 | return False
|
---|
200 |
|
---|
201 | # Exported Methods
|
---|
202 | @export
|
---|
203 | def add_torrent_file(self, filename, filedump, options):
|
---|
204 | """
|
---|
205 | Adds a torrent file to the session.
|
---|
206 |
|
---|
207 | :param filename: the filename of the torrent
|
---|
208 | :type filename: string
|
---|
209 | :param filedump: a base64 encoded string of the torrent file contents
|
---|
210 | :type filedump: string
|
---|
211 | :param options: the options to apply to the torrent on add
|
---|
212 | :type options: dict
|
---|
213 |
|
---|
214 | :returns: the torrent_id as a str or None
|
---|
215 | :rtype: string
|
---|
216 |
|
---|
217 | """
|
---|
218 | try:
|
---|
219 | filedump = base64.decodestring(filedump)
|
---|
220 | except Exception, e:
|
---|
221 | log.error("There was an error decoding the filedump string!")
|
---|
222 | log.exception(e)
|
---|
223 |
|
---|
224 | try:
|
---|
225 | torrent_id = self.torrentmanager.add(
|
---|
226 | filedump=filedump, options=options, filename=filename
|
---|
227 | )
|
---|
228 | except Exception, e:
|
---|
229 | log.error("There was an error adding the torrent file %s", filename)
|
---|
230 | log.exception(e)
|
---|
231 | torrent_id = None
|
---|
232 |
|
---|
233 | return torrent_id
|
---|
234 |
|
---|
235 | @export
|
---|
236 | def add_torrent_url(self, url, options, headers=None):
|
---|
237 | """
|
---|
238 | Adds a torrent from a url. Deluge will attempt to fetch the torrent
|
---|
239 | from url prior to adding it to the session.
|
---|
240 |
|
---|
241 | :param url: the url pointing to the torrent file
|
---|
242 | :type url: string
|
---|
243 | :param options: the options to apply to the torrent on add
|
---|
244 | :type options: dict
|
---|
245 | :param headers: any optional headers to send
|
---|
246 | :type headers: dict
|
---|
247 |
|
---|
248 | :returns: a Deferred which returns the torrent_id as a str or None
|
---|
249 | """
|
---|
250 | log.info("Attempting to add url %s", url)
|
---|
251 | def on_download_success(filename):
|
---|
252 | # We got the file, so add it to the session
|
---|
253 | f = open(filename, "rb")
|
---|
254 | data = f.read()
|
---|
255 | f.close()
|
---|
256 | try:
|
---|
257 | os.remove(filename)
|
---|
258 | except Exception, e:
|
---|
259 | log.warning("Couldn't remove temp file: %s", e)
|
---|
260 | return self.add_torrent_file(
|
---|
261 | filename, base64.encodestring(data), options
|
---|
262 | )
|
---|
263 |
|
---|
264 | def on_download_fail(failure):
|
---|
265 | if failure.check(twisted.web.error.PageRedirect):
|
---|
266 | new_url = urljoin(url, failure.getErrorMessage().split(" to ")[1])
|
---|
267 | result = download_file(
|
---|
268 | new_url, tempfile.mkstemp()[1], headers=headers,
|
---|
269 | force_filename=True
|
---|
270 | )
|
---|
271 | result.addCallbacks(on_download_success, on_download_fail)
|
---|
272 | elif failure.check(twisted.web.client.PartialDownloadError):
|
---|
273 | result = download_file(
|
---|
274 | url, tempfile.mkstemp()[1], headers=headers,
|
---|
275 | force_filename=True, allow_compression=False
|
---|
276 | )
|
---|
277 | result.addCallbacks(on_download_success, on_download_fail)
|
---|
278 | else:
|
---|
279 | # Log the error and pass the failure onto the client
|
---|
280 | log.error("Error occured downloading torrent from %s", url)
|
---|
281 | log.error("Reason: %s", failure.getErrorMessage())
|
---|
282 | result = failure
|
---|
283 | return result
|
---|
284 |
|
---|
285 | d = download_file(
|
---|
286 | url, tempfile.mkstemp()[1], headers=headers, force_filename=True
|
---|
287 | )
|
---|
288 | d.addCallbacks(on_download_success, on_download_fail)
|
---|
289 | return d
|
---|
290 |
|
---|
291 | @export
|
---|
292 | def add_torrent_magnet(self, uri, options):
|
---|
293 | """
|
---|
294 | Adds a torrent from a magnet link.
|
---|
295 |
|
---|
296 | :param uri: the magnet link
|
---|
297 | :type uri: string
|
---|
298 | :param options: the options to apply to the torrent on add
|
---|
299 | :type options: dict
|
---|
300 |
|
---|
301 | :returns: the torrent_id
|
---|
302 | :rtype: string
|
---|
303 |
|
---|
304 | """
|
---|
305 | log.debug("Attempting to add by magnet uri: %s", uri)
|
---|
306 |
|
---|
307 | return self.torrentmanager.add(magnet=uri, options=options)
|
---|
308 |
|
---|
309 | @export
|
---|
310 | def remove_torrent(self, torrent_id, remove_data):
|
---|
311 | """
|
---|
312 | Removes a torrent from the session.
|
---|
313 |
|
---|
314 | :param torrent_id: the torrent_id of the torrent to remove
|
---|
315 | :type torrent_id: string
|
---|
316 | :param remove_data: if True, remove the data associated with this torrent
|
---|
317 | :type remove_data: boolean
|
---|
318 | :returns: True if removed successfully
|
---|
319 | :rtype: bool
|
---|
320 |
|
---|
321 | :raises InvalidTorrentError: if the torrent_id does not exist in the session
|
---|
322 |
|
---|
323 | """
|
---|
324 | log.debug("Removing torrent %s from the core.", torrent_id)
|
---|
325 | return self.torrentmanager.remove(torrent_id, remove_data)
|
---|
326 |
|
---|
327 | @export
|
---|
328 | def get_session_status(self, keys):
|
---|
329 | """
|
---|
330 | Gets the session status values for 'keys', these keys are taking
|
---|
331 | from libtorrent's session status.
|
---|
332 |
|
---|
333 | See: http://www.rasterbar.com/products/libtorrent/manual.html#status
|
---|
334 |
|
---|
335 | :param keys: the keys for which we want values
|
---|
336 | :type keys: list
|
---|
337 | :returns: a dictionary of {key: value, ...}
|
---|
338 | :rtype: dict
|
---|
339 |
|
---|
340 | """
|
---|
341 | status = {}
|
---|
342 | session_status = self.session.status()
|
---|
343 | for key in keys:
|
---|
344 | status[key] = getattr(session_status, key)
|
---|
345 |
|
---|
346 | return status
|
---|
347 |
|
---|
348 | @export
|
---|
349 | def get_cache_status(self):
|
---|
350 | """
|
---|
351 | Returns a dictionary of the session's cache status.
|
---|
352 |
|
---|
353 | :returns: the cache status
|
---|
354 | :rtype: dict
|
---|
355 |
|
---|
356 | """
|
---|
357 |
|
---|
358 | status = self.session.get_cache_status()
|
---|
359 | cache = {}
|
---|
360 | for attr in dir(status):
|
---|
361 | if attr.startswith("_"):
|
---|
362 | continue
|
---|
363 | cache[attr] = getattr(status, attr)
|
---|
364 |
|
---|
365 | # Add in a couple ratios
|
---|
366 | try:
|
---|
367 | cache["write_hit_ratio"] = float((cache["blocks_written"] - cache["writes"])) / float(cache["blocks_written"])
|
---|
368 | except ZeroDivisionError:
|
---|
369 | cache["write_hit_ratio"] = 0.0
|
---|
370 |
|
---|
371 | try:
|
---|
372 | cache["read_hit_ratio"] = float(cache["blocks_read_hit"]) / float(cache["blocks_read"])
|
---|
373 | except ZeroDivisionError:
|
---|
374 | cache["read_hit_ratio"] = 0.0
|
---|
375 |
|
---|
376 | return cache
|
---|
377 |
|
---|
378 | @export
|
---|
379 | def force_reannounce(self, torrent_ids):
|
---|
380 | log.debug("Forcing reannouncment to: %s", torrent_ids)
|
---|
381 | for torrent_id in torrent_ids:
|
---|
382 | self.torrentmanager[torrent_id].force_reannounce()
|
---|
383 |
|
---|
384 | @export
|
---|
385 | def pause_torrent(self, torrent_ids):
|
---|
386 | log.debug("Pausing: %s", torrent_ids)
|
---|
387 | for torrent_id in torrent_ids:
|
---|
388 | if not self.torrentmanager[torrent_id].pause():
|
---|
389 | log.warning("Error pausing torrent %s", torrent_id)
|
---|
390 |
|
---|
391 | @export
|
---|
392 | def connect_peer(self, torrent_id, ip, port):
|
---|
393 | log.debug("adding peer %s to %s", ip, torrent_id)
|
---|
394 | if not self.torrentmanager[torrent_id].connect_peer(ip, port):
|
---|
395 | log.warning("Error adding peer %s:%s to %s", ip, port, torrent_id)
|
---|
396 |
|
---|
397 | @export
|
---|
398 | def move_storage(self, torrent_ids, dest):
|
---|
399 | log.debug("Moving storage %s to %s", torrent_ids, dest)
|
---|
400 | for torrent_id in torrent_ids:
|
---|
401 | if not self.torrentmanager[torrent_id].move_storage(dest):
|
---|
402 | log.warning("Error moving torrent %s to %s", torrent_id, dest)
|
---|
403 |
|
---|
404 | @export
|
---|
405 | def pause_all_torrents(self):
|
---|
406 | """Pause all torrents in the session"""
|
---|
407 | for torrent in self.torrentmanager.torrents.values():
|
---|
408 | torrent.pause()
|
---|
409 |
|
---|
410 | @export
|
---|
411 | def resume_all_torrents(self):
|
---|
412 | """Resume all torrents in the session"""
|
---|
413 | for torrent in self.torrentmanager.torrents.values():
|
---|
414 | torrent.resume()
|
---|
415 | component.get("EventManager").emit(SessionResumedEvent())
|
---|
416 |
|
---|
417 | @export
|
---|
418 | def resume_torrent(self, torrent_ids):
|
---|
419 | log.debug("Resuming: %s", torrent_ids)
|
---|
420 | for torrent_id in torrent_ids:
|
---|
421 | self.torrentmanager[torrent_id].resume()
|
---|
422 |
|
---|
423 | def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False, all_keys=False):
|
---|
424 | try:
|
---|
425 | status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update, all_keys=all_keys)
|
---|
426 | except KeyError:
|
---|
427 | import traceback
|
---|
428 | traceback.print_exc()
|
---|
429 | # Torrent was probaly removed meanwhile
|
---|
430 | return {}
|
---|
431 |
|
---|
432 | # Ask the plugin manager to fill in the plugin keys
|
---|
433 | if len(plugin_keys) > 0:
|
---|
434 | status.update(self.pluginmanager.get_status(torrent_id, plugin_keys))
|
---|
435 | return status
|
---|
436 |
|
---|
437 | @export
|
---|
438 | def get_torrent_status(self, torrent_id, keys, diff=False):
|
---|
439 | torrent_keys, plugin_keys = self.torrentmanager.separate_keys(keys, [torrent_id])
|
---|
440 | return self.create_torrent_status(torrent_id, torrent_keys, plugin_keys, diff=diff, update=True, all_keys=not keys)
|
---|
441 |
|
---|
442 | @export
|
---|
443 | def get_torrents_status(self, filter_dict, keys, diff=False):
|
---|
444 | """
|
---|
445 | returns all torrents , optionally filtered by filter_dict.
|
---|
446 | """
|
---|
447 | torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict)
|
---|
448 | status_dict = {}.fromkeys(torrent_ids)
|
---|
449 | d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=diff)
|
---|
450 |
|
---|
451 | def add_plugin_fields(args):
|
---|
452 | status_dict, plugin_keys = args
|
---|
453 | # Ask the plugin manager to fill in the plugin keys
|
---|
454 | if len(plugin_keys) > 0:
|
---|
455 | for key in status_dict.keys():
|
---|
456 | status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys))
|
---|
457 | return status_dict
|
---|
458 | d.addCallback(add_plugin_fields)
|
---|
459 | return d
|
---|
460 |
|
---|
461 | @export
|
---|
462 | def get_filter_tree(self , show_zero_hits=True, hide_cat=None):
|
---|
463 | """
|
---|
464 | returns {field: [(value,count)] }
|
---|
465 | for use in sidebar(s)
|
---|
466 | """
|
---|
467 | return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat)
|
---|
468 |
|
---|
469 | @export
|
---|
470 | def get_session_state(self):
|
---|
471 | """Returns a list of torrent_ids in the session."""
|
---|
472 | # Get the torrent list from the TorrentManager
|
---|
473 | return self.torrentmanager.get_torrent_list()
|
---|
474 |
|
---|
475 | @export
|
---|
476 | def get_config(self):
|
---|
477 | """Get all the preferences as a dictionary"""
|
---|
478 | return self.config.config
|
---|
479 |
|
---|
480 | @export
|
---|
481 | def get_config_value(self, key):
|
---|
482 | """Get the config value for key"""
|
---|
483 | return self.config.get(key)
|
---|
484 |
|
---|
485 | @export
|
---|
486 | def get_config_values(self, keys):
|
---|
487 | """Get the config values for the entered keys"""
|
---|
488 | return dict((key, self.config.get(key)) for key in keys)
|
---|
489 |
|
---|
490 | @export
|
---|
491 | def set_config(self, config):
|
---|
492 | """Set the config with values from dictionary"""
|
---|
493 | # Load all the values into the configuration
|
---|
494 | for key in config.keys():
|
---|
495 | if isinstance(config[key], basestring):
|
---|
496 | config[key] = config[key].encode("utf8")
|
---|
497 | self.config[key] = config[key]
|
---|
498 |
|
---|
499 | @export
|
---|
500 | def get_listen_port(self):
|
---|
501 | """Returns the active listen port"""
|
---|
502 | return self.session.listen_port()
|
---|
503 |
|
---|
504 | @export
|
---|
505 | def get_num_connections(self):
|
---|
506 | """Returns the current number of connections"""
|
---|
507 | return self.session.num_connections()
|
---|
508 |
|
---|
509 | @export
|
---|
510 | def get_available_plugins(self):
|
---|
511 | """Returns a list of plugins available in the core"""
|
---|
512 | return self.pluginmanager.get_available_plugins()
|
---|
513 |
|
---|
514 | @export
|
---|
515 | def get_enabled_plugins(self):
|
---|
516 | """Returns a list of enabled plugins in the core"""
|
---|
517 | return self.pluginmanager.get_enabled_plugins()
|
---|
518 |
|
---|
519 | @export
|
---|
520 | def enable_plugin(self, plugin):
|
---|
521 | self.pluginmanager.enable_plugin(plugin)
|
---|
522 | return None
|
---|
523 |
|
---|
524 | @export
|
---|
525 | def disable_plugin(self, plugin):
|
---|
526 | self.pluginmanager.disable_plugin(plugin)
|
---|
527 | return None
|
---|
528 |
|
---|
529 | @export
|
---|
530 | def force_recheck(self, torrent_ids):
|
---|
531 | """Forces a data recheck on torrent_ids"""
|
---|
532 | for torrent_id in torrent_ids:
|
---|
533 | self.torrentmanager[torrent_id].force_recheck()
|
---|
534 |
|
---|
535 | @export
|
---|
536 | def set_torrent_options(self, torrent_ids, options):
|
---|
537 | """Sets the torrent options for torrent_ids"""
|
---|
538 | for torrent_id in torrent_ids:
|
---|
539 | self.torrentmanager[torrent_id].set_options(options)
|
---|
540 |
|
---|
541 | @export
|
---|
542 | def set_torrent_trackers(self, torrent_id, trackers):
|
---|
543 | """Sets a torrents tracker list. trackers will be [{"url", "tier"}]"""
|
---|
544 | return self.torrentmanager[torrent_id].set_trackers(trackers)
|
---|
545 |
|
---|
546 | @export
|
---|
547 | def set_torrent_max_connections(self, torrent_id, value):
|
---|
548 | """Sets a torrents max number of connections"""
|
---|
549 | return self.torrentmanager[torrent_id].set_max_connections(value)
|
---|
550 |
|
---|
551 | @export
|
---|
552 | def set_torrent_max_upload_slots(self, torrent_id, value):
|
---|
553 | """Sets a torrents max number of upload slots"""
|
---|
554 | return self.torrentmanager[torrent_id].set_max_upload_slots(value)
|
---|
555 |
|
---|
556 | @export
|
---|
557 | def set_torrent_max_upload_speed(self, torrent_id, value):
|
---|
558 | """Sets a torrents max upload speed"""
|
---|
559 | return self.torrentmanager[torrent_id].set_max_upload_speed(value)
|
---|
560 |
|
---|
561 | @export
|
---|
562 | def set_torrent_max_download_speed(self, torrent_id, value):
|
---|
563 | """Sets a torrents max download speed"""
|
---|
564 | return self.torrentmanager[torrent_id].set_max_download_speed(value)
|
---|
565 |
|
---|
566 | @export
|
---|
567 | def set_torrent_file_priorities(self, torrent_id, priorities):
|
---|
568 | """Sets a torrents file priorities"""
|
---|
569 | return self.torrentmanager[torrent_id].set_file_priorities(priorities)
|
---|
570 |
|
---|
571 | @export
|
---|
572 | def set_torrent_prioritize_first_last(self, torrent_id, value):
|
---|
573 | """Sets a higher priority to the first and last pieces"""
|
---|
574 | return self.torrentmanager[torrent_id].set_prioritize_first_last(value)
|
---|
575 |
|
---|
576 | @export
|
---|
577 | def set_torrent_sequential_download(self, torrent_id, value):
|
---|
578 | """Toggle sequencial pieces download"""
|
---|
579 | return self.torrentmanager[torrent_id].set_sequential_download(value)
|
---|
580 |
|
---|
581 | @export
|
---|
582 | def set_torrent_auto_managed(self, torrent_id, value):
|
---|
583 | """Sets the auto managed flag for queueing purposes"""
|
---|
584 | return self.torrentmanager[torrent_id].set_auto_managed(value)
|
---|
585 |
|
---|
586 | @export
|
---|
587 | def set_torrent_stop_at_ratio(self, torrent_id, value):
|
---|
588 | """Sets the torrent to stop at 'stop_ratio'"""
|
---|
589 | return self.torrentmanager[torrent_id].set_stop_at_ratio(value)
|
---|
590 |
|
---|
591 | @export
|
---|
592 | def set_torrent_stop_ratio(self, torrent_id, value):
|
---|
593 | """Sets the ratio when to stop a torrent if 'stop_at_ratio' is set"""
|
---|
594 | return self.torrentmanager[torrent_id].set_stop_ratio(value)
|
---|
595 |
|
---|
596 | @export
|
---|
597 | def set_torrent_remove_at_ratio(self, torrent_id, value):
|
---|
598 | """Sets the torrent to be removed at 'stop_ratio'"""
|
---|
599 | return self.torrentmanager[torrent_id].set_remove_at_ratio(value)
|
---|
600 |
|
---|
601 | @export
|
---|
602 | def set_torrent_move_completed(self, torrent_id, value):
|
---|
603 | """Sets the torrent to be moved when completed"""
|
---|
604 | return self.torrentmanager[torrent_id].set_move_completed(value)
|
---|
605 |
|
---|
606 | @export
|
---|
607 | def set_torrent_move_completed_path(self, torrent_id, value):
|
---|
608 | """Sets the path for the torrent to be moved when completed"""
|
---|
609 | return self.torrentmanager[torrent_id].set_move_completed_path(value)
|
---|
610 |
|
---|
611 | @export(AUTH_LEVEL_ADMIN)
|
---|
612 | def set_torrents_owner(self, torrent_ids, username):
|
---|
613 | """Set's the torrent owner.
|
---|
614 |
|
---|
615 | :param torrent_id: the torrent_id of the torrent to remove
|
---|
616 | :type torrent_id: string
|
---|
617 | :param username: the new owner username
|
---|
618 | :type username: string
|
---|
619 |
|
---|
620 | :raises DelugeError: if the username is not known
|
---|
621 | """
|
---|
622 | if not self.authmanager.has_account(username):
|
---|
623 | raise DelugeError("Username \"%s\" is not known." % username)
|
---|
624 | if isinstance(torrent_ids, basestring):
|
---|
625 | torrent_ids = [torrent_ids]
|
---|
626 | for torrent_id in torrent_ids:
|
---|
627 | self.torrentmanager[torrent_id].set_owner(username)
|
---|
628 | return None
|
---|
629 |
|
---|
630 | @export
|
---|
631 | def set_torrents_shared(self, torrent_ids, shared):
|
---|
632 | if isinstance(torrent_ids, basestring):
|
---|
633 | torrent_ids = [torrent_ids]
|
---|
634 | for torrent_id in torrent_ids:
|
---|
635 | self.torrentmanager[torrent_id].set_options({"shared": shared})
|
---|
636 |
|
---|
637 | @export
|
---|
638 | def get_path_size(self, path):
|
---|
639 | """Returns the size of the file or folder 'path' and -1 if the path is
|
---|
640 | unaccessible (non-existent or insufficient privs)"""
|
---|
641 | return deluge.common.get_path_size(path)
|
---|
642 |
|
---|
643 | @export
|
---|
644 | def create_torrent(self, path, tracker, piece_length, comment, target,
|
---|
645 | webseeds, private, created_by, trackers, add_to_session):
|
---|
646 |
|
---|
647 | log.debug("creating torrent..")
|
---|
648 | threading.Thread(target=self._create_torrent_thread,
|
---|
649 | args=(
|
---|
650 | path,
|
---|
651 | tracker,
|
---|
652 | piece_length,
|
---|
653 | comment,
|
---|
654 | target,
|
---|
655 | webseeds,
|
---|
656 | private,
|
---|
657 | created_by,
|
---|
658 | trackers,
|
---|
659 | add_to_session)).start()
|
---|
660 |
|
---|
661 | def _create_torrent_thread(self, path, tracker, piece_length, comment, target,
|
---|
662 | webseeds, private, created_by, trackers, add_to_session):
|
---|
663 | import deluge.metafile
|
---|
664 | deluge.metafile.make_meta_file(
|
---|
665 | path,
|
---|
666 | tracker,
|
---|
667 | piece_length,
|
---|
668 | comment=comment,
|
---|
669 | target=target,
|
---|
670 | webseeds=webseeds,
|
---|
671 | private=private,
|
---|
672 | created_by=created_by,
|
---|
673 | trackers=trackers)
|
---|
674 | log.debug("torrent created!")
|
---|
675 | if add_to_session:
|
---|
676 | options = {}
|
---|
677 | options["download_location"] = os.path.split(path)[0]
|
---|
678 | self.add_torrent_file(os.path.split(target)[1], open(target, "rb").read(), options)
|
---|
679 |
|
---|
680 | @export
|
---|
681 | def upload_plugin(self, filename, filedump):
|
---|
682 | """This method is used to upload new plugins to the daemon. It is used
|
---|
683 | when connecting to the daemon remotely and installing a new plugin on
|
---|
684 | the client side. 'plugin_data' is a xmlrpc.Binary object of the file data,
|
---|
685 | ie, plugin_file.read()"""
|
---|
686 |
|
---|
687 | try:
|
---|
688 | filedump = base64.decodestring(filedump)
|
---|
689 | except Exception, e:
|
---|
690 | log.error("There was an error decoding the filedump string!")
|
---|
691 | log.exception(e)
|
---|
692 | return
|
---|
693 |
|
---|
694 | f = open(os.path.join(deluge.configmanager.get_config_dir(), "plugins", filename), "wb")
|
---|
695 | f.write(filedump)
|
---|
696 | f.close()
|
---|
697 | component.get("CorePluginManager").scan_for_plugins()
|
---|
698 |
|
---|
699 | @export
|
---|
700 | def rescan_plugins(self):
|
---|
701 | """
|
---|
702 | Rescans the plugin folders for new plugins
|
---|
703 | """
|
---|
704 | component.get("CorePluginManager").scan_for_plugins()
|
---|
705 |
|
---|
706 | @export
|
---|
707 | def rename_files(self, torrent_id, filenames):
|
---|
708 | """
|
---|
709 | Rename files in torrent_id. Since this is an asynchronous operation by
|
---|
710 | libtorrent, watch for the TorrentFileRenamedEvent to know when the
|
---|
711 | files have been renamed.
|
---|
712 |
|
---|
713 | :param torrent_id: the torrent_id to rename files
|
---|
714 | :type torrent_id: string
|
---|
715 | :param filenames: a list of index, filename pairs
|
---|
716 | :type filenames: ((index, filename), ...)
|
---|
717 |
|
---|
718 | :raises InvalidTorrentError: if torrent_id is invalid
|
---|
719 |
|
---|
720 | """
|
---|
721 | if torrent_id not in self.torrentmanager.torrents:
|
---|
722 | raise InvalidTorrentError("torrent_id is not in session")
|
---|
723 |
|
---|
724 | self.torrentmanager[torrent_id].rename_files(filenames)
|
---|
725 |
|
---|
726 | @export
|
---|
727 | def rename_folder(self, torrent_id, folder, new_folder):
|
---|
728 | """
|
---|
729 | Renames the 'folder' to 'new_folder' in 'torrent_id'. Watch for the
|
---|
730 | TorrentFolderRenamedEvent which is emitted when the folder has been
|
---|
731 | renamed successfully.
|
---|
732 |
|
---|
733 | :param torrent_id: the torrent to rename folder in
|
---|
734 | :type torrent_id: string
|
---|
735 | :param folder: the folder to rename
|
---|
736 | :type folder: string
|
---|
737 | :param new_folder: the new folder name
|
---|
738 | :type new_folder: string
|
---|
739 |
|
---|
740 | :raises InvalidTorrentError: if the torrent_id is invalid
|
---|
741 |
|
---|
742 | """
|
---|
743 | if torrent_id not in self.torrentmanager.torrents:
|
---|
744 | raise InvalidTorrentError("torrent_id is not in session")
|
---|
745 |
|
---|
746 | self.torrentmanager[torrent_id].rename_folder(folder, new_folder)
|
---|
747 |
|
---|
748 | @export
|
---|
749 | def queue_top(self, torrent_ids):
|
---|
750 | log.debug("Attempting to queue %s to top", torrent_ids)
|
---|
751 | # torrent_ids must be sorted in reverse before moving to preserve order
|
---|
752 | for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True):
|
---|
753 | try:
|
---|
754 | # If the queue method returns True, then we should emit a signal
|
---|
755 | if self.torrentmanager.queue_top(torrent_id):
|
---|
756 | component.get("EventManager").emit(TorrentQueueChangedEvent())
|
---|
757 | except KeyError:
|
---|
758 | log.warning("torrent_id: %s does not exist in the queue", torrent_id)
|
---|
759 |
|
---|
760 | @export
|
---|
761 | def queue_up(self, torrent_ids):
|
---|
762 | log.debug("Attempting to queue %s to up", torrent_ids)
|
---|
763 | torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
|
---|
764 | torrent_moved = True
|
---|
765 | prev_queue_position = None
|
---|
766 | #torrent_ids must be sorted before moving.
|
---|
767 | for queue_position, torrent_id in sorted(torrents):
|
---|
768 | # Move the torrent if and only if there is space (by not moving it we preserve the order)
|
---|
769 | if torrent_moved or queue_position - prev_queue_position > 1:
|
---|
770 | try:
|
---|
771 | torrent_moved = self.torrentmanager.queue_up(torrent_id)
|
---|
772 | except KeyError:
|
---|
773 | log.warning("torrent_id: %s does not exist in the queue", torrent_id)
|
---|
774 | # If the torrent moved, then we should emit a signal
|
---|
775 | if torrent_moved:
|
---|
776 | component.get("EventManager").emit(TorrentQueueChangedEvent())
|
---|
777 | else:
|
---|
778 | prev_queue_position = queue_position
|
---|
779 |
|
---|
780 | @export
|
---|
781 | def queue_down(self, torrent_ids):
|
---|
782 | log.debug("Attempting to queue %s to down", torrent_ids)
|
---|
783 | torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
|
---|
784 | torrent_moved = True
|
---|
785 | prev_queue_position = None
|
---|
786 | #torrent_ids must be sorted before moving.
|
---|
787 | for queue_position, torrent_id in sorted(torrents, reverse=True):
|
---|
788 | # Move the torrent if and only if there is space (by not moving it we preserve the order)
|
---|
789 | if torrent_moved or prev_queue_position - queue_position > 1:
|
---|
790 | try:
|
---|
791 | torrent_moved = self.torrentmanager.queue_down(torrent_id)
|
---|
792 | except KeyError:
|
---|
793 | log.warning("torrent_id: %s does not exist in the queue", torrent_id)
|
---|
794 | # If the torrent moved, then we should emit a signal
|
---|
795 | if torrent_moved:
|
---|
796 | component.get("EventManager").emit(TorrentQueueChangedEvent())
|
---|
797 | else:
|
---|
798 | prev_queue_position = queue_position
|
---|
799 |
|
---|
800 | @export
|
---|
801 | def queue_bottom(self, torrent_ids):
|
---|
802 | log.debug("Attempting to queue %s to bottom", torrent_ids)
|
---|
803 | # torrent_ids must be sorted before moving to preserve order
|
---|
804 | for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position):
|
---|
805 | try:
|
---|
806 | # If the queue method returns True, then we should emit a signal
|
---|
807 | if self.torrentmanager.queue_bottom(torrent_id):
|
---|
808 | component.get("EventManager").emit(TorrentQueueChangedEvent())
|
---|
809 | except KeyError:
|
---|
810 | log.warning("torrent_id: %s does not exist in the queue", torrent_id)
|
---|
811 |
|
---|
812 | @export
|
---|
813 | def glob(self, path):
|
---|
814 | return glob.glob(path)
|
---|
815 |
|
---|
816 | @export
|
---|
817 | def test_listen_port(self):
|
---|
818 | """
|
---|
819 | Checks if the active port is open
|
---|
820 |
|
---|
821 | :returns: True if the port is open, False if not
|
---|
822 | :rtype: bool
|
---|
823 |
|
---|
824 | """
|
---|
825 | from twisted.web.client import getPage
|
---|
826 |
|
---|
827 | d = getPage("http://deluge-torrent.org/test_port.php?port=%s" %
|
---|
828 | self.get_listen_port(), timeout=30)
|
---|
829 |
|
---|
830 | def on_get_page(result):
|
---|
831 | return bool(int(result))
|
---|
832 |
|
---|
833 | def logError(failure):
|
---|
834 | log.warning("Error testing listen port: %s", failure)
|
---|
835 |
|
---|
836 | d.addCallback(on_get_page)
|
---|
837 | d.addErrback(logError)
|
---|
838 |
|
---|
839 | return d
|
---|
840 |
|
---|
841 | @export
|
---|
842 | def get_free_space(self, path=None):
|
---|
843 | """
|
---|
844 | Returns the number of free bytes at path
|
---|
845 |
|
---|
846 | :param path: the path to check free space at, if None, use the default
|
---|
847 | download location
|
---|
848 | :type path: string
|
---|
849 |
|
---|
850 | :returns: the number of free bytes at path
|
---|
851 | :rtype: int
|
---|
852 |
|
---|
853 | :raises InvalidPathError: if the path is invalid
|
---|
854 |
|
---|
855 | """
|
---|
856 | if not path:
|
---|
857 | path = self.config["download_location"]
|
---|
858 | try:
|
---|
859 | return deluge.common.free_space(path)
|
---|
860 | except InvalidPathError:
|
---|
861 | return 0
|
---|
862 |
|
---|
863 | @export
|
---|
864 | def get_libtorrent_version(self):
|
---|
865 | """
|
---|
866 | Returns the libtorrent version.
|
---|
867 |
|
---|
868 | :returns: the version
|
---|
869 | :rtype: string
|
---|
870 |
|
---|
871 | """
|
---|
872 | return lt.version
|
---|
873 |
|
---|
874 | @export(AUTH_LEVEL_ADMIN)
|
---|
875 | def get_known_accounts(self):
|
---|
876 | return self.authmanager.get_known_accounts()
|
---|
877 |
|
---|
878 | @export(AUTH_LEVEL_NONE)
|
---|
879 | def get_auth_levels_mappings(self):
|
---|
880 | return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE)
|
---|
881 |
|
---|
882 | @export(AUTH_LEVEL_ADMIN)
|
---|
883 | def create_account(self, username, password, authlevel):
|
---|
884 | return self.authmanager.create_account(username, password, authlevel)
|
---|
885 |
|
---|
886 | @export(AUTH_LEVEL_ADMIN)
|
---|
887 | def update_account(self, username, password, authlevel):
|
---|
888 | return self.authmanager.update_account(username, password, authlevel)
|
---|
889 |
|
---|
890 | @export(AUTH_LEVEL_ADMIN)
|
---|
891 | def remove_account(self, username):
|
---|
892 | return self.authmanager.remove_account(username)
|
---|