Compare commits

...

110 Commits

Author SHA1 Message Date
2263463114 Bump version to 1.3.13 and update dates 2016-07-20 15:23:28 +01:00
454c7be364 Revert "[#2852] Set maximum supported libtorrent version to 1.0.x"
This reverts commit 852b51f224.

Changes applied for libtorrent 1.1.1 release should bring back
backward compatibility for Deluge 1.3.
2016-07-19 20:06:27 +01:00
85fdacc0e7 [Changelog] Update with recent changes 2016-07-19 17:38:15 +01:00
869dbab459 [WebUI] Compress javascript files 2016-07-19 17:37:43 +01:00
852b51f224 [#2852] Set maximum supported libtorrent version to 1.0.x 2016-07-19 15:20:38 +01:00
492ad07965 [#2293] [WebUI] Fix plugins not loading when using WebUI plugin
- Any plugins that were started before the WebUI plugin would not be loaded
   upon starting the web server and would be not show up. The fix is to use
   web.pluginmanager.start to get all enabled plugins from core.
 - Update log message output for enable/disable in pluginmanager.
 - Deregister plugin events on json_api disable.
2016-07-19 15:04:50 +01:00
904a51835b [#2857] [Notification] Fix issues with SMTP port input 2016-07-19 12:56:33 +01:00
d38b8fc45c [#2855] [WebUI] Unable to add UDP trackers 2016-07-19 11:50:26 +01:00
5f92810f76 [#2784] [Execute] Escape ampersand in args for Windows
Due to the nature of passing a command and args to cmd.exe and then
to a batch file in Windows any ampersands in execute args need to be
double-escaped so prefixing with tripe-caret (^^^&) is the fix for this.
2016-06-29 23:25:30 +01:00
34e12fcb38 [Translations] Update po's, pot and gettext.js 2016-06-19 12:30:30 +01:00
f769afd3ac [WebUI] Compress javascript 2016-06-19 12:30:29 +01:00
e1d78c3de6 [Changelog] Add recent changes 2016-06-19 12:30:05 +01:00
15a4023208 [#2077] [Extractor] Ignore the remaining rar part files
* Bump version to 0.6
2016-06-10 16:14:52 +01:00
cbb7415a18 [#2785] [Extractor] Fix successful claimed extract leaving empty folder
* The main fix here is adding os.environ to the command call otherwise in some configurations
   the extraction would fail. Was unable to reproduce locally but users confirm this fix works.
 * Refactored the code to properly report errors if the extract command fails and the
   actual command output.
 * Bump version to 0.5.
2016-06-10 16:00:23 +01:00
1a11e085b2 [#2828] [Packaging] Fix ImportError with setuptools version > 18.8 2016-05-19 17:23:07 +01:00
fcb65940d9 [AutoAdd] Fix watch dir not accepting uppercase file extension
- Auto-add feature will now accept torrents when the .torrent extension
   has capital letters in it
2016-05-12 19:19:39 +01:00
aa10e084a4 [Scheduler] Bump to version 0.3 2016-05-12 11:41:00 +01:00
b2be4aba53 [#2796] [Console] Add time_added to info sort keys 2016-05-10 14:17:37 +01:00
a1e66a4dc1 [#2815] [Console] Fix 'add' cmd path inconsistency on windows
- When adding a torrent with a download location from command prompt
on Windows the slashes were not being normalised resulting in path errors.
2016-05-10 13:00:02 +01:00
6240243251 [#2795] [GTKUI] Reduce height of Add Torrent Dialog
- Reduced height from 575px to 480px
 - Low resolution screen users (600px high) will be unable to click
the add button with a dialog height of >550px. Keeping the height
to less than 500px leaves more room for large size themes.
2016-05-10 12:45:25 +01:00
ad58fca1f9 [#2790] Ensure base32 magnet hash is uppercase 2016-05-09 23:24:59 +01:00
f221ae53eb [#2832] [UI] Skip blank lines in auth file 2016-05-09 16:40:02 +01:00
5590c31ace [Daemon] Fix unable to use uppercase log level 2016-04-27 08:20:58 +01:00
4e5754b285 [Core] Fix UnboundLocalError in torrentmanager 2016-04-07 11:00:16 +01:00
90a22af5e5 Add command-line option for the daemon to restrict some config keys to being read-only.
This only affects the core.set_config() RPC method which will drop items if the key
is listed as read-only.
2016-02-02 19:02:28 -08:00
77f8449c0c [#2767] [Packaging] Don't include .py files in OSX App 2015-12-11 18:50:49 +00:00
be7ad16a3f [#2783] [GTKUI] Case insensitive sort for name column 2015-12-11 18:02:10 +00:00
e28954f63e Update Changelog 2015-12-11 14:27:11 +00:00
52e60ac5b0 [#2782] [WebUI] Fix HTTPS negotiating incorrect cipher 2015-12-11 11:53:52 +00:00
6ffe5cd2a4 [Core] Improve logging in update_state 2015-12-11 11:52:59 +00:00
9038357d78 [OSX] Fix starting deluged from connection manager 2015-12-10 21:31:37 +00:00
d56f6cb4f1 [Core] Increase RSA key size 2015-12-10 09:47:41 +00:00
5d301a4b33 [Core] Fix move_storage exception handling 2015-12-09 19:02:18 +00:00
e65a7ff2ea [GTKUI] Only save_state when mainwindow is visible
* A similar fix (550ddc01) was applied to develop so backporting to guard
   against similar problems with columns not saving properly.
2015-11-30 23:41:51 +00:00
1bdc99ded7 [GTKUI] Fix installing plugin from non-ascii path 2015-11-27 13:41:06 +00:00
dd34492e16 [Core] Update tracker_host when setting new tracker status
* Fixes the tracker_host not updating when a tracker announce
   is received from a different tracker and sets the tracker status.
2015-11-27 12:03:55 +00:00
9f3b2f3167 [#2093] Backport win32_unicode_argv from develop
* Also includes fix for drag'n'drop non-ascii filepaths by decoding after urlparse.
2015-11-26 13:46:04 +00:00
0260e34189 [#2485] [WebUI] Fix unconnected Options in context menu 2015-11-23 23:20:45 +00:00
5464cf674a [#2777] Update MSVC SP1 check to latest release CLID 2015-11-15 18:43:04 +00:00
a58ce30e7b [#2738] [Core] Fix illegal argument with torrent_handle.set_max_connections 2015-11-15 14:17:00 +00:00
83cecc0c09 [Core] Put back translation markup for tracker error 2015-11-05 22:59:17 +00:00
00757af149 [Core] Empty error message fix with certain trackers
By design alert.msg will be empty if the error code is '-1' so use
a.e.message() to get the message as fallback. It was not used at
replacement because when error code is not '-1' then a.e.message()
will also include the error code, which we do not want.
2015-11-05 22:23:35 +00:00
639eefcf1d [Core] Supress warnings with fresh config
* Test TMState has torrents before attempting old state update.
 * Only warn about missing fastresume if torrents in session.
2015-10-28 15:35:36 +00:00
69a1f5f210 [GTKUI] Don't display percentage for Error'd torrents 2015-10-28 15:35:36 +00:00
0a74812eeb [Console] Fix adding non-ascii torrent in non-interactive mode 2015-10-28 15:35:35 +00:00
cf437b6a33 [Core] Add handle.clear_error to resume 2015-10-28 15:35:35 +00:00
0ab7ebd017 [#1032] [Core] Force a torrent error if resume data is rejected
* Add two new methods, force_error_state and clear_forced_error_state.
 * Force error state upon rejected resume data.
 * Keep original resume data in forced_error state.
2015-10-28 15:35:35 +00:00
34e92b9f12 [Core] Add fastresume rejected alert handler 2015-10-20 15:32:15 +01:00
86b1b75fb8 [#2772] [GTKUI] Fix GtkWarning with unknown pango markup 2015-10-18 18:47:32 +01:00
4b9dcf377c [Core] Fix Twisted AlreadyCalled error on shutdown 2015-10-07 00:18:40 +01:00
560318a5a7 [#2703] [Core] Stop moving files if target files exist 2015-09-29 23:18:04 +01:00
244ae878c9 [Core] Fix placement of self.state in torrent.py
* Need to be created earlier as set_options calls update_state
2015-09-29 23:17:46 +01:00
f9b7892976 [Core] Reset trackers in force_recheck only if paused 2015-09-29 19:05:50 +01:00
5f5b6fad0b Fix indention error in move_storage 2015-09-29 18:51:52 +01:00
5c545c5e0b [Core] Fix torrent displaying wrong state 2015-09-29 18:44:52 +01:00
20088a5c70 [Core] Workaround unwanted tracker announce when force rechecking paused torrent
* This workaround updates the stored torrent.trackers, sets empty handle.trackers then
   resets trackers after pausing.
2015-09-29 18:43:11 +01:00
099a4eb8c6 [#2753] [GTKUI] Fix 'Added' column showing wrong date
* Unsure why added_time would be zero but only set the date if it is a postive value.
2015-09-28 13:17:13 +01:00
ad7e519fb2 [Core] Minor correction to session resume 2015-09-28 12:37:15 +01:00
df57c7f924 [#2729] [Blocklist] Fix plugin lockup with empty url 2015-09-28 11:56:32 +01:00
7315255831 [#1330] [Core] Fix pausing and resuming session
* The paused state of torrents is now correctly stored on shutdown if the session is paused.
 * core.pause_all_torrents now uses libtorrent session.pause and resume_all_torrents also refreshes
   all torrents' state. This fixes only torrents that changed state being updated so queued torrents
   would be incorrectly displayed as paused.
 * Scheduler and Blocklist now use updated core methods rather than calling libtorrent directly.
2015-09-28 11:53:27 +01:00
eab7850ed6 [Core] Return all plugin status keys with empty list 2015-09-28 11:30:33 +01:00
542e028977 [#2236] [Core] Fix filter keyerror removing plugin 2015-09-26 12:58:52 +01:00
f131194b75 [GTKUI] [OSX] Fix empty scrolling status (systray) menu
* Same issue as seen on Windows in #302
2015-09-25 23:58:32 +01:00
d7e6afb01e [#2435] [GTKUI] Prevent user changing selection when editing tracker 2015-09-25 17:45:59 +01:00
e1dcf378c3 [#2705] [WebUI] Fix hostlist not being created 2015-09-25 13:56:39 +01:00
697c22a46c [#2765] Add support for TLS SNI in httpdownloader 2015-09-25 13:56:39 +01:00
7ca704be72 [GTKUI] Fix connected issue in connection manager
* If host was not an ip address then it would not show as connected
2015-09-25 13:56:00 +01:00
72d381a3b6 Fix data_files check in setup.py 2015-09-20 18:41:24 +01:00
59c2520e0d [Packaging] Revert unintended changes to osx scripts 2015-09-20 15:48:03 +01:00
58d385241f [#2762] [GTKUI] Use correct column types for data 2015-09-20 15:39:04 +01:00
58059300bd [#2763] [GTKUI] Fix unhandled error with invalid magnet uri 2015-09-20 15:19:57 +01:00
e4f2a450d6 [#2764] [Scheduler] Fix corrupt plugin prefs page on osx 2015-09-20 14:59:33 +01:00
64bba77807 [Packaging] Minor osx updates 2015-09-20 01:50:05 +01:00
a13b4270b5 [Packaging] Updates to the NSIS Installer script
* New message box popup if VC 2008 Redist package not detected.
 * Add Start Menu page to choose where/if to install items.
 * Add desktop shortcut install option to finish page.
 * Clean up spacing and use consistent 4 spaces to indent.
 * Exclude as many unneeded pygame libraries as possible.
2015-09-18 22:47:06 +01:00
52c8fde461 [Packaging] Updates to osx scripts
* bundle_contents now appends 'Contents' without adding it twice.
 * Remove reference to non-existent gdk-pixbuf.loaders
 * Separate libtorrent in new module.
 * Update lib versions for bundle file.
2015-09-18 22:47:05 +01:00
0a01aa28b0 [#2402] [Notification] Fix popup to show actual count of files finished 2015-09-18 22:44:58 +01:00
bfb202086d [#2754] [GTKUI] Fix Deluge isn't sorting torrents properly 2015-09-17 22:26:24 +01:00
6032c25813 [#2696] [GTKUI] Fix incorrect destination folder shown in GTK UI 2015-09-17 11:28:56 +02:00
6cbb2fa5e1 [GTKUI] Remove deprecated 'has_separator' from glade files
* Deprecated since GTK 2.22 and defaults to False.
2015-09-16 15:31:46 +01:00
cdf301601f [Scheduler] Revert erroneous fix backported from develop branch
* The issue this was intended to fix only occurs on develop branch
2015-09-16 15:20:03 +01:00
1b974d1061 [Win32] Fix icon path and output exes in bbfreeze 2015-09-13 22:47:31 +01:00
602a913fa3 Bump version to 1.3.12 2015-09-13 21:32:11 +01:00
6a8f24e973 Fix icon paths in setup 2015-09-13 21:32:10 +01:00
fde46885e9 Update Translation files 2015-09-12 11:56:27 +01:00
7223a51ba5 [WebUI] Lint and minify 2015-09-12 11:35:50 +01:00
8ac65d77e0 Update ChangeLog 2015-09-10 15:01:52 +01:00
65ebcf5384 [#2325] [Packaging] Fix uninstaller deleting non-deluge files 2015-09-10 14:43:32 +01:00
53caeb4565 [Packaging] bbfreeze updates
* No need for data_files to be installed on windows
2015-09-10 14:39:25 +01:00
3b1cb0f58e [Scheduler] Show current speed limit in statusbar
* Intercepts the updates of the statusbar and displays plugin values when in Yellow zone.
 * Core fix for resetting speed limits to core.conf values.
2015-09-07 11:32:09 +01:00
41ac46c7fe [Core] Backport atomic fastresume and state file saving fixes
* On Windows using shutil.move is not atomic and could account for corruption on power loss.
 * Using file saving code from develop branch including latest changes:
	7414737cbf
2015-09-07 11:25:31 +01:00
8e3d737adc [#2731] [GTKUI] Fix potential AttributeError in is_on_active_workspace
* Without being able to replicate adding the forced updated is the likely fix for 'win'
being None but also add test in case it's not...
2015-09-01 16:27:57 +01:00
7ef9e3dbe0 Check for private flag on duplicate added torrent 2015-08-31 00:47:19 +01:00
78fcf1781a [#2333] [Console] Fix 'set and then get' in config command
* The get method was returning old config information so use correct
 core get callback.
 * Remove redundant deferred in set method
2015-08-28 17:19:40 +01:00
2b08ed06af [Core] Enable lt extension bindings again for versions >=0.16.7
* This will also no longer enable the lt_trackers extensions that seems
to be an issue for private trackers mixing with public ones #2721.
2015-08-28 15:34:56 +01:00
0cdab04a64 [Packaging] bbfreeze tweaks and comments
* Reduce output from bbfreeze and add debug option to enable again.
2015-08-26 17:30:00 +01:00
84aca3c009 [Packaging] Fix typo in bbfreeze 2015-08-26 17:27:28 +01:00
9662ccf486 Use just Taiwan in countries list 2015-08-25 16:30:25 +01:00
83719e8404 [Win32] Updated bbreeze script from develop branch 2015-08-24 15:56:51 +01:00
04d90903a6 [#2758] [win32] Include _cffi_backend module in bbfreeze 2015-08-24 15:42:44 +01:00
f599b883cf [win32] Update packaging scripts
* Update directory paths.
2015-08-24 15:40:55 +01:00
bef71e60b3 [#2734] Add 256x256 to deluge.ico 2015-08-24 15:37:16 +01:00
acf4fc4193 [#2233] [lp:#1487704] Fix AttributeError in set_trackers with lt 1.0 2015-08-22 15:31:26 +01:00
123dd8f011 [WebUI] Fix i18n issue in Connection Manager
The status strings were incorrectly marked for translation which when combined with
some translations using 'connected' and 'online' as the same word resulted in
users being unabe to connect to running daemon.

 * Removed translation markup from json_api but left as original capitalised word in
case other third-party scripts do comparison on these status strings.
 * Added translation markup prior to displaying ConnectionManager using template.
 * Reworded password prompt and added translation markup.
 * Update gettext.js
2015-08-20 13:48:34 +01:00
0516e3df45 Update author name as per request 2015-08-17 23:05:34 +01:00
0c750084dc [#2295] [WebUI] Increased lifespan of display settings
Display settings for the WebUI are persisted using cookies created by
Ex.state.CookieProvider. When no expiration date is provided, a default
value of (now + 7 days) is used. This causes display settings to be
lost frequently.

This fix adds an 'expires' parameter with a value of (now + 10 years).
This change does not affect the lifespan of the session cookie, which
is created by a separate system.
2015-08-14 16:51:38 +01:00
907109b8bc Update man pages 2015-08-14 13:22:23 +01:00
630aa730d5 [#2730] Fix Deluge dev versions not starting
Change to use the VersionSplit class and fix code there.
2015-08-14 00:20:02 +01:00
16faa26124 [GTKUI] Improve About dialog copyright format for translators 2015-08-13 23:03:26 +01:00
ebabd20c98 Remove stray tab in label plugin text 2015-08-09 12:17:47 +01:00
d40dfcd53c Fix for Twisted 15.0 URI class rename 2015-02-23 12:39:40 +00:00
158 changed files with 22739 additions and 20060 deletions

View File

@ -14,7 +14,7 @@ libtorrent (http://www.libtorrent.org):
Contributors (and Past Developers):
* Zach Tibbitts <zach@collegegeek.org>
* Alon Zakai ('Kripken') <kripkensteiner@gmail.com>
* Marcos Pinto ('markybob') <markybob@gmail.com>
* Marcos Mobley ('markybob') <markybob@gmail.com>
* Alex Dedul
* Sadrul Habib Chowdhury
* Ido Abramovich <ido.deluge@gmail.com>
@ -457,7 +457,7 @@ Translation Contributors:
Marco Rodrigues
Marcos
Marcos Escalier
Marcos Pinto
Marcos Mobley
Marcus Ekstrom
Marek Dębowski
Mário Buči

View File

@ -1,3 +1,101 @@
=== Deluge 1.3.13 (20 July 2016) ===
==== Core ====
* Increase RSA key size from 1024 to 2048 and use SHA256 digest.
* Fixed empty error message from certain trackers.
* Fixed torrent ending up displaying the wrong state.
* #1032: Force a torrent into Error state if the resume data is rejected.
* Workaround unwanted tracker announce when force rechecking paused torrent.
* #2703: Stop moving torrent files if target files exist to prevent unintended clobbering of data.
* #1330: Fixed the pausing and resuming of the Deluge session so torrents return to previous state.
* #2765: Add support for TLS SNI in httpdownloader.
* #2790: Ensure base32 magnet hash is uppercase to fix lowercase magnets uris.
==== Daemon ====
* New command-line option to restict selected config key to read-only.
* Allow use of uppercase log level to match UIs.
==== UI ====
* #2832: Fixed error with blank lines in auth file.
==== GtkUI ====
* Fixed installing plugin from a non-ascii directory.
* Error'd torrents no longer display a progress percentage.
* #2753: Fixed the 'Added' column showing the wrong date.
* #2435: Prevent the user from changing tracker selection when editing trackers.
* Fixed showing the wrong connected status with hostname in the Connection Manager.
* #2754: Fixed the progress column to sort by progress and state correctly.
* #2696: Fixed incorrect Move Completed folder shown in Options tab.
* #2783: Sorting for name column is now case insensitive.
* #2795: Reduce height of Add Torrent Dialog to help with smaller screeen resoltuions.
* OSX: Fixed empty scrolling status (systray) menu.
* OSX: Fixed starting deluged from connection manager.
* #2093: Windows OS: Fixed opening non-ascii torrent files.
* #2855: Fixed adding UDP trackers to trackers dialog.
==== WebUI ====
* #2782: Fixed HTTPS negotiating incorrect cipher.
* #2485: Fixed the broken Options context menu.
* #2705: Fixed the hostlist config file not being created.
* #2293: Fixed plugin's js code not loading when using the WebUI plugin.
==== Console ====
* Fixed adding non-ascii torrent in non-interactive mode.
* #2796: Add time_added to info sort keys.
* #2815: Fixed 'add' cmd path inconsistency on Windows.
==== OSX Packaging ====
* Source .py files no longer included in Deluge.app.
==== Windows OS Packaging ====
* #2777: Updated MSVC SP1 check to latest release CLID.
==== Blocklist Plugin ====
* #2729: Fixed plugin lockup with empty url.
==== Scheduler Plugin ====
* Fixed corrupt plugin prefences page on OSX.
* Fixed error accidentally introduced in 1.3.12.
==== Notification Plugin ====
* #2402: Fixed the popup to show the actual count of files finished.
* #2857: Fixed issue with SMTP port entry not updating in GTKUI.
==== AutoAdd Plugin ====
* Fixed watch dir not accepting uppercase file extension.
==== Extractor Plugin ====
* Ignore the remaining rar part files to prevent spawning useless processes.
* #2785: Fixed only an empty folder when extracting rar files.
==== Execute Plugin ====
* #2784: Windows OS: Escape ampersand in torrent args.
=== Deluge 1.3.12 (13 September 2015) ===
==== GtkUI ====
* #2731: Fix potential AttributeError in is_on_active_workspace
==== Core ====
* Include fix for Twisted 15.0 URI class rename
* #2233: Fix AttributeError in set_trackers with lt 1.0
* Enable lt extension bindings again for versions >=0.16.7 (this disables Tracker Exchange by default)
* Backport atomic fastresume and state file saving fixes as another attempt to prevent data loss on unclean exits
==== WebUI ====
* Fixed i18n issue in Connection Manager which left users unable to connect
* #2295: Increase cookie lifespan for display settings
==== Console ====
* #2333: Fixed 'set and then get' in config command
==== Scheduler Plugin ====
* Show current speed limit in statusbar
==== Win32 Packaging ====
* #2736: Added version info to the properties of Deluge exes
* #2734: Added a 256x256 to deluge.ico
* #2325: Fixed the uninstaller deleting non-deluge files
* Include pillow module to enable resizing of tracker icons
=== Deluge 1.3.11 (30 November 2014) ===
==== GtkUI ====
* Fixed ImportError for users with Twisted < 10

View File

@ -695,7 +695,7 @@ class VersionSplit(object):
def __init__(self, ver):
ver = ver.lower()
vs = ver.replace("_", "-").split("-")
self.version = [int(x) for x in vs[0].split(".")]
self.version = [int(x) for x in vs[0].split(".") if x.isdigit()]
self.suffix = None
self.dev = False
if len(vs) > 1:
@ -718,3 +718,27 @@ class VersionSplit(object):
v1 = [self.version, self.suffix or 'z', self.dev]
v2 = [ver.version, ver.suffix or 'z', ver.dev]
return cmp(v1, v2)
def win32_unicode_argv():
""" Gets sys.argv as list of unicode objects on any platform."""
if windows_check():
# Versions 2.x of Python don't support Unicode in sys.argv on Windows, with the
# underlying Windows API instead replacing multi-byte characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
get_cmd_linew = cdll.kernel32.GetCommandLineW
get_cmd_linew.argtypes = []
get_cmd_linew.restype = LPCWSTR
cmdline_to_argvw = windll.shell32.CommandLineToArgvW
cmdline_to_argvw.argtypes = [LPCWSTR, POINTER(c_int)]
cmdline_to_argvw.restype = POINTER(LPWSTR)
cmd = get_cmd_linew()
argc = c_int(0)
argv = cmdline_to_argvw(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in xrange(start, argc.value)]

View File

@ -74,7 +74,8 @@ class AlertManager(component.Component):
def stop(self):
for dc in self.delayed_calls:
dc.cancel()
if dc.active():
dc.cancel()
self.delayed_calls = []
def register_handler(self, alert_type, handler):

View File

@ -72,22 +72,30 @@ from deluge.core.eventmanager import EventManager
from deluge.core.rpcserver import export
class Core(component.Component):
def __init__(self, listen_interface=None):
def __init__(self, listen_interface=None, read_only_config_keys=None):
log.debug("Core init..")
component.Component.__init__(self, "Core")
# These keys will be dropped from the set_config() RPC and are
# configurable from the command-line.
self.read_only_config_keys = read_only_config_keys
log.debug("read_only_config_keys: %s", read_only_config_keys)
# Start the libtorrent session
log.info("Starting libtorrent %s session..", lt.version)
# Create the client fingerprint
version = [int(value.split("-")[0]) for value in deluge.common.get_version().split(".")]
version = deluge.common.VersionSplit(deluge.common.get_version()).version
while len(version) < 4:
version.append(0)
# Note: All libtorrent python bindings to set plugins/extensions need to be disabled
# due to GIL issue. https://code.google.com/p/libtorrent/issues/detail?id=369
# Setting session flags to 1 enables all libtorrent default plugins
self.session = lt.session(lt.fingerprint("DE", *version), flags=1)
# In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue.
# https://code.google.com/p/libtorrent/issues/detail?id=369
if deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.7.0"):
self.session = lt.session(lt.fingerprint("DE", *version), flags=0)
else:
# Setting session flags to 1 enables all libtorrent default plugins
self.session = lt.session(lt.fingerprint("DE", *version), flags=1)
# Load the session state if available
self.__load_session_state()
@ -108,11 +116,12 @@ class Core(component.Component):
self.session.set_settings(self.settings)
# Load metadata extension
# Note: All libtorrent python bindings to set plugins/extensions need to be disabled
# due to GIL issue. https://code.google.com/p/libtorrent/issues/detail?id=369
# self.session.add_extension(lt.create_metadata_plugin)
# self.session.add_extension(lt.create_ut_metadata_plugin)
# self.session.add_extension(lt.create_smart_ban_plugin)
# In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue.
# https://code.google.com/p/libtorrent/issues/detail?id=369
if deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.7.0"):
self.session.add_extension("metadata_transfer")
self.session.add_extension("ut_metadata")
self.session.add_extension("smart_ban")
# Create the components
self.eventmanager = EventManager()
@ -405,15 +414,18 @@ class Core(component.Component):
@export
def pause_all_torrents(self):
"""Pause all torrents in the session"""
for torrent in self.torrentmanager.torrents.values():
torrent.pause()
if not self.session.is_paused():
self.session.pause()
component.get("EventManager").emit(SessionPausedEvent())
@export
def resume_all_torrents(self):
"""Resume all torrents in the session"""
for torrent in self.torrentmanager.torrents.values():
torrent.resume()
component.get("EventManager").emit(SessionResumedEvent())
if self.session.is_paused():
self.session.resume()
for torrent_id in self.torrentmanager.torrents:
self.torrentmanager[torrent_id].update_state()
component.get("EventManager").emit(SessionResumedEvent())
@export
def resume_torrent(self, torrent_ids):
@ -430,9 +442,9 @@ class Core(component.Component):
# Torrent was probaly removed meanwhile
return {}
# Get the leftover fields and ask the plugin manager to fill them
# Get any remaining keys from plugin manager or 'all' if no keys specified.
leftover_fields = list(set(keys) - set(status.keys()))
if len(leftover_fields) > 0:
if len(leftover_fields) > 0 or len(keys) == 0:
status.update(self.pluginmanager.get_status(torrent_id, leftover_fields))
return status
@ -495,6 +507,8 @@ class Core(component.Component):
"""Set the config with values from dictionary"""
# Load all the values into the configuration
for key in config.keys():
if self.read_only_config_keys and key in self.read_only_config_keys:
continue
if isinstance(config[key], unicode) or isinstance(config[key], str):
config[key] = config[key].encode("utf8")
self.config[key] = config[key]

View File

@ -133,9 +133,15 @@ class Daemon(object):
else:
listen_interface = ""
if options and options.read_only_config_keys:
read_only_config_keys = options.read_only_config_keys.split(",")
else:
read_only_config_keys = []
from deluge.core.core import Core
# Start the core as a thread and join it until it's done
self.core = Core(listen_interface=listen_interface)
self.core = Core(listen_interface=listen_interface,
read_only_config_keys=read_only_config_keys)
port = self.core.config["daemon_port"]
if options and options.port:

View File

@ -169,7 +169,9 @@ class FilterManager(component.Component):
for torrent_id in list(torrent_ids):
status = status_func(torrent_id, filter_dict.keys()) #status={key:value}
for field, values in filter_dict.iteritems():
if (not status[field] in values) and torrent_id in torrent_ids:
if field in status and status[field] in values:
continue
elif torrent_id in torrent_ids:
torrent_ids.remove(torrent_id)
return torrent_ids

View File

@ -92,6 +92,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def get_status(self, torrent_id, fields):
"""Return the value of status fields for the selected torrent_id."""
status = {}
if len(fields) == 0:
fields = self.status_fields.keys()
for field in fields:
try:
status[field] = self.status_fields[field](torrent_id)

View File

@ -337,11 +337,10 @@ class PreferencesManager(component.Component):
def _on_set_utpex(self, key, value):
log.debug("utpex value set to %s", value)
if value:
# Note: All libtorrent python bindings to set plugins/extensions need to be disabled
# due to GIL issue. https://code.google.com/p/libtorrent/issues/detail?id=369
#self.session.add_extension(lt.create_ut_pex_plugin)
pass
# In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue.
# https://code.google.com/p/libtorrent/issues/detail?id=369
if value and deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.7.0"):
self.session.add_extension("ut_pex")
def _on_set_encryption(self, key, value):
log.debug("encryption value %s set to %s..", key, value)

View File

@ -493,10 +493,10 @@ def generate_ssl_keys():
"""
This method generates a new SSL key/cert.
"""
digest = "md5"
digest = "sha256"
# Generate key pair
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 1024)
pkey.generate_key(crypto.TYPE_RSA, 2048)
# Generate cert request
req = crypto.X509Req()
@ -509,7 +509,7 @@ def generate_ssl_keys():
cert = crypto.X509()
cert.set_serial_number(0)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(60*60*24*365*5) # Five Years
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years
cert.set_issuer(req.get_subject())
cert.set_subject(req.get_subject())
cert.set_pubkey(req.get_pubkey())

View File

@ -98,6 +98,14 @@ class TorrentOptions(dict):
self["file_priorities"] = []
self["mapped_files"] = {}
class TorrentError(object):
def __init__(self, error_message, was_paused=False, restart_to_resume=False):
self.error_message = error_message
self.was_paused = was_paused
self.restart_to_resume = restart_to_resume
class Torrent(object):
"""Torrent holds information about torrents added to the libtorrent session.
"""
@ -141,6 +149,9 @@ class Torrent(object):
# Store the magnet uri used to add this torrent if available
self.magnet = magnet
# Torrent state e.g. Paused, Downloading, etc.
self.state = None
# Holds status info so that we don't need to keep getting it from lt
self.status = self.handle.status()
@ -185,11 +196,14 @@ class Torrent(object):
# Various torrent options
self.handle.resolve_countries(True)
self.set_options(self.options)
# Details of torrent forced into error state (i.e. not by libtorrent).
self.forced_error = None
# Status message holds error info about the torrent
self.statusmsg = "OK"
self.set_options(self.options)
# The torrents state
self.update_state()
@ -209,8 +223,6 @@ class Torrent(object):
self.forcing_recheck = False
self.forcing_recheck_paused = False
log.debug("Torrent object created.")
## Options methods ##
def set_options(self, options):
OPTIONS_FUNCS = {
@ -235,6 +247,10 @@ class Torrent(object):
def set_max_connections(self, max_connections):
if max_connections == 0:
max_connections = -1
elif max_connections == 1:
max_connections = 2
self.options["max_connections"] = int(max_connections)
self.handle.set_max_connections(max_connections)
@ -323,14 +339,17 @@ class Torrent(object):
# Set the first/last priorities if needed
self.set_prioritize_first_last(self.options["prioritize_first_last_pieces"])
def set_trackers(self, trackers):
def set_trackers(self, trackers, reannounce=True):
"""Sets trackers"""
if trackers == None:
trackers = []
for value in self.handle.trackers():
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
if lt.version_major == 0 and lt.version_minor < 15:
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
else:
tracker = value
trackers.append(tracker)
self.trackers = trackers
self.tracker_host = None
@ -350,7 +369,7 @@ class Torrent(object):
# log.debug("tier: %s tracker: %s", t["tier"], t["url"])
# Set the tracker list in the torrent object
self.trackers = trackers
if len(trackers) > 0:
if len(trackers) > 0 and reannounce:
# Force a reannounce if there is at least 1 tracker
self.force_reannounce()
@ -363,51 +382,56 @@ class Torrent(object):
def set_tracker_status(self, status):
"""Sets the tracker status"""
self.tracker_host = None
self.tracker_status = self.get_tracker_host() + ": " + status
def update_state(self):
"""Updates the state based on what libtorrent's state for the torrent is"""
# Set the initial state based on the lt state
LTSTATE = deluge.common.LT_TORRENT_STATE
ltstate = int(self.handle.status().state)
status = self.handle.status()
ltstate = status.state
# Set self.state to the ltstate right away just incase we don't hit some
# of the logic below
if ltstate in LTSTATE:
self.state = LTSTATE[ltstate]
else:
self.state = str(ltstate)
# Set self.state to the ltstate right away just incase we don't hit some of the logic below
old_state = self.state
self.state = LTSTATE.get(int(ltstate), str(ltstate))
log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
log.debug("session.is_paused: %s", component.get("Core").session.is_paused())
is_paused = self.handle.is_paused()
is_auto_managed = self.handle.is_auto_managed()
session_paused = component.get("Core").session.is_paused()
# First we check for an error from libtorrent, and set the state to that
# if any occurred.
if len(self.handle.status().error) > 0:
if self.forced_error:
self.state = "Error"
self.set_status_message("Error: " + self.forced_error.error_message)
elif status.error:
# This is an error'd torrent
self.state = "Error"
self.set_status_message(self.handle.status().error)
if self.handle.is_paused():
self.set_status_message(status.error)
if is_paused:
self.handle.auto_managed(False)
return
if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]:
if self.handle.is_paused():
else:
if is_paused and is_auto_managed and not session_paused:
self.state = "Queued"
elif is_paused or session_paused:
self.state = "Paused"
else:
elif ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"] or \
ltstate == LTSTATE["Checking Resume Data"]:
self.state = "Checking"
return
elif ltstate == LTSTATE["Downloading"] or ltstate == LTSTATE["Downloading Metadata"]:
self.state = "Downloading"
elif ltstate == LTSTATE["Finished"] or ltstate == LTSTATE["Seeding"]:
self.state = "Seeding"
elif ltstate == LTSTATE["Allocating"]:
self.state = "Allocating"
elif ltstate == LTSTATE["Downloading"] or ltstate == LTSTATE["Downloading Metadata"]:
self.state = "Downloading"
elif ltstate == LTSTATE["Finished"] or ltstate == LTSTATE["Seeding"]:
self.state = "Seeding"
elif ltstate == LTSTATE["Allocating"]:
self.state = "Allocating"
if self.handle.is_paused() and self.handle.is_auto_managed() and not component.get("Core").session.is_paused():
self.state = "Queued"
elif component.get("Core").session.is_paused() or (self.handle.is_paused() and not self.handle.is_auto_managed()):
self.state = "Paused"
if self.state != old_state:
log.debug("Using torrent state from lt: %s, auto_managed: %s, paused: %s, session_paused: %s",
ltstate, is_auto_managed, is_paused, session_paused)
log.debug("Torrent %s set from %s to %s: '%s'",
self.torrent_id, old_state, self.state, self.statusmsg)
component.get("EventManager").emit(TorrentStateChangedEvent(self.torrent_id, self.state))
def set_state(self, state):
"""Accepts state strings, ie, "Paused", "Seeding", etc."""
@ -421,6 +445,37 @@ class Torrent(object):
def set_status_message(self, message):
self.statusmsg = message
def force_error_state(self, message, restart_to_resume=True):
"""Forces the torrent into an error state.
For setting an error state not covered by libtorrent.
Args:
message (str): The error status message.
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
session can resume.
"""
status = self.handle.status()
self.handle.auto_managed(False)
self.forced_error = TorrentError(message, status.paused, restart_to_resume)
if not status.paused:
self.handle.pause()
self.update_state()
def clear_forced_error_state(self, update_state=True):
if not self.forced_error:
return
if self.forced_error.restart_to_resume:
log.error("Restart deluge to clear this torrent error")
if not self.forced_error.was_paused and self.options["auto_managed"]:
self.handle.auto_managed(True)
self.forced_error = None
self.set_status_message("OK")
if update_state:
self.update_state()
def get_eta(self):
"""Returns the ETA in seconds for this torrent"""
if self.status == None:
@ -776,6 +831,8 @@ class Torrent(object):
def pause(self):
"""Pause this torrent"""
if self.state == "Error":
return False
# Turn off auto-management so the torrent will not be unpaused by lt queueing
self.handle.auto_managed(False)
if self.handle.is_paused():
@ -799,7 +856,8 @@ class Torrent(object):
if self.handle.is_paused() and self.handle.is_auto_managed():
log.debug("Torrent is being auto-managed, cannot resume!")
return
elif self.forced_error and self.forced_error.was_paused:
log.debug("Skip resuming Error state torrent that was originally paused.")
else:
# Reset the status message just in case of resuming an Error'd torrent
self.set_status_message("OK")
@ -823,6 +881,11 @@ class Torrent(object):
return True
if self.forced_error and not self.forced_error.restart_to_resume:
self.clear_forced_error_state()
elif self.state == "Error" and not self.forced_error:
self.handle.clear_error()
def connect_peer(self, ip, port):
"""adds manual peer"""
try:
@ -835,27 +898,31 @@ class Torrent(object):
def move_storage(self, dest):
"""Move a torrent's storage location"""
try:
dest = unicode(dest, "utf-8")
dest = unicode(dest, "utf-8")
except TypeError:
# String is already unicode
pass
# String is already unicode
pass
if not os.path.exists(dest):
try:
# Try to make the destination path if it doesn't exist
os.makedirs(dest)
except IOError, e:
log.exception(e)
log.error("Could not move storage for torrent %s since %s does not exist and could not create the directory.", self.torrent_id, dest_u)
except OSError, e:
log.error("Could not move storage for torrent %s since %s does "
"not exist and could not create the directory: %s",
self.torrent_id, dest, ex)
return False
kwargs = {}
if deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("1.0.0.0"):
kwargs['flags'] = 1 # fail_if_exist
dest_bytes = dest.encode('utf-8')
try:
# libtorrent needs unicode object if wstrings are enabled, utf8 bytestring otherwise
try:
self.handle.move_storage(dest)
self.handle.move_storage(dest, **kwargs)
except TypeError:
self.handle.move_storage(dest_bytes)
self.handle.move_storage(dest_bytes, **kwargs)
except Exception, e:
log.error("Error calling libtorrent move_storage: %s" % e)
return False
@ -865,8 +932,12 @@ class Torrent(object):
def save_resume_data(self):
"""Signals libtorrent to build resume data for this torrent, it gets
returned in a libtorrent alert"""
self.handle.save_resume_data()
self.waiting_on_resume_data = True
# Don't generate fastresume data if torrent is in a Deluge Error state.
if self.forced_error:
log.debug("Skipped creating resume_data while in Error state")
else:
self.handle.save_resume_data()
self.waiting_on_resume_data = True
def on_metadata_received(self):
if self.options["prioritize_first_last_pieces"]:
@ -923,16 +994,27 @@ class Torrent(object):
def force_recheck(self):
"""Forces a recheck of the torrents pieces"""
paused = self.handle.is_paused()
self.forcing_recheck = True
if self.forced_error:
self.forcing_recheck_paused = self.forced_error.was_paused
self.clear_forced_error_state(update_state=False)
else:
self.forcing_recheck_paused = self.handle.is_paused()
# Store trackers for paused torrents to prevent unwanted announce before pausing again.
if self.forcing_recheck_paused:
self.set_trackers(None, reannounce=False)
self.handle.replace_trackers([])
try:
self.handle.force_recheck()
self.handle.resume()
except Exception, e:
log.debug("Unable to force recheck: %s", e)
return False
self.forcing_recheck = True
self.forcing_recheck_paused = paused
return True
self.forcing_recheck = False
if self.forcing_recheck_paused:
self.set_trackers(torrent.trackers, reannounce=False)
return self.forcing_recheck
def rename_files(self, filenames):
"""Renames files in the torrent. 'filenames' should be a list of
@ -981,4 +1063,3 @@ class Torrent(object):
for key in self.prev_status.keys():
if not self.rpcserver.is_session_valid(key):
del self.prev_status[key]

View File

@ -202,6 +202,12 @@ class TorrentManager(component.Component):
self.on_alert_file_error)
self.alerts.register_handler("file_completed_alert",
self.on_alert_file_completed)
self.alerts.register_handler("fastresume_rejected_alert",
self.on_alert_fastresume_rejected)
# Define timers
self.save_state_timer = LoopingCall(self.save_state)
self.save_resume_data_timer = LoopingCall(self.save_resume_data)
def start(self):
# Get the pluginmanager reference
@ -210,14 +216,12 @@ class TorrentManager(component.Component):
# Run the old state upgrader before loading state
deluge.core.oldstateupgrader.OldStateUpgrader()
# Try to load the state from file
# Try to load the state from file.
self.load_state()
# Save the state every 5 minutes
self.save_state_timer = LoopingCall(self.save_state)
# Save the state and resume data every ~3 minutes.
self.save_state_timer.start(200, False)
self.save_resume_data_timer = LoopingCall(self.save_resume_data)
self.save_resume_data_timer.start(190)
self.save_resume_data_timer.start(190, False)
def stop(self):
# Stop timers
@ -391,7 +395,7 @@ class TorrentManager(component.Component):
if add_torrent_id in self.get_torrent_list():
log.info("Merging trackers for torrent (%s) already in session...", add_torrent_id)
# Don't merge trackers if either torrent has private flag set
if self[add_torrent_id].get_status(["private"])["private"]:
if torrent_info.priv() or self[add_torrent_id].get_status(["private"])["private"]:
log.info("Merging trackers abandoned: Torrent has private flag set.")
return
@ -491,6 +495,10 @@ class TorrentManager(component.Component):
component.resume("AlertManager")
# Store the orignal resume_data, in case of errors.
if resume_data:
self.resume_data[torrent.torrent_id] = resume_data
# Resume the torrent if needed
if not options["add_paused"]:
torrent.resume()
@ -628,21 +636,24 @@ class TorrentManager(component.Component):
def load_state(self):
"""Load the state of the TorrentManager from the torrents.state file"""
state = TorrentManagerState()
try:
log.debug("Opening torrent state file for load.")
state_file = open(
os.path.join(get_config_dir(), "state", "torrents.state"), "rb")
state = cPickle.load(state_file)
state_file.close()
except (EOFError, IOError, Exception, cPickle.UnpicklingError), e:
log.warning("Unable to load state file: %s", e)
filepath = os.path.join(get_config_dir(), "state", "torrents.state")
log.debug("Opening torrent state file for load.")
for _filepath in (filepath, filepath + ".bak"):
try:
state_file = open(_filepath, "rb")
state = cPickle.load(state_file)
state_file.close()
except (EOFError, IOError, Exception, cPickle.UnpicklingError), e:
log.warning("Unable to load state file: %s", e)
state = TorrentManagerState()
else:
log.info("Successfully loaded state file: %s", _filepath)
break
# Try to use an old state
try:
state_tmp = TorrentState()
if dir(state.torrents[0]) != dir(state_tmp):
if state.torrents and dir(state.torrents[0]) != dir(state_tmp):
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
for s in state.torrents:
setattr(s, attr, getattr(state_tmp, attr, None))
@ -671,9 +682,14 @@ class TorrentManager(component.Component):
state = TorrentManagerState()
# Create the state for each Torrent and append to the list
for torrent in self.torrents.values():
paused = False
if torrent.state == "Paused":
if self.session.is_paused():
paused = torrent.handle.is_paused()
elif torrent.forced_error:
paused = torrent.forced_error.was_paused
elif torrent.state == "Paused":
paused = True
else:
paused = False
torrent_state = TorrentState(
torrent.torrent_id,
@ -703,26 +719,32 @@ class TorrentManager(component.Component):
state.torrents.append(torrent_state)
# Pickle the TorrentManagerState object
filepath = os.path.join(get_config_dir(), "state", "torrents.state")
filepath_tmp = filepath + ".tmp"
filepath_bak = filepath + ".bak"
try:
log.debug("Saving torrent state file.")
state_file = open(os.path.join(get_config_dir(),
"state", "torrents.state.new"), "wb")
os.remove(filepath_bak)
except OSError:
pass
try:
log.debug("Creating backup of state at: %s", filepath_bak)
os.rename(filepath, filepath_bak)
except OSError, ex:
log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex)
try:
log.info("Saving the state at: %s", filepath)
state_file = open(filepath_tmp, "wb", 0)
cPickle.dump(state, state_file)
state_file.flush()
os.fsync(state_file.fileno())
state_file.close()
except IOError, e:
log.warning("Unable to save state file: %s", e)
return True
# We have to move the 'torrents.state.new' file to 'torrents.state'
try:
shutil.move(
os.path.join(get_config_dir(), "state", "torrents.state.new"),
os.path.join(get_config_dir(), "state", "torrents.state"))
os.rename(filepath_tmp, filepath)
except IOError:
log.warning("Unable to save state file.")
return True
log.error("Unable to save %s: %s", filepath, ex)
if os.path.isfile(filepath_bak):
log.info("Restoring backup of state from: %s", filepath_bak)
os.rename(filepath_bak, filepath)
# We return True so that the timer thread will continue
return True
@ -742,15 +764,20 @@ class TorrentManager(component.Component):
self.num_resume_data = len(torrent_ids)
def load_resume_data_file(self):
resume_data = {}
try:
log.debug("Opening torrents fastresume file for load.")
fastresume_file = open(os.path.join(get_config_dir(), "state",
"torrents.fastresume"), "rb")
resume_data = lt.bdecode(fastresume_file.read())
fastresume_file.close()
except (EOFError, IOError, Exception), e:
log.warning("Unable to load fastresume file: %s", e)
filepath = os.path.join(get_config_dir(), "state", "torrents.fastresume")
log.debug("Opening torrents fastresume file for load.")
for _filepath in (filepath, filepath + ".bak"):
try:
fastresume_file = open(_filepath, "rb")
resume_data = lt.bdecode(fastresume_file.read())
fastresume_file.close()
except (EOFError, IOError, Exception), e:
if self.torrents:
log.warning("Unable to load fastresume file: %s", e)
resume_data = None
else:
log.info("Successfully loaded fastresume file: %s", _filepath)
break
# If the libtorrent bdecode doesn't happen properly, it will return None
# so we need to make sure we return a {}
@ -774,8 +801,9 @@ class TorrentManager(component.Component):
if self.num_resume_data or not self.resume_data:
return
path = os.path.join(get_config_dir(), "state", "torrents.fastresume")
path_tmp = path + ".tmp"
filepath = os.path.join(get_config_dir(), "state", "torrents.fastresume")
filepath_tmp = filepath + ".tmp"
filepath_bak = filepath + ".bak"
# First step is to load the existing file and update the dictionary
if resume_data is None:
@ -785,15 +813,27 @@ class TorrentManager(component.Component):
self.resume_data = {}
try:
log.debug("Saving fastresume file: %s", path)
fastresume_file = open(path_tmp, "wb")
os.remove(filepath_bak)
except OSError:
pass
try:
log.debug("Creating backup of fastresume at: %s", filepath_bak)
os.rename(filepath, filepath_bak)
except OSError, ex:
log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex)
try:
log.info("Saving the fastresume at: %s", filepath)
fastresume_file = open(filepath_tmp, "wb", 0)
fastresume_file.write(lt.bencode(resume_data))
fastresume_file.flush()
os.fsync(fastresume_file.fileno())
fastresume_file.close()
shutil.move(path_tmp, path)
except IOError:
log.warning("Error trying to save fastresume file")
os.rename(filepath_tmp, filepath)
except IOError, ex:
log.error("Unable to save %s: %s", filepath, ex)
if os.path.isfile(filepath_bak):
log.info("Restoring backup of fastresume from: %s", filepath_bak)
os.rename(filepath_bak, filepath)
def remove_empty_folders(self, torrent_id, folder):
"""
@ -936,10 +976,7 @@ class TorrentManager(component.Component):
except:
return
# Set the torrent state
old_state = torrent.state
torrent.update_state()
if torrent.state != old_state:
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
# Don't save resume data for each torrent after self.stop() was called.
# We save resume data in bulk in self.stop() in this case.
@ -963,6 +1000,7 @@ class TorrentManager(component.Component):
torrent.forcing_recheck = False
if torrent.forcing_recheck_paused:
torrent.handle.pause()
torrent.set_trackers(torrent.trackers, reannounce=False)
# Set the torrent state
torrent.update_state()
@ -1005,13 +1043,19 @@ class TorrentManager(component.Component):
torrent.set_tracker_status(tracker_status)
def on_alert_tracker_error(self, alert):
log.debug("on_alert_tracker_error")
"""Alert handler for libtorrent tracker_error_alert"""
error_message = decode_string(alert.msg)
# If alert.msg is empty then it's a '-1' code so fallback to a.e.message. Note that alert.msg
# cannot be replaced by a.e.message because the code is included in the string (for non-'-1').
if not error_message:
error_message = decode_string(alert.error.message())
log.debug("Tracker Error Alert: %s [%s]", decode_string(alert.message()), error_message)
try:
torrent = self.torrents[str(alert.handle.info_hash())]
except:
except (RuntimeError, KeyError):
return
tracker_status = "%s: %s" % (_("Error"), alert.msg)
torrent.set_tracker_status(tracker_status)
torrent.set_tracker_status("%s: %s" % (_("Error"), error_message))
def on_alert_storage_moved(self, alert):
log.debug("on_alert_storage_moved")
@ -1037,6 +1081,7 @@ class TorrentManager(component.Component):
except (RuntimeError, KeyError):
return
log.error("Torrent %s, %s", torrent_id, decode_string(alert.message()))
if torrent_id in self.waiting_on_finish_moving:
self.waiting_on_finish_moving.remove(torrent_id)
torrent.is_finished = True
@ -1049,11 +1094,7 @@ class TorrentManager(component.Component):
torrent_id = str(alert.handle.info_hash())
except:
return
old_state = torrent.state
torrent.update_state()
if torrent.state != old_state:
# We need to emit a TorrentStateChangedEvent too
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
component.get("EventManager").emit(TorrentResumedEvent(torrent_id))
def on_alert_state_changed(self, alert):
@ -1064,7 +1105,6 @@ class TorrentManager(component.Component):
except:
return
old_state = torrent.state
torrent.update_state()
# Torrent may need to download data after checking.
@ -1072,10 +1112,6 @@ class TorrentManager(component.Component):
torrent.is_finished = False
self.queued_torrents.add(torrent_id)
# Only emit a state changed event if the state has actually changed
if torrent.state != old_state:
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
def on_alert_save_resume_data(self, alert):
log.debug("on_alert_save_resume_data")
try:
@ -1104,6 +1140,25 @@ class TorrentManager(component.Component):
self.save_resume_data_file()
def on_alert_fastresume_rejected(self, alert):
"""Alert handler for libtorrent fastresume_rejected_alert"""
alert_msg = decode_string(alert.message())
log.error("on_alert_fastresume_rejected: %s", alert_msg)
try:
torrent_id = str(alert.handle.info_hash())
torrent = self.torrents[torrent_id]
except (RuntimeError, KeyError):
return
if alert.error.value() == 134:
if not os.path.isdir(torrent.options["download_location"]):
error_msg = "Unable to locate Download Folder!"
else:
error_msg = "Missing or invalid torrent data!"
else:
error_msg = "Problem with resume data: %s" % alert_msg.split(":", 1)[1].strip()
torrent.force_error_state(error_msg, restart_to_resume=True)
def on_alert_file_renamed(self, alert):
log.debug("on_alert_file_renamed")

View File

@ -146,7 +146,7 @@ def sanitise_filename(filename):
log.warning("Potentially malicious server: trying to write to file '%s'" % filename)
# Only use the basename
filename = os.path.basename(filename)
filename = filename.strip()
if filename.startswith(".") or ";" in filename or "|" in filename:
# Dodgy server, log it
@ -192,21 +192,42 @@ def download_file(url, filename, callback=None, headers=None, force_filename=Fal
headers = {}
headers["accept-encoding"] = "deflate, gzip, x-gzip"
# In twisted 13.1.0 the _parse() function was replaced by the _URI class
if hasattr(client, '_parse'):
# In Twisted 13.1.0 _parse() function replaced by _URI class.
# In Twisted 15.0.0 _URI class renamed to URI.
if hasattr(client, "_parse"):
scheme, host, port, path = client._parse(url)
else:
from twisted.web.client import _URI
uri = _URI.fromBytes(url)
try:
from twisted.web.client import _URI as URI
except ImportError:
from twisted.web.client import URI
uri = URI.fromBytes(url)
scheme = uri.scheme
host = uri.host
port = uri.port
path = uri.path
factory = HTTPDownloader(url, filename, callback, headers, force_filename, allow_compression)
if scheme == "https":
from twisted.internet import ssl
reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
# ClientTLSOptions in Twisted >= 14, see ticket #2765 for details on this addition.
try:
from twisted.internet._sslverify import ClientTLSOptions
except ImportError:
ctx_factory = ssl.ClientContextFactory()
else:
class TLSSNIContextFactory(ssl.ClientContextFactory):
"""
A custom context factory to add a server name for TLS connections.
"""
def getContext(self, hostname=None, port=None):
ctx = ssl.ClientContextFactory.getContext(self)
ClientTLSOptions(host, ctx)
return ctx
ctx_factory = TLSSNIContextFactory()
reactor.connectSSL(host, port, factory, ctx_factory)
else:
reactor.connectTCP(host, port, factory)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -86,7 +86,10 @@ def start_ui():
help="Sets the log level to 'none', this is the same as `-L none`", action="store_true", default=False)
# Get the options and args from the OptionParser
(options, args) = parser.parse_args()
if deluge.common.windows_check():
(options, args) = parser.parse_args(deluge.common.win32_unicode_argv()[1:])
else:
(options, args) = parser.parse_args()
# Setup the logger
if options.quiet:
@ -166,6 +169,9 @@ this should be an IP address", metavar="IFACE",
help="Sets the log level to 'none', this is the same as `-L none`", action="store_true", default=False)
parser.add_option("--profile", dest="profile", action="store_true", default=False,
help="Profiles the daemon")
parser.add_option("--read-only-config-keys", dest="read_only_config_keys",
help="List of comma-separated config keys that will not be modified by set_config RPC.",
action="store", type="str")
# Get the options and args from the OptionParser
(options, args) = parser.parse_args()
@ -173,6 +179,8 @@ this should be an IP address", metavar="IFACE",
# Setup the logger
if options.quiet:
options.loglevel = "none"
if options.loglevel:
options.loglevel = options.loglevel.lower()
if options.logfile:
# Try to create the logfile's directory if it doesn't exist
try:
@ -204,7 +212,7 @@ this should be an IP address", metavar="IFACE",
# If the donot daemonize is set, then we just skip the forking
if not options.donot:
# Windows check, we log to the config folder by default
if deluge.common.windows_check() or deluge.common.osx_check():
if deluge.common.windows_check():
open_logfile()
write_pidfile()
else:

View File

@ -259,12 +259,13 @@ class Core(CorePluginBase):
# Skip directories
continue
else:
ext = os.path.splitext(filename)[1]
ext = os.path.splitext(filename)[1].lower()
if ext == ".torrent":
magnet = False
elif ext == ".magnet":
magnet = True
else:
log.debug("File checked for auto-loading is invalid: %s", filename)
continue
try:
filedump = self.load_torrent(filepath, magnet)

View File

@ -135,6 +135,8 @@ class Core(CorePluginBase):
:rtype: Deferred
"""
if not self.config["url"]:
return
# Reset variables
self.filename = None
@ -425,12 +427,12 @@ class Core(CorePluginBase):
def pause_session(self):
if not self.core.session.is_paused():
self.core.session.pause()
self.core.pause_all_torrents()
self.need_to_resume_session = True
else:
self.need_to_resume_session = False
def resume_session(self, result):
self.core.session.resume()
self.core.resume_all_torrents()
self.need_to_resume_session = False
return result

View File

@ -138,7 +138,7 @@ class GtkUI(GtkPluginBase):
def _on_apply_prefs(self):
config = {}
config["url"] = self.glade.get_widget("entry_url").get_text()
config["url"] = self.glade.get_widget("entry_url").get_text().strip()
config["check_after_days"] = self.glade.get_widget("spin_check_days").get_value_as_int()
config["load_on_start"] = self.glade.get_widget("chk_import_on_start").get_active()
client.blocklist.set_config(config)

View File

@ -44,7 +44,7 @@ import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.core.rpcserver import export
from deluge.event import DelugeEvent
from deluge.common import utf8_encoded
from deluge.common import utf8_encoded, windows_check
DEFAULT_CONFIG = {
@ -140,9 +140,15 @@ class Core(CorePluginBase):
if command[EXECUTE_EVENT] == event:
command = os.path.expandvars(command[EXECUTE_COMMAND])
command = os.path.expanduser(command)
cmd_args = [torrent_id, torrent_name, save_path]
if windows_check:
# Escape ampersand on windows (see #2784)
cmd_args = [arg.replace("&", "^^^&") for arg in cmd_args]
if os.path.isfile(command) and os.access(command, os.X_OK):
log.debug("[execute] Running %s", command)
d = getProcessOutputAndValue(command, (torrent_id, torrent_name, save_path), env=os.environ)
log.debug("[execute] Running %s with args: %s", command, cmd_args)
d = getProcessOutputAndValue(command, cmd_args, env=os.environ)
d.addCallback(log_error, command)
else:
log.error("[execute] Execute script not found or not executable")

View File

@ -37,9 +37,10 @@
#
#
import errno
import os
from twisted.internet.utils import getProcessValue
from twisted.internet.utils import getProcessOutputAndValue
from deluge.log import LOG as log
from deluge.plugins.pluginbase import CorePluginBase
@ -142,40 +143,38 @@ class Core(CorePluginBase):
if file_ext_sec and file_ext_sec + file_ext in EXTRACT_COMMANDS:
file_ext = file_ext_sec + file_ext
elif file_ext not in EXTRACT_COMMANDS or file_ext_sec == '.tar':
log.warning("EXTRACTOR: Can't extract file with unknown file type: %s" % f["path"])
log.debug("EXTRACTOR: Can't extract file with unknown file type: %s", f["path"])
continue
elif file_ext == ".rar" and "part" in file_ext_sec:
part_num = file_ext_sec.split("part")[1]
if part_num.isdigit() and int(part_num) != 1:
log.debug("Skipping remaining multi-part rar files: %s", f["path"])
continue
cmd = EXTRACT_COMMANDS[file_ext]
# Now that we have the cmd, lets run it to extract the files
fpath = os.path.join(tid_status["save_path"], os.path.normpath(f["path"]))
# Get the destination path
dest = os.path.normpath(self.config["extract_path"])
if self.config["use_name_folder"]:
name = tid_status["name"]
dest = os.path.join(dest, name)
dest = os.path.join(dest, tid_status["name"])
# Create the destination folder if it doesn't exist
if not os.path.exists(dest):
try:
os.makedirs(dest)
except Exception, e:
log.error("EXTRACTOR: Error creating destination folder: %s", e)
return
try:
os.makedirs(dest)
except OSError, ex:
if not (ex.errno == errno.EEXIST and os.path.isdir(dest)):
log.error("EXTRACTOR: Error creating destination folder: %s", ex)
break
def on_extract_success(result, torrent_id, fpath):
# XXX: Emit an event
log.info("EXTRACTOR: Extract successful: %s (%s)", fpath, torrent_id)
def on_extract(result, torrent_id, fpath):
# Check command exit code.
if not result[2]:
log.info("EXTRACTOR: Extract successful: %s (%s)", fpath, torrent_id)
else:
log.error("EXTRACTOR: Extract failed: %s (%s) %s", fpath, torrent_id, result[1])
def on_extract_failed(result, torrent_id, fpath):
# XXX: Emit an event
log.error("EXTRACTOR: Extract failed: %s (%s)", fpath, torrent_id)
# Run the command and add some callbacks
log.debug("EXTRACTOR: Extracting %s with %s %s to %s", fpath, cmd[0], cmd[1], dest)
d = getProcessValue(cmd[0], cmd[1].split() + [str(fpath)], {}, str(dest))
d.addCallback(on_extract_success, torrent_id, fpath)
d.addErrback(on_extract_failed, torrent_id, fpath)
# Run the command and add callback.
log.debug("EXTRACTOR: Extracting %s from %s with %s %s to %s", fpath, torrent_id, cmd[0], cmd[1], dest)
d = getProcessOutputAndValue(cmd[0], cmd[1].split() + [str(fpath)], os.environ, str(dest))
d.addCallback(on_extract, torrent_id, fpath)
@export
def set_config(self, config):

View File

@ -42,7 +42,7 @@ from setuptools import setup
__plugin_name__ = "Extractor"
__author__ = "Andrew Resch"
__author_email__ = "andrewresch@gmail.com"
__version__ = "0.4"
__version__ = "0.6"
__url__ = "http://deluge-torrent.org"
__license__ = "GPLv3"
__description__ = "Extract files upon torrent completion"

View File

@ -10,7 +10,6 @@
<property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="skip_taskbar_hint">True</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
@ -155,7 +154,7 @@
<widget class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Upload Slots: </property>
<property name="label" translatable="yes">Upload Slots:</property>
</widget>
<packing>
<property name="top_attach">2</property>
@ -650,7 +649,6 @@
<property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="skip_taskbar_hint">True</property>
<property name="has_separator">False</property>
<signal name="close" handler="on_label_cancel"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="UTF-8"?>
<glade-interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-requires gtk+ 2.16 -->
<!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkWindow" id="window">
<child>
@ -179,7 +179,6 @@
<widget class="GtkEntry" id="smtp_host">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
@ -205,12 +204,10 @@
<widget class="GtkSpinButton" id="smtp_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">65535</property>
<property name="invisible_char">&#x25CF;</property>
<property name="max_length">5</property>
<property name="width_chars">5</property>
<property name="adjustment">25 1 65535 0 10 0</property>
<property name="adjustment">25 1 65535 1 10 0</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
@ -235,7 +232,6 @@
<widget class="GtkEntry" id="smtp_user">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
@ -259,7 +255,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
@ -401,7 +396,6 @@
<widget class="GtkEntry" id="smtp_from">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>

View File

@ -227,7 +227,7 @@ class GtkUiNotifications(CustomNotifications):
return ''
def _on_torrent_finished_event_popup(self, torrent_id):
d = client.core.get_torrent_status(torrent_id, ["name", "num_files"])
d = client.core.get_torrent_status(torrent_id, ["name", "file_progress"])
d.addCallback(self._on_torrent_finished_event_got_torrent_status)
d.addErrback(self._on_torrent_finished_event_torrent_status_failure)
return d
@ -239,6 +239,7 @@ class GtkUiNotifications(CustomNotifications):
log.debug("Handler for TorrentFinishedEvent GTKUI called. "
"Got Torrent Status")
title = _("Finished Torrent")
torrent_status["num_files"] = torrent_status["file_progress"].count(1.0)
message = _("The torrent \"%(name)s\" including %(num_files)i file(s) "
"has finished downloading.") % torrent_status
return title, message
@ -253,7 +254,6 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
"notifications-gtk.conf", DEFAULT_PREFS
)
self.glade = gtk.glade.XML(get_resource("config.glade"))
self.glade.get_widget("smtp_port").set_value(25)
self.prefs = self.glade.get_widget("prefs_box")
self.prefs.show_all()

View File

@ -129,7 +129,7 @@ class Core(CorePluginBase):
for setting in CONTROLLED_SETTINGS:
core_config.apply_set_functions(setting)
# Resume the session if necessary
component.get("Core").session.resume()
component.get("Core").resume_all_torrents()
def do_schedule(self, timer=True):
"""
@ -153,10 +153,10 @@ class Core(CorePluginBase):
settings.active_seeds = self.config["low_active_up"]
session.set_settings(settings)
# Resume the session if necessary
component.get("Core").session.resume()
component.get("Core").resume_all_torrents()
elif state == "Red":
# This is Red (Stop), so pause the libtorrent session
component.get("Core").session.pause()
component.get("Core").pause_all_torrents()
if state != self.state:
# The state has changed since last update so we need to emit an event

View File

@ -77,20 +77,20 @@ class SchedulerSelectWidget(gtk.DrawingArea):
#redraw the whole thing
def expose(self, widget, event):
self.context = self.window.cairo_create()
self.context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
self.context.clip()
context = self.window.cairo_create()
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
context.clip()
width = self.window.get_size()[0]
height = self.window.get_size()[1]
for y in xrange(7):
for x in xrange(24):
self.context.set_source_rgba(self.colors[self.button_state[x][y]][0], self.colors[self.button_state[x][y]][1], self.colors[self.button_state[x][y]][2], 0.7)
self.context.rectangle(width*(6*x/145.0+1/145.0), height*(6*y/43.0+1/43.0), 5*width/145.0, 5*height/43.0)
self.context.fill_preserve()
self.context.set_source_rgba(0.5, 0.5, 0.5, 0.5)
self.context.stroke()
context.set_source_rgba(self.colors[self.button_state[x][y]][0], self.colors[self.button_state[x][y]][1], self.colors[self.button_state[x][y]][2], 0.7)
context.rectangle(width*(6*x/145.0+1/145.0), height*(6*y/43.0+1/43.0), 5*width/145.0, 5*height/43.0)
context.fill_preserve()
context.set_source_rgba(0.5, 0.5, 0.5, 0.5)
context.stroke()
#coordinates --> which box
def get_point(self, event):
@ -155,23 +155,26 @@ class GtkUI(GtkPluginBase):
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
self.status_item = component.get("StatusBar").add_item(
self.statusbar = component.get("StatusBar")
self.status_item = self.statusbar.add_item(
image=get_resource("green.png"),
text="",
callback=self.on_status_item_clicked,
tooltip="Scheduler")
def on_get_state(state):
self.status_item.set_image_from_file(get_resource(state.lower() + ".png"))
self.state_deferred = client.scheduler.get_state().addCallback(on_get_state)
def on_state_deferred(state):
self.state = state
self.on_scheduler_event(state)
client.scheduler.get_state().addCallback(on_state_deferred)
client.register_event_handler("SchedulerEvent", self.on_scheduler_event)
def disable(self):
component.get("Preferences").remove_page(_("Scheduler"))
# Remove status item
component.get("StatusBar").remove_item(self.status_item)
# Reset statusbar dict.
self.statusbar.config_value_changed_dict["max_download_speed"] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict["max_upload_speed"] = self.statusbar._on_max_upload_speed
# Remove statusbar item.
self.statusbar.remove_item(self.status_item)
del self.status_item
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
@ -202,10 +205,30 @@ class GtkUI(GtkPluginBase):
client.scheduler.get_config().addCallback(on_get_config)
def on_scheduler_event(self, state):
def on_state_deferred(s):
self.status_item.set_image_from_file(get_resource(state.lower() + ".png"))
self.state = state
self.status_item.set_image_from_file(get_resource(self.state.lower() + ".png"))
if self.state == "Yellow":
# Prevent func calls in Statusbar if the config changes.
self.statusbar.config_value_changed_dict.pop("max_download_speed", None)
self.statusbar.config_value_changed_dict.pop("max_upload_speed", None)
try:
self.statusbar._on_max_download_speed(self.spin_download.get_value())
self.statusbar._on_max_upload_speed(self.spin_upload.get_value())
except AttributeError:
# Skip error due to Plugin being enabled before statusbar items created on startup.
pass
else:
self.statusbar.config_value_changed_dict["max_download_speed"] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict["max_upload_speed"] = self.statusbar._on_max_upload_speed
self.state_deferred.addCallback(on_state_deferred)
def update_config_values(config):
try:
self.statusbar._on_max_download_speed(config["max_download_speed"])
self.statusbar._on_max_upload_speed(config["max_upload_speed"])
except AttributeError:
# Skip error due to Plugin being enabled before statusbar items created on startup.
pass
client.core.get_config_values(["max_download_speed", "max_upload_speed"]).addCallback(update_config_values)
def on_status_item_clicked(self, widget, event):
component.get("Preferences").show("Scheduler")

View File

@ -41,7 +41,7 @@ from setuptools import setup
__plugin_name__ = "Scheduler"
__author__ = "Andrew Resch"
__author_email__ = "andrewresch@gmail.com"
__version__ = "0.2"
__version__ = "0.3"
__url__ = "http://deluge-torrent.org"
__license__ = "GPLv3"
__description__ = "Schedule limits on a per-hour per-day basis."

View File

@ -576,8 +576,6 @@ class Client(object):
try:
if deluge.common.windows_check():
subprocess.Popen(["deluged", "--port=%s" % port, "--config=%s" % config])
elif deluge.common.osx_check():
subprocess.call(["nohup", "deluged", "--port=%s" % port, "--config=%s" % config])
else:
subprocess.call(["deluged", "--port=%s" % port, "--config=%s" % config])
except OSError, e:

View File

@ -410,10 +410,11 @@ def get_localhost_auth():
auth_file = deluge.configmanager.get_config_dir("auth")
if os.path.exists(auth_file):
for line in open(auth_file):
if line.startswith("#"):
# This is a comment line
continue
line = line.strip()
if line.startswith("#") or not line:
# This is a comment or blank line
continue
try:
lsplit = line.split(":")
except Exception, e:

View File

@ -59,7 +59,7 @@ class Command(BaseCommand):
t_options = {}
if options["path"]:
t_options["download_location"] = os.path.expanduser(options["path"])
t_options["download_location"] = os.path.abspath(os.path.expanduser(options["path"]))
def on_success(result):
self.console.write("{!success!}Torrent added!")

View File

@ -34,8 +34,6 @@
#
#
from twisted.internet import defer
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
from deluge.ui.client import client
@ -106,35 +104,33 @@ class Command(BaseCommand):
return self._get_config(*args, **options)
def _get_config(self, *args, **options):
deferred = defer.Deferred()
config = component.get("CoreConfig")
keys = config.keys()
keys.sort()
s = ""
for key in keys:
if args and key not in args:
continue
color = "{!white,black,bold!}"
value = config[key]
if type(value) in colors.type_color:
color = colors.type_color[type(value)]
def _on_get_config(config):
keys = config.keys()
keys.sort()
s = ""
for key in keys:
if args and key not in args:
continue
color = "{!white,black,bold!}"
value = config[key]
if type(value) in colors.type_color:
color = colors.type_color[type(value)]
# We need to format dicts for printing
if isinstance(value, dict):
import pprint
value = pprint.pformat(value, 2, 80)
new_value = []
for line in value.splitlines():
new_value.append("%s%s" % (color, line))
value = "\n".join(new_value)
# We need to format dicts for printing
if isinstance(value, dict):
import pprint
value = pprint.pformat(value, 2, 80)
new_value = []
for line in value.splitlines():
new_value.append("%s%s" % (color, line))
value = "\n".join(new_value)
s += " %s: %s%s\n" % (key, color, value)
s += " %s: %s%s\n" % (key, color, value)
self.console.write(s)
self.console.write(s)
return config
return client.core.get_config().addCallback(_on_get_config)
def _set_config(self, *args, **options):
deferred = defer.Deferred()
config = component.get("CoreConfig")
key = options["set"][0]
val = simple_eval(options["set"][1] + " " .join(args))
@ -152,11 +148,9 @@ class Command(BaseCommand):
def on_set_config(result):
self.console.write("{!success!}Configuration value successfully updated.")
deferred.callback(True)
self.console.write("Setting %s to %s.." % (key, val))
client.core.set_config({key: val}).addCallback(on_set_config)
return deferred
return client.core.set_config({key: val}).addCallback(on_set_config)
def complete(self, text):
return [ k for k in component.get("CoreConfig").keys() if k.startswith(text) ]

View File

@ -70,7 +70,8 @@ STATUS_KEYS = ["state",
"is_seed",
"is_finished",
"active_time",
"seeding_time"
"seeding_time",
"time_added"
]
# Add filter specific state to torrent states

View File

@ -289,7 +289,7 @@ Please use commands from the command line, eg:\n
if self.interactive:
self.screen.add_line(line, not self.batch_write)
else:
print colors.strip_colors(line.encode("utf-8"))
print colors.strip_colors(deluge.common.utf8_encoded(line))
def do_command(self, cmd):
"""

View File

@ -221,7 +221,7 @@ COUNTRIES = {
'SE': _('Sweden'),
'CH': _('Switzerland'),
'SY': _('Syrian Arab Republic'),
'TW': _('Taiwan, Province of China'),
'TW': _('Taiwan'),
'TJ': _('Tajikistan'),
'TZ': _('Tanzania, United Republic of'),
'TH': _('Thailand'),

Some files were not shown because too many files have changed in this diff Show More