Compare commits

...

179 Commits

Author SHA1 Message Date
Cyker Way
e050905b29 Save copies of torrent files from magnet links.
This patch is meant for 1.3-stable (1.3.15 as of the commit date).

The bug is: when you add a new magnet link to download, even if you tick
the option *Copy of .torrent files to:*, deluge still fails to save a
copy of torrent. This patch adds extra code to do so.
2018-09-26 14:27:04 +01:00
Calum Lind
6c3442e7e7 Compress pngs with Zopflipng 2018-06-22 08:31:22 +01:00
Calum Lind
993abbc6a6 [#3130|GTKUI] Add deluge-panel for systray
To help theme deluge systray icon use a separate icon.
2018-06-21 15:08:45 +01:00
Calum Lind
a2fcebe15c [WebUI] Encode HTML entitiies
Ensure that torrent keys that could contain HTML entities are encoded
when displayed in webui.
2018-02-04 21:42:16 +00:00
Calum Lind
b8e5ebe822 [Console] Refactor console config command for windows paths
Parse windows paths regardless of console running on a windows machine.
2017-11-05 17:28:09 +00:00
Calum Lind
e33a8fbea4 [#3075|Console] Fix config handling windows paths
The console config token parser was unable to handle windows paths
starting with 'C:'.
2017-11-05 15:27:09 +00:00
Calum Lind
bcc7a74725 [#3112|Console] Fix handling hex for setting peer_tos in config
The token parser was converting hex value to int which is not what
should be passed onto libtorrent peer_tos setting.
2017-10-29 22:11:11 +00:00
Calum Lind
ffb8d9f8c3 [#3070] Fix httpdownloader error with missing content-disposition filename
The parsing of the content-disposition in httpdownloader was not able to
handle missing parameters e.g. "Content-Disposition: attachment" and would
result in an IndexError. Added a test for this use-case.

Fixed the issue using the cgi.parse_header to extract the parameters.
2017-10-29 12:34:31 +00:00
Calum Lind
396417bcd0 [#3124|GTKUI] Fix comparing Name str if value is None
The original fix was not correct as the strcoll function cannot
accept None only strings. This fix ensures that the value is an
empty string if None for comparison.
2017-10-29 11:16:21 +00:00
Calum Lind
b13da8a42a [#3010|GTKUI] Handle unknown OverflowError from twisted reactor 2017-10-29 10:46:26 +00:00
Calum Lind
415979e2f7 [#3066] [Core] Add extra DHT bootstrap nodes 2017-10-29 10:08:36 +00:00
Calum Lind
5f0694deb2 [#3044] [Core] Ignore resume data timestamps on startup
The timestamps of torrent data files are being modified after the
resume data is stored and upon resuming an error is raised because
the files fail resume data checks. So ignore this check as it is
not reliable.
2017-10-29 10:04:05 +00:00
Calum Lind
6d14be18b0 [#3079] Fix config parsing for json objects
* If a curly brace was used in a string then find_json_ojects would
   fail to find objects correctly. To fix this ignore double-quoted entries.
2017-06-28 10:39:20 +01:00
Calum Lind
65fac156eb [#3064|WebUI] Fix server not sending TLS intermediate certs
* Sending of cert chain was unintentionallly removed in commit c1902e43 (#2792).
2017-06-15 13:59:05 +01:00
Calum Lind
956f2ad574 [AutoAdd] Fix version string 2017-06-14 10:59:13 +01:00
Rato
275c93657f [Core] Save torrent state only if state has changed 2017-06-13 06:53:10 +02:00
Calum Lind
38d7b7cdfd [GTKUI] Fix keyerror showing prefs
* Fix the single_proxy ui to True as unlikely any users using <=0.15
   and need to have different proxy type settings.
2017-05-13 00:08:23 +01:00
Calum Lind
7661127b9d Fix 1.3.15 release date in changelog 2017-05-12 17:39:22 +01:00
Calum Lind
a6e8ac8725 Update Changelog and bump release to 1.3.15 2017-05-12 13:57:19 +01:00
Calum Lind
d91584b700 [#3012] [GTKUI] Consistent button position on Windows
* The deluge custom gtkui dialog buttons are not currently setup to
   use the Windows-style button ordering so disable this option.
2017-05-03 15:26:59 +01:00
Calum Lind
5427cbb73a Update translations 2017-05-03 10:51:59 +01:00
Calum Lind
d977915f32 [#2991] Fix display/setting single proxy in UIs
* Now copies all proxy settings from peer to other types to reflect
   how that the single undelying libtorrent proxy is set.
 * Grey-out the other proxies types in GTKUI to avoid some confusion.
2017-05-03 10:27:46 +01:00
Calum Lind
a86b6f0f8f [GTKUI] Fix column sort state not saved in Thinclient mode
* In torrentview.stop the listview is cleared however this meant in thinclient mode
   that listview sort details are empty and overwrites existing data when save_state is
   then called in torrentview.shutdown.
2017-05-02 12:40:20 +01:00
Calum Lind
3dfe6af1ee [Label] Fix namespace issue in js 2017-04-11 18:04:47 +01:00
Calum Lind
1f315a9ef0 Fix except statements for Py2.5 compat 2017-04-01 11:38:28 +01:00
Calum Lind
08c03d7678 [#2786] [GTKUI] Fix showing connection manager with malformed ip 2017-03-29 16:48:43 +01:00
Calum Lind
dd08cb29e5 [#3008] [Core] Fix datetime objects in tracker items breaking UIs 2017-03-29 14:43:19 +01:00
Calum Lind
909176e9aa [#2866] Tray menu rename All to Session 2017-03-29 14:43:04 +01:00
Calum Lind
85eeadcfca [Plugins] Add webui pref pages for Label and Autoadd
* Add info-only preference pages for these plugins in WebUI.
2017-03-29 14:42:02 +01:00
Calum Lind
f870741d9d [#2913] [Notifications] Fix webui passing string for port value 2017-03-20 18:51:43 +00:00
Calum Lind
cc69c9c85b [#2990] [Core] Fix torrent priorities mismatch
* The old priorities instead of updated call to lt were being saved to self.options.
2017-03-20 08:29:45 +00:00
Kyle Neideck
41acade01a [WebUI] Check render template files exist and raise 404 if not
- Check render/* requests match to .html files in the 'render' dir
 - Protects against directory (path) traversal
2017-03-16 17:34:26 +00:00
Calum Lind
9bec5142c7 Update Changelog and bump version to 1.3.14
- Minify js files
2017-03-06 09:56:20 +00:00
Calum Lind
0f1f62ec62 Update translation files 2017-03-06 09:53:07 +00:00
Calum Lind
318ab17986 [WebUI] Only accept application/json content-type requests
- Protects against CSRF (Cross-site request forgery)
2017-03-01 14:35:49 +00:00
Calum Lind
25150f13af [Core] Catch None type country in get_peers 2017-02-23 19:18:01 +00:00
Calum Lind
7cde3efb94 [#2976] [Console] Fix help showing usage details 2017-02-22 23:28:27 +00:00
Calum Lind
7f01dc909e [#2879] [OSX] Fix dyld error opening file from within Deluge
- Using DYLD_LIBRARY_PATH seems to have the unintended effect of making associated apps
   unusable (unable to locate dylds) when opening a file from within Deluge. The workaround
   for now is to switch to using DYLD_FALLBACK_LIBRARY_PATH.
2017-02-22 11:24:45 +00:00
Calum Lind
10ebf9b0b0 Fix mistakes in commit 42ba908
commit accidentally pushed before being tested
2017-02-21 10:36:04 +00:00
Calum Lind
3ba5443c76 [#2826] Fix create_torrent filedump not encoded 2017-02-20 22:40:14 +00:00
Calum Lind
c39f00fa0b [WebUI] Log successful logins with associated ip 2017-02-20 18:47:25 +00:00
Calum Lind
3962c41a55 [Core] Switch move_storage flag to dont_replace 2017-02-20 18:47:25 +00:00
Calum Lind
42ba9086d0 [#2956] Fix empty file_priorities with magnets 2017-02-20 16:25:08 +00:00
Calum Lind
2d4dec669e [AutoAdd] Remove duplicate magnet extension when splitting 2017-02-20 13:22:47 +00:00
Calum Lind
bcf0fe4a61 [#2957] [GTKUI] Fix AttributeError in torrentview column sort 2017-02-20 10:02:54 +00:00
Calum Lind
1dc4c465c7 [#2964] Fix TypeError when checking auth level in RPC Server
- self.factory.authorized_sessions requires tuple of (int, string).
2017-02-20 09:44:32 +00:00
Calum Lind
b52de1549e [Core] Fix adding magnet with trailing newline
* A bug in libtorrent means that a magnet with a trailing newline will be added
   but with an invalid info_hash. Strip any whitespace before add is a workaround.
2017-02-15 23:33:28 +00:00
Calum Lind
8a3f15e5c0 [Autoadd] Fixes for splitting magnets from file
* use splitlines to remove line endings so filter with len works as intended.
 * use a short form of the magnet hash so the resulting filename will be unique
   and prevent potential overwriting of other files.
 * verify magnet is valid
2017-02-14 18:37:20 +00:00
Calum Lind
8565eccb3d [#2149] [Core] Fix for overwriting single proxy in lt>=0.16 2017-01-30 10:36:33 +00:00
Calum Lind
30eaf775c2 [#2948] [Console] Fix decode error comparing non-ascii (str) torrent name 2017-01-18 11:11:00 +00:00
Calum Lind
ffb1316f09 [#2861] [Core] Add support for python-geoip 2017-01-17 09:28:46 +00:00
Calum Lind
bd80ad62a0 [Core] Fix return type in t.get_file_progress 2017-01-17 09:20:11 +00:00
Calum Lind
78851becf2 [#2946] Workaround 1.1 libtorrent default piece priority
* The default piece priority was changed in lt 1.1 from 1 to 4
   so in 1.3 we will simple convert them back to 1 as 4 is not used.
 * The set_file_priorities method was refactored to make the changes simpler.
2017-01-17 09:20:11 +00:00
Calum Lind
af76abb038 [UI] Fix usage of 'with Image.open' in trackericons
* Revert changes made to fix 'too many files open' as Image.open does
   not return a file descriptor and generated the following error:
     exceptions.AttributeError: 'NoneType' object has no attribute 'startswith'
 * Also fix style for raising an exception.
2017-01-11 22:56:05 +00:00
Calum Lind
bf01b53bda [WebUI] Fix missing self.interface attribute for 8a48ec012 2017-01-11 13:00:28 +00:00
Calum Lind
8a48ec0126 [#2951] [#1908] [WebUI] Add bind interface option for server 2017-01-10 20:20:44 +00:00
Calum Lind
c3a02e5291 [#2888] [WebUI] Fix shift-click in FilesTab 2017-01-10 17:30:35 +00:00
Calum Lind
3c1995476d [#2953] Fix except variable typo 2017-01-09 17:55:52 +00:00
Calum Lind
48cedf635f [GTKUI] [WEBUI] Add tracker_status translation to UIs 2016-11-30 13:29:33 +00:00
Calum Lind
0b4627be8a [#2941] Remove tracker_status translation markup from core
* A UnicodeDecodeError can occur if creating a string using translated
   text but we should not be translating anything in core anyway so
   remove the markup and do the translating in UI.
2016-11-30 13:29:33 +00:00
Calum Lind
739537f860 [#2942] Catch file_progress IndexError when checking a torrent 2016-11-30 11:54:59 +00:00
bendikro
df88c82265 [#2784] Fix typo in bugfix 5f92810f 2016-11-01 11:59:52 +00:00
bendikro
5394ac5604 [#2875][Web] Fix: WebUI Json dumps Error
* A torrent file contains an uncommon field 'filehash' which must be hex encoded
   to allow dumping the torrent info to json. Otherwise it will fail with:
   UnicodeDecodeError: 'utf8' codec can't decode byte 0xe5 in position 0: invalid continuation byte
2016-10-19 11:58:03 +01:00
Calum Lind
f739269dfd [GTKUI] Autofill infohash entry from clipboard 2016-10-18 19:20:47 +01:00
Calum Lind
f57ee74ee2 [#2901] [GTKUI] Strip whitespace from infohash entry before checks
* Copy-pasting from web page can include extra space at end of string.
 * Also make minor change to populate the magnet name with infohash
   for nicer UI display.
2016-10-18 19:14:51 +01:00
Calum Lind
798f5e2deb [#2889] Fix for UnboundLocalError in exception handler 2016-10-17 12:49:22 +01:00
Calum Lind
a7fe4d4510 [#2889] Fixes for 'Too many files open' error
* Ensure all file descriptors are closed. Using the with statement ensures
   closure and adding 'from __future__ ...' for Python 2.5 compatibility.
 * The main problem was with thousands of unclosed file desciptors from
   tracker_icons mkstemp.
 * Use a prefix 'deluge_ticon.' to identify created tracker_icon tmp files.
2016-10-17 12:46:28 +01:00
Calum Lind
6c73105a73 [#2882] [Core] Nicer log message about missing GeoIP support in lt 1.1.1 2016-09-28 10:36:06 +01:00
Calum Lind
e66be42c81 [#2768] [GTKUI] [OSX] Fix invalid file error at startup
When installed to the system, not using .app, error is raised on startup
as nsapp_open_file is ignoring Deluge-bin but not deluge or deluge-gtk for
potential 'filename' when connecting NSApplicationOpenFile.
2016-07-20 20:31:40 +01:00
Calum Lind
2263463114 Bump version to 1.3.13 and update dates 2016-07-20 15:23:28 +01:00
Calum Lind
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
Calum Lind
85fdacc0e7 [Changelog] Update with recent changes 2016-07-19 17:38:15 +01:00
Calum Lind
869dbab459 [WebUI] Compress javascript files 2016-07-19 17:37:43 +01:00
Calum Lind
852b51f224 [#2852] Set maximum supported libtorrent version to 1.0.x 2016-07-19 15:20:38 +01:00
Calum Lind
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
Calum Lind
904a51835b [#2857] [Notification] Fix issues with SMTP port input 2016-07-19 12:56:33 +01:00
Calum Lind
d38b8fc45c [#2855] [WebUI] Unable to add UDP trackers 2016-07-19 11:50:26 +01:00
Calum Lind
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
Calum Lind
34e12fcb38 [Translations] Update po's, pot and gettext.js 2016-06-19 12:30:30 +01:00
Calum Lind
f769afd3ac [WebUI] Compress javascript 2016-06-19 12:30:29 +01:00
Calum Lind
e1d78c3de6 [Changelog] Add recent changes 2016-06-19 12:30:05 +01:00
Calum Lind
15a4023208 [#2077] [Extractor] Ignore the remaining rar part files
* Bump version to 0.6
2016-06-10 16:14:52 +01:00
Calum Lind
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
Calum Lind
1a11e085b2 [#2828] [Packaging] Fix ImportError with setuptools version > 18.8 2016-05-19 17:23:07 +01:00
TannerMoore
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
Calum Lind
aa10e084a4 [Scheduler] Bump to version 0.3 2016-05-12 11:41:00 +01:00
Calum Lind
b2be4aba53 [#2796] [Console] Add time_added to info sort keys 2016-05-10 14:17:37 +01:00
Calum Lind
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
Calum Lind
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
Calum Lind
ad58fca1f9 [#2790] Ensure base32 magnet hash is uppercase 2016-05-09 23:24:59 +01:00
Calum Lind
f221ae53eb [#2832] [UI] Skip blank lines in auth file 2016-05-09 16:40:02 +01:00
Calum Lind
5590c31ace [Daemon] Fix unable to use uppercase log level 2016-04-27 08:20:58 +01:00
bendikro
4e5754b285 [Core] Fix UnboundLocalError in torrentmanager 2016-04-07 11:00:16 +01:00
Andrew Resch
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
Calum Lind
77f8449c0c [#2767] [Packaging] Don't include .py files in OSX App 2015-12-11 18:50:49 +00:00
bendikro
be7ad16a3f [#2783] [GTKUI] Case insensitive sort for name column 2015-12-11 18:02:10 +00:00
Calum Lind
e28954f63e Update Changelog 2015-12-11 14:27:11 +00:00
Calum Lind
52e60ac5b0 [#2782] [WebUI] Fix HTTPS negotiating incorrect cipher 2015-12-11 11:53:52 +00:00
Calum Lind
6ffe5cd2a4 [Core] Improve logging in update_state 2015-12-11 11:52:59 +00:00
Calum Lind
9038357d78 [OSX] Fix starting deluged from connection manager 2015-12-10 21:31:37 +00:00
Calum Lind
d56f6cb4f1 [Core] Increase RSA key size 2015-12-10 09:47:41 +00:00
Calum Lind
5d301a4b33 [Core] Fix move_storage exception handling 2015-12-09 19:02:18 +00:00
Calum Lind
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
Calum Lind
1bdc99ded7 [GTKUI] Fix installing plugin from non-ascii path 2015-11-27 13:41:06 +00:00
Calum Lind
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
Calum Lind
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
Calum Lind
0260e34189 [#2485] [WebUI] Fix unconnected Options in context menu 2015-11-23 23:20:45 +00:00
Calum Lind
5464cf674a [#2777] Update MSVC SP1 check to latest release CLID 2015-11-15 18:43:04 +00:00
Calum Lind
a58ce30e7b [#2738] [Core] Fix illegal argument with torrent_handle.set_max_connections 2015-11-15 14:17:00 +00:00
Calum Lind
83cecc0c09 [Core] Put back translation markup for tracker error 2015-11-05 22:59:17 +00:00
Calum Lind
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
Calum Lind
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
Calum Lind
69a1f5f210 [GTKUI] Don't display percentage for Error'd torrents 2015-10-28 15:35:36 +00:00
Calum Lind
0a74812eeb [Console] Fix adding non-ascii torrent in non-interactive mode 2015-10-28 15:35:35 +00:00
Calum Lind
cf437b6a33 [Core] Add handle.clear_error to resume 2015-10-28 15:35:35 +00:00
Calum Lind
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
Calum Lind
34e92b9f12 [Core] Add fastresume rejected alert handler 2015-10-20 15:32:15 +01:00
Calum Lind
86b1b75fb8 [#2772] [GTKUI] Fix GtkWarning with unknown pango markup 2015-10-18 18:47:32 +01:00
Calum Lind
4b9dcf377c [Core] Fix Twisted AlreadyCalled error on shutdown 2015-10-07 00:18:40 +01:00
Calum Lind
560318a5a7 [#2703] [Core] Stop moving files if target files exist 2015-09-29 23:18:04 +01:00
Calum Lind
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
Calum Lind
f9b7892976 [Core] Reset trackers in force_recheck only if paused 2015-09-29 19:05:50 +01:00
Calum Lind
5f5b6fad0b Fix indention error in move_storage 2015-09-29 18:51:52 +01:00
Calum Lind
5c545c5e0b [Core] Fix torrent displaying wrong state 2015-09-29 18:44:52 +01:00
Calum Lind
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
Calum Lind
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
Calum Lind
ad7e519fb2 [Core] Minor correction to session resume 2015-09-28 12:37:15 +01:00
Calum Lind
df57c7f924 [#2729] [Blocklist] Fix plugin lockup with empty url 2015-09-28 11:56:32 +01:00
Calum Lind
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
Calum Lind
eab7850ed6 [Core] Return all plugin status keys with empty list 2015-09-28 11:30:33 +01:00
Calum Lind
542e028977 [#2236] [Core] Fix filter keyerror removing plugin 2015-09-26 12:58:52 +01:00
Calum Lind
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
Calum Lind
d7e6afb01e [#2435] [GTKUI] Prevent user changing selection when editing tracker 2015-09-25 17:45:59 +01:00
Calum Lind
e1dcf378c3 [#2705] [WebUI] Fix hostlist not being created 2015-09-25 13:56:39 +01:00
Calum Lind
697c22a46c [#2765] Add support for TLS SNI in httpdownloader 2015-09-25 13:56:39 +01:00
Calum Lind
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
Calum Lind
72d381a3b6 Fix data_files check in setup.py 2015-09-20 18:41:24 +01:00
Calum Lind
59c2520e0d [Packaging] Revert unintended changes to osx scripts 2015-09-20 15:48:03 +01:00
Calum Lind
58d385241f [#2762] [GTKUI] Use correct column types for data 2015-09-20 15:39:04 +01:00
Calum Lind
58059300bd [#2763] [GTKUI] Fix unhandled error with invalid magnet uri 2015-09-20 15:19:57 +01:00
Calum Lind
e4f2a450d6 [#2764] [Scheduler] Fix corrupt plugin prefs page on osx 2015-09-20 14:59:33 +01:00
Calum Lind
64bba77807 [Packaging] Minor osx updates 2015-09-20 01:50:05 +01:00
Calum Lind
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
Calum Lind
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
Calum Lind
0a01aa28b0 [#2402] [Notification] Fix popup to show actual count of files finished 2015-09-18 22:44:58 +01:00
bendikro
bfb202086d [#2754] [GTKUI] Fix Deluge isn't sorting torrents properly 2015-09-17 22:26:24 +01:00
bendikro
6032c25813 [#2696] [GTKUI] Fix incorrect destination folder shown in GTK UI 2015-09-17 11:28:56 +02:00
Calum Lind
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
Calum Lind
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
Calum Lind
1b974d1061 [Win32] Fix icon path and output exes in bbfreeze 2015-09-13 22:47:31 +01:00
Calum Lind
602a913fa3 Bump version to 1.3.12 2015-09-13 21:32:11 +01:00
Calum Lind
6a8f24e973 Fix icon paths in setup 2015-09-13 21:32:10 +01:00
Calum Lind
fde46885e9 Update Translation files 2015-09-12 11:56:27 +01:00
Calum Lind
7223a51ba5 [WebUI] Lint and minify 2015-09-12 11:35:50 +01:00
Calum Lind
8ac65d77e0 Update ChangeLog 2015-09-10 15:01:52 +01:00
Calum Lind
65ebcf5384 [#2325] [Packaging] Fix uninstaller deleting non-deluge files 2015-09-10 14:43:32 +01:00
Calum Lind
53caeb4565 [Packaging] bbfreeze updates
* No need for data_files to be installed on windows
2015-09-10 14:39:25 +01:00
Calum Lind
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
Calum Lind
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
Calum Lind
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
Calum Lind
7ef9e3dbe0 Check for private flag on duplicate added torrent 2015-08-31 00:47:19 +01:00
Calum Lind
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
Calum Lind
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
Calum Lind
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
Calum Lind
84aca3c009 [Packaging] Fix typo in bbfreeze 2015-08-26 17:27:28 +01:00
Calum Lind
9662ccf486 Use just Taiwan in countries list 2015-08-25 16:30:25 +01:00
Calum Lind
83719e8404 [Win32] Updated bbreeze script from develop branch 2015-08-24 15:56:51 +01:00
doadin
04d90903a6 [#2758] [win32] Include _cffi_backend module in bbfreeze 2015-08-24 15:42:44 +01:00
Calum Lind
f599b883cf [win32] Update packaging scripts
* Update directory paths.
2015-08-24 15:40:55 +01:00
mohd-akram
bef71e60b3 [#2734] Add 256x256 to deluge.ico 2015-08-24 15:37:16 +01:00
Calum Lind
acf4fc4193 [#2233] [lp:#1487704] Fix AttributeError in set_trackers with lt 1.0 2015-08-22 15:31:26 +01:00
Calum Lind
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
Calum Lind
0516e3df45 Update author name as per request 2015-08-17 23:05:34 +01:00
Benjamin Dykstra
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
Calum Lind
907109b8bc Update man pages 2015-08-14 13:22:23 +01:00
Calum Lind
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
Calum Lind
16faa26124 [GTKUI] Improve About dialog copyright format for translators 2015-08-13 23:03:26 +01:00
Calum Lind
ebabd20c98 Remove stray tab in label plugin text 2015-08-09 12:17:47 +01:00
Andrew Resch
d40dfcd53c Fix for Twisted 15.0 URI class rename 2015-02-23 12:39:40 +00:00
537 changed files with 24819 additions and 20786 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

156
ChangeLog
View File

@@ -1,3 +1,159 @@
=== Deluge 1.3.16 (unreleased) ===
==== Core ====
* Fix saving copy of torrent file for magnet links.
=== Deluge 1.3.15 (12 May 2017) ===
==== Core ====
* #2991: Fix issues with displaying libtorrent single proxy.
* #3008: Fix libtorrent 1.2 trackers crashing Deluge UIs.
* #2990: Fix error in torrent priorities causing file priority mismatch in UIs.
==== GtkUI ====
* #3012: Configure gtkrc to use consistent button ordering on Windows.
* Fix column sort state not saved in Thinclient mode.
* #2786: Fix connection manager error with malformed ip.
* #2866: Rename SystemTray/Indicator 'Pause/Resume All' to 'Pause/Resume Session'.
* #2991: Workaround lt single proxy by greying out unused proxy types.
==== WebUI ====
* Security Fix: Check render template files exist otherwise raise 404.
==== Notification Plugin ====
* #2913: Fix webui passing string for int port value.
==== AutoAdd Plugin ====
* Add WebUI preferences page detailing lack of configuration via WebUI.
==== Label Plugin ====
* Add WebUI preferences page detailing how to configure plugin.
=== Deluge 1.3.14 (6 March 2017) ===
==== Core ====
* #2889: Fixed 'Too many files open' errors.
* #2861: Added support for python-geoip for use with libtorrent 1.1.
* #2149: Fixed a single proxy entry being overwritten resulting in no proxy set.
==== UI ====
* Added tracker_status translation to UIs.
==== GtkUI ====
* #2901: Strip whitespace from infohash before checks.
* Add missed feature autofill infohash entry from clipboard.
==== WebUI ====
* #1908: Backport bind interface option for server.
* Security: Fixed WebUI CSRF Vulnerability.
==== ConsoleUI ====
* [#2948] [Console] Fix decode error comparing non-ascii (str) torrent name.
==== AutoAdd Plugin ====
* Fixes for splitting magnets from file.
* Remove duplicate magnet extension when splitting.
=== 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

@@ -12,6 +12,7 @@
* geoip-database (optional)
* setproctitle (optional)
* pillow (optional)
* python-geoip (optional)
* libtorrent (rasterbar) >= 0.14

View File

@@ -36,6 +36,8 @@
"""Common functions for various parts of Deluge to use."""
from __future__ import with_statement
import os
import time
import subprocess
@@ -177,10 +179,11 @@ def get_default_download_dir():
if not windows_check():
from xdg.BaseDirectory import xdg_config_home
try:
for line in open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r'):
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
download_dir = os.path.expandvars(line.partition("=")[2].rstrip().strip('"'))
break
with open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r') as _file:
for line in _file:
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
download_dir = os.path.expandvars(line.partition("=")[2].rstrip().strip('"'))
break
except IOError:
pass
@@ -695,7 +698,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 +721,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

@@ -67,6 +67,8 @@ version as this will be done internally.
"""
from __future__ import with_statement
import cPickle as pickle
import shutil
import os
@@ -109,8 +111,13 @@ def find_json_objects(s):
if start < 0:
return []
quoted = False
for index, c in enumerate(s[offset:]):
if c == "{":
if c == '"':
quoted = not quoted
elif quoted:
continue
elif c == "{":
opens += 1
elif c == "}":
opens -= 1
@@ -356,7 +363,8 @@ what is currently in the config and it could not convert the value
filename = self.__config_file
try:
data = open(filename, "rb").read()
with open(filename, "rb") as _file:
data = _file.read()
except IOError, e:
log.warning("Unable to open config file %s: %s", filename, e)
return
@@ -404,7 +412,8 @@ what is currently in the config and it could not convert the value
# Check to see if the current config differs from the one on disk
# We will only write a new config file if there is a difference
try:
data = open(filename, "rb").read()
with open(filename, "rb") as _file:
data = _file.read()
objects = find_json_objects(data)
start, end = objects[0]
version = json.loads(data[start:end])

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

@@ -33,6 +33,8 @@
#
#
from __future__ import with_statement
import os
import random
import stat
@@ -118,7 +120,8 @@ class AuthManager(component.Component):
f = [localclient]
else:
# Load the auth file into a dictionary: {username: password, ...}
f = open(auth_file, "r").readlines()
with open(auth_file, "r") as _file:
f = _file.readlines()
for line in f:
line = line.strip()
@@ -143,4 +146,5 @@ class AuthManager(component.Component):
self.__auth[username.strip()] = (password.strip(), level)
if "localclient" not in self.__auth:
open(auth_file, "a").write(self.__create_localclient_account())
with open(auth_file, "a") as _file:
_file.write(self.__create_localclient_account())

View File

@@ -33,6 +33,8 @@
#
#
from __future__ import with_statement
from deluge._libtorrent import lt
import os
@@ -72,22 +74,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()
@@ -97,6 +107,8 @@ class Core(component.Component):
self.settings.user_agent = "Deluge %s" % deluge.common.get_version()
# Increase the alert queue size so that alerts don't get lost
self.settings.alert_queue_size = 10000
# Ignore buggy resume data timestamps checking #3044.
self.settings.ignore_resume_timestamps = True
# Set session settings
self.settings.send_redundant_have = True
@@ -108,11 +120,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()
@@ -127,6 +140,9 @@ class Core(component.Component):
# New release check information
self.new_release = None
# GeoIP instance with db loaded
self.geoip_instance = None
# Get the core config
self.config = deluge.configmanager.ConfigManager("core.conf")
@@ -177,8 +193,8 @@ class Core(component.Component):
def __load_session_state(self):
"""Loads the libtorrent session state"""
try:
self.session.load_state(lt.bdecode(
open(deluge.configmanager.get_config_dir("session.state"), "rb").read()))
with open(deluge.configmanager.get_config_dir("session.state"), "rb") as _file:
self.session.load_state(lt.bdecode(_file.read()))
except Exception, e:
log.warning("Failed to load lt state: %s", e)
@@ -405,15 +421,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 +449,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 +514,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]
@@ -647,7 +668,9 @@ class Core(component.Component):
if add_to_session:
options = {}
options["download_location"] = os.path.split(path)[0]
self.add_torrent_file(os.path.split(target)[1], open(target, "rb").read(), options)
with open(target, "rb") as _file:
filedump = base64.encodestring(_file.read())
self.add_torrent_file(os.path.split(target)[1], filedump, options)
@export
def upload_plugin(self, filename, filedump):

View File

@@ -32,6 +32,8 @@
# statement from all source files in the program, then also delete it here.
#
from __future__ import with_statement
import os
import gettext
import locale
@@ -52,7 +54,8 @@ class Daemon(object):
if os.path.isfile(deluge.configmanager.get_config_dir("deluged.pid")):
# Get the PID and the port of the supposedly running daemon
try:
(pid, port) = open(deluge.configmanager.get_config_dir("deluged.pid")).read().strip().split(";")
with open(deluge.configmanager.get_config_dir("deluged.pid")) as _file:
(pid, port) = _file.read().strip().split(";")
pid = int(pid)
port = int(port)
except ValueError:
@@ -133,9 +136,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:
@@ -163,8 +172,8 @@ class Daemon(object):
if not classic:
# Write out a pid file all the time, we use this to see if a deluged is running
# We also include the running port number to do an additional test
open(deluge.configmanager.get_config_dir("deluged.pid"), "wb").write(
"%s;%s\n" % (os.getpid(), port))
with open(deluge.configmanager.get_config_dir("deluged.pid"), "wb") as _file:
_file.write("%s;%s\n" % (os.getpid(), port))
component.start()
try:

View File

@@ -91,7 +91,7 @@ def tracker_error_filter(torrent_ids, values):
# Check all the torrent's tracker_status for 'Error:' and only return torrent_ids
# that have this substring in their tracker_status
for torrent_id in torrent_ids:
if _("Error") + ":" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
if "Error:" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
@@ -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

@@ -33,6 +33,7 @@
#
#
from __future__ import with_statement
import os.path
import threading
@@ -48,6 +49,12 @@ import deluge.common
import deluge.component as component
from deluge.log import LOG as log
try:
import GeoIP
except ImportError:
GeoIP = None
DEFAULT_PREFS = {
"send_info": False,
"info_sent": 0.0,
@@ -144,6 +151,8 @@ DEFAULT_PREFS = {
}
class PreferencesManager(component.Component):
LT_SINGLE_PROXY = deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.0.0")
def __init__(self):
component.Component.__init__(self, "PreferencesManager")
@@ -298,7 +307,8 @@ class PreferencesManager(component.Component):
if value:
state = None
try:
state = lt.bdecode(open(state_file, "rb").read())
with open(state_file, "rb") as _file:
state = lt.bdecode(_file.read())
except Exception, e:
log.warning("Unable to read DHT state file: %s", e)
@@ -309,6 +319,8 @@ class PreferencesManager(component.Component):
self.session.start_dht(None)
self.session.add_dht_router("router.bittorrent.com", 6881)
self.session.add_dht_router("router.utorrent.com", 6881)
self.session.add_dht_router("dht.transmissionbt.com", 6881)
self.session.add_dht_router("dht.aelitis.com", 6881)
self.session.add_dht_router("router.bitcomet.com", 6881)
else:
self.core.save_dht_state()
@@ -337,11 +349,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)
@@ -471,15 +482,34 @@ class PreferencesManager(component.Component):
self.new_release_timer.stop()
def _on_set_proxies(self, key, value):
for k, v in value.items():
# Test for single proxy with lt >= 0.16
if self.LT_SINGLE_PROXY:
for proxy_type in value:
if proxy_type == "peer":
continue
if self.config["proxies"][proxy_type] != value["peer"]:
log.warning("This version of libtorrent only supports a single proxy setting "
"based upon 'peer' which will apply to all other other types.")
self.config["proxies"][proxy_type] = value["peer"]
proxy_settings = lt.proxy_settings()
proxy_settings.type = lt.proxy_type(v["type"])
proxy_settings.username = str(v["username"])
proxy_settings.password = str(v["password"])
proxy_settings.hostname = str(v["hostname"])
proxy_settings.port = v["port"]
log.debug("setting %s proxy settings", k)
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
proxy_settings.type = lt.proxy_type(value["peer"]["type"])
proxy_settings.username = str(value["peer"]["username"])
proxy_settings.password = str(value["peer"]["password"])
proxy_settings.hostname = str(value["peer"]["hostname"])
proxy_settings.port = value["peer"]["port"]
log.debug("Setting proxy settings: %s", value["peer"])
self.session.set_proxy(proxy_settings)
else:
for k, v in value.items():
proxy_settings = lt.proxy_settings()
proxy_settings.type = lt.proxy_type(v["type"])
proxy_settings.username = str(v["username"])
proxy_settings.password = str(v["password"])
proxy_settings.hostname = str(v["hostname"])
proxy_settings.port = v["port"]
log.debug("Setting %s proxy settings: %s", k, v)
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
def _on_rate_limit_ip_overhead(self, key, value):
log.debug("%s: %s", key, value)
@@ -488,21 +518,19 @@ class PreferencesManager(component.Component):
def _on_geoip_db_location(self, key, value):
log.debug("%s: %s", key, value)
# Load the GeoIP DB for country look-ups if available
geoip_db = ""
if os.path.exists(value):
geoip_db = value
elif os.path.exists(pkg_resources.resource_filename("deluge", os.path.join("data", "GeoIP.dat"))):
geoip_db = pkg_resources.resource_filename("deluge", os.path.join("data", "GeoIP.dat"))
try:
self.core.geoip_instance = GeoIP.open(value, GeoIP.GEOIP_STANDARD)
except AttributeError:
try:
self.session.load_country_db(value)
except RuntimeError, ex:
log.error("Unable to load geoip database: %s", ex)
except AttributeError:
log.warning("GeoIP Unavailable")
else:
log.warning("Unable to find GeoIP database file!")
if geoip_db:
try:
self.session.load_country_db(str(geoip_db))
except Exception, e:
log.error("Unable to load geoip database!")
log.exception(e)
def _on_cache_size(self, key, value):
log.debug("%s: %s", key, value)
self.session_set_setting("cache_size", value)

View File

@@ -35,6 +35,8 @@
"""RPCServer Module"""
from __future__ import with_statement
import sys
import zlib
import os
@@ -202,8 +204,8 @@ class DelugeRPCProtocol(Protocol):
"""
peer = self.transport.getPeer()
log.info("Deluge Client connection made from: %s:%s", peer.host, peer.port)
# Set the initial auth level of this session to AUTH_LEVEL_NONE
self.factory.authorized_sessions[self.transport.sessionno] = AUTH_LEVEL_NONE
# Set the initial auth level of this session to AUTH_LEVEL_NONE and empty username.
self.factory.authorized_sessions[self.transport.sessionno] = (AUTH_LEVEL_NONE, "")
def connectionLost(self, reason):
"""
@@ -493,10 +495,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 +511,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())
@@ -517,8 +519,10 @@ def generate_ssl_keys():
# Write out files
ssl_dir = deluge.configmanager.get_config_dir("ssl")
open(os.path.join(ssl_dir, "daemon.pkey"), "w").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
open(os.path.join(ssl_dir, "daemon.cert"), "w").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
with open(os.path.join(ssl_dir, "daemon.pkey"), "w") as _file:
_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
with open(os.path.join(ssl_dir, "daemon.cert"), "w") as _file:
_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
# Make the files only readable by this user
for f in ("daemon.pkey", "daemon.cert"):
os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE)

View File

@@ -34,6 +34,8 @@
"""Internal Torrent class"""
from __future__ import with_statement
import os
import time
from urllib import unquote
@@ -98,6 +100,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.
"""
@@ -134,13 +144,16 @@ class Torrent(object):
# We store the filename just in case we need to make a copy of the torrentfile
if not filename:
# If no filename was provided, then just use the infohash
filename = self.torrent_id
filename = self.torrent_id + '.torrent'
self.filename = filename
# 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()
@@ -170,26 +183,20 @@ class Torrent(object):
self.filename = state.filename
self.is_finished = state.is_finished
else:
# Tracker list
self.trackers = []
# Create a list of trackers
for value in self.handle.trackers():
if lt.version_major == 0 and lt.version_minor < 15:
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
else:
tracker = value
self.trackers.append(tracker)
# Set trackers from libtorrent
self.set_trackers(None)
# 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 +216,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 +240,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)
@@ -292,19 +301,19 @@ class Torrent(object):
self.options["move_completed_path"] = move_completed_path
def set_file_priorities(self, file_priorities):
if len(file_priorities) != len(self.get_files()):
log.debug("file_priorities len != num_files")
self.options["file_priorities"] = self.handle.file_priorities()
return
if self.options["compact_allocation"]:
log.debug("setting file priority with compact allocation does not work!")
self.options["file_priorities"] = self.handle.file_priorities()
return
handle_file_priorities = self.handle.file_priorities()
# Workaround for libtorrent 1.1 changing default priorities from 1 to 4.
if 4 in handle_file_priorities:
handle_file_priorities = [1 if x == 4 else x for x in handle_file_priorities]
log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
self.handle.prioritize_files(file_priorities)
if (self.handle.has_metadata() and not self.options["compact_allocation"] and
file_priorities and len(file_priorities) == len(self.get_files())):
self.handle.prioritize_files(file_priorities)
else:
log.debug("Unable to set new file priorities.")
file_priorities = handle_file_priorities
if 0 in self.options["file_priorities"]:
# We have previously marked a file 'Do Not Download'
@@ -323,14 +332,19 @@ 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
# These unused lt 1.1.2 tracker datetime entries need to be None for rencode.
tracker["min_announce"] = tracker["next_announce"] = None
trackers.append(tracker)
self.trackers = trackers
self.tracker_host = None
@@ -339,10 +353,13 @@ class Torrent(object):
log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
tracker_list = []
for tracker in trackers:
for idx, tracker in enumerate(trackers):
new_entry = lt.announce_entry(tracker["url"])
new_entry.tier = tracker["tier"]
tracker_list.append(new_entry)
# These unused lt 1.1.2 tracker datetime entries need to be None for rencode.
trackers[idx]["min_announce"] = trackers[idx]["next_announce"] = None
self.handle.replace_trackers(tracker_list)
# Print out the trackers
@@ -350,7 +367,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 +380,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 +443,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:
@@ -499,13 +552,15 @@ class Torrent(object):
except UnicodeDecodeError:
client = str(peer.client).decode("latin-1")
# Make country a proper string
country = str()
for c in peer.country:
if not c.isalpha():
country += " "
else:
country += c
try:
country = component.get("Core").geoip_instance.country_code_by_addr(peer.ip[0])
except AttributeError:
country = peer.country
try:
country = "".join([char if char.isalpha() else " " for char in country])
except TypeError:
country = ""
ret.append({
"client": client,
@@ -523,10 +578,21 @@ class Torrent(object):
"""Returns the torrents queue position"""
return self.handle.queue_position()
def get_file_priorities(self):
"""Return the file priorities"""
if not self.handle.has_metadata():
return []
if not self.options["file_priorities"]:
# Ensure file_priorities option is populated.
self.set_file_priorities([])
return self.options["file_priorities"]
def get_file_progress(self):
"""Returns the file progress as a list of floats.. 0.0 -> 1.0"""
if not self.handle.has_metadata():
return 0.0
return []
file_progress = self.handle.file_progress()
ret = []
@@ -535,6 +601,8 @@ class Torrent(object):
ret.append(float(file_progress[i]) / float(f["size"]))
except ZeroDivisionError:
ret.append(0.0)
except IndexError:
return []
return ret
@@ -617,7 +685,6 @@ class Torrent(object):
"compact": self.options["compact_allocation"],
"distributed_copies": distributed_copies,
"download_payload_rate": self.status.download_payload_rate,
"file_priorities": self.options["file_priorities"],
"hash": self.torrent_id,
"is_auto_managed": self.options["auto_managed"],
"is_finished": self.is_finished,
@@ -716,6 +783,7 @@ class Torrent(object):
fns = {
"comment": ti_comment,
"eta": self.get_eta,
"file_priorities": self.get_file_priorities,
"file_progress": self.get_file_progress,
"files": self.get_files,
"is_seed": self.handle.is_seed,
@@ -776,6 +844,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 +869,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 +894,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 +911,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, ex:
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'] = 2 # dont_replace
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 +945,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"]:
@@ -886,7 +970,13 @@ class Torrent(object):
md = lt.bdecode(self.torrent_info.metadata())
torrent_file = {}
torrent_file["info"] = md
open(path, "wb").write(lt.bencode(torrent_file))
with open(path, "wb") as _file:
_file.write(lt.bencode(torrent_file))
if self.config["copy_torrent_file"]:
config_dir = self.config['torrentfiles_location']
filepath = os.path.join(config_dir, self.filename)
with open(filepath, "wb") as _file:
_file.write(lt.bencode(torrent_file))
except Exception, e:
log.warning("Unable to save torrent file: %s", e)
@@ -923,16 +1013,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 +1082,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

@@ -111,10 +111,22 @@ class TorrentState:
self.move_completed = move_completed
self.move_completed_path = move_completed_path
def __eq__(self, other):
return isinstance(other, TorrentState) and self.__dict__ == other.__dict__
def __ne__(self, other):
return not self == other
class TorrentManagerState:
def __init__(self):
self.torrents = []
def __eq__(self, other):
return isinstance(other, TorrentManagerState) and self.torrents == other.torrents
def __ne__(self, other):
return not self == other
class TorrentManager(component.Component):
"""
TorrentManager contains a list of torrents in the current libtorrent
@@ -157,6 +169,9 @@ class TorrentManager(component.Component):
# Workaround to determine if TorrentAddedEvent is from state file
self.session_started = False
# Keep the previous saved state
self.prev_saved_state = None
# Register set functions
self.config.register_set_function("max_connections_per_torrent",
self.on_set_max_connections_per_torrent)
@@ -202,6 +217,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 +231,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 +410,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
@@ -467,7 +486,8 @@ class TorrentManager(component.Component):
handle = None
try:
if magnet:
handle = lt.add_magnet_uri(self.session, utf8_encoded(magnet), add_torrent_params)
magnet_uri = utf8_encoded(magnet.strip())
handle = lt.add_magnet_uri(self.session, magnet_uri, add_torrent_params)
else:
handle = self.session.add_torrent(add_torrent_params)
except RuntimeError, e:
@@ -491,6 +511,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 +652,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 +698,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,
@@ -702,27 +734,38 @@ class TorrentManager(component.Component):
)
state.torrents.append(torrent_state)
# If the state hasn't changed, no need to save it
if self.prev_saved_state == state:
return
# 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"))
except IOError:
log.warning("Unable to save state file.")
return True
os.rename(filepath_tmp, filepath)
self.prev_saved_state = state
except IOError, ex:
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 +785,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 +822,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 +834,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 +997,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 +1021,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()
@@ -976,7 +1035,7 @@ class TorrentManager(component.Component):
# Set the tracker status for the torrent
if alert.message() != "Got peers from DHT":
torrent.set_tracker_status(_("Announce OK"))
torrent.set_tracker_status("Announce OK")
# Check to see if we got any peer information from the tracker
if alert.handle.status().num_complete == -1 or \
@@ -992,7 +1051,7 @@ class TorrentManager(component.Component):
return
# Set the tracker status for the torrent
torrent.set_tracker_status(_("Announce Sent"))
torrent.set_tracker_status("Announce Sent")
def on_alert_tracker_warning(self, alert):
log.debug("on_alert_tracker_warning")
@@ -1000,18 +1059,24 @@ class TorrentManager(component.Component):
torrent = self.torrents[str(alert.handle.info_hash())]
except:
return
tracker_status = '%s: %s' % (_("Warning"), decode_string(alert.message()))
tracker_status = '%s: %s' % ("Warning", decode_string(alert.message()))
# Set the tracker status for the torrent
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("Error: %s" % error_message)
def on_alert_storage_moved(self, alert):
log.debug("on_alert_storage_moved")
@@ -1037,6 +1102,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 +1115,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 +1126,6 @@ class TorrentManager(component.Component):
except:
return
old_state = torrent.state
torrent.update_state()
# Torrent may need to download data after checking.
@@ -1072,10 +1133,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 +1161,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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 B

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 625 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 B

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 B

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 320 B

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