Compare commits

..

217 Commits

Author SHA1 Message Date
4748c96d26 Tag 1.2.0 release 2010-01-10 23:21:52 +00:00
79c78fa7ef Fix displaying new release dialog in Windows 2010-01-10 19:47:31 +00:00
89c9228cec Prep for release 2010-01-09 21:16:08 +00:00
0df79b03c5 Update translations 2010-01-09 20:41:51 +00:00
c54a50af34 Do not save_state right away, but rather wait for the timer loop 2010-01-08 23:36:44 +00:00
e81d2ea0bd Fix displaying root folder when using the 'path.utf-8' keys in torrents 2010-01-08 21:26:33 +00:00
5e19f91613 Add twisted.internet.utils to the freezer includes 2010-01-08 17:28:08 +00:00
5a35d60178 Use cStringIO to open zip files in python 2.5 2010-01-07 15:27:05 +00:00
34b2ada0db Rewrote call to use maybeDeferred (this handles methods that return deferreds).
Fixes #1114.
2010-01-07 00:45:47 +00:00
547cfa6339 put the default password in the manpage for those too lazy to type "deluge default web password" in google 2010-01-05 14:07:16 +00:00
3870c25c9d Fix #1117 can't use the '3' key 2010-01-04 00:18:53 +00:00
4cde35a067 Fix blocklist ignoring try_times.
Make blocklist print error messages on failures.
2010-01-03 16:27:05 +00:00
d5a11d0e95 Fix #1116 not being able to use command aliases when not connected to a daemon 2010-01-03 01:37:57 +00:00
1f8919b47f Fix #1115 not showing usage for the 'debug' command 2010-01-02 23:25:45 +00:00
59f05a890f Don't apply 'stop/remove on completed' rules to torrents in a Queued or Paused state 2009-12-28 21:50:13 +00:00
afbeaa6b7d Fix torrent info name not being utf-8 decoded when root file/folder name is blank 2009-12-26 17:09:05 +00:00
f5409e0d0c Have the .desktop file run 'deluge-gtk' 2009-12-23 23:54:00 +00:00
ac857eaef7 Change share ratio calculation to use the total done instead of the all time downloaded value. This change will make the share ratio calculation not use data downloaded in failed hash checks. 2009-12-23 18:44:00 +00:00
886f2d8682 Add in a '_' builtin method if unable to initialize gettext 2009-12-23 01:34:58 +00:00
fa79c37aa2 Fix possible TorrentFinishedEvents being emitted on startup for already completed torrents 2009-12-23 01:28:33 +00:00
6a8e023ffd Fix exceptions when gettext/locale cannot be initialized properly 2009-12-21 19:41:31 +00:00
6b396f1f77 More relaxed definition of what a commented line is in a blocklist.
Decorate readranges with raiseError rather than parse to increase performance.
2009-12-21 13:58:24 +00:00
b2990e89cb fix filtering on the tracker host, use == rather than in so tracker urls that contain another trackers url within them aren't picked up as well 2009-12-21 10:26:50 +00:00
7ebe07db15 Fix blocklist status icon not opening proper preferences page.
Show blocklist's file date using the locale's date / time representation.
2009-12-20 09:01:07 +00:00
14eb3b8b33 Fix some packing due to the last commit 2009-12-20 04:23:50 +00:00
cffc25aac8 Plugin preference pages will now expand to fill 2009-12-20 04:18:21 +00:00
ed2568c8b0 Fix displaying folders in the add torrent dialog 2009-12-20 04:02:41 +00:00
700fe2ec17 Fix file renaming 2009-12-20 03:45:14 +00:00
571b8a84af Update bbfreeze script to include wsgiref.handlers so that blocklist plugin will work 2009-12-18 04:59:04 +00:00
3d821fb80b Fix #1104, #735 use path.utf-8 if available 2009-12-18 04:36:42 +00:00
00c4b53b67 Remove MANIFEST.in file 2009-12-18 00:46:26 +00:00
9a9ae71564 Update translations 2009-12-18 00:01:36 +00:00
c9c57e0e32 Update pot file 2009-12-17 23:58:21 +00:00
c70a9875c5 Prep for release 2009-12-17 23:49:25 +00:00
c932de8470 Remove redundant variable. 2009-12-17 23:08:55 +00:00
0663da4424 Partial fix for #1103 if the per-torrent option for stopping at a ratio has been unchecked, then do
not stop it at the global setting.
2009-12-17 21:40:21 +00:00
8c265bb308 Fix #1095 incorrect piece size used when using some non-English languages 2009-12-17 21:27:04 +00:00
fe6c89a2e1 Upgrade older confs instead of just dying.
Fix major logic error when checking if an update is needed.
2009-12-16 13:46:39 +00:00
3c5d5aa16e Fix import on startup which got broken in previous commit. 2009-12-16 12:46:57 +00:00
389e9cdbb0 Fix blocklist not working for all locales (#1059). 2009-12-16 11:55:34 +00:00
193f1b85ca Apply #1100 sort plugins list by name 2009-12-15 17:37:05 +00:00
4daa2a098b Fix #1099 use triple quotes on docstrings 2009-12-15 06:36:02 +00:00
52817498cf Fix showing the remove torrent dialog twice if using the delete key and pressing cancel in the dialog 2009-12-14 23:25:03 +00:00
cc3f3495b6 Fix #768 save tracker list for create torrent dialog 2009-12-14 18:46:08 +00:00
4bd00aa331 Fix #1086 deprecated gtk.Tooltips usage 2009-12-14 18:20:51 +00:00
e27c38ca67 Fix #594 tray password dialog freeze in Windows
Made the password dialog prettier
2009-12-14 02:15:08 +00:00
eeac8efcf2 Fix issue where stoping a daemon that you aren't connected to causes the gtkui to shutdown the
currently connected daemon.
2009-12-14 00:48:18 +00:00
f59901c1be Fix #823 setting config values to -1.0 2009-12-13 22:47:15 +00:00
ee76075fa4 Add #891 remove torrents by pressing the Delete key 2009-12-13 22:16:05 +00:00
3f69b05ded disconnect the client if getting information fails 2009-12-13 21:50:16 +00:00
ecca68772e use metavar to change the output of --help by optparse 2009-12-13 21:49:10 +00:00
9878fced37 Fix issue where hosts will show up erroneously as Offline 2009-12-13 21:46:55 +00:00
c312cf4b91 Disconnect from daemon if getting the info fails 2009-12-13 21:34:26 +00:00
de262bf7db Fix autoconnecting to a host other than the first one in the list 2009-12-13 21:29:46 +00:00
8edd91a3d5 Add an error dialog when trying to add a duplicate host
Center the add host dialog over the connection manager
2009-12-13 21:25:22 +00:00
f6fdae727d Fix #1036 autoconnecting to localhost daemon on start-up 2009-12-13 20:49:23 +00:00
139d6538a2 Fix #782 do not ask for tray password when window is not minimized to tray 2009-12-12 22:38:26 +00:00
b88319b4be Fix #692 no longer require tray password when quitting from the tray icon while the window is
visible.
2009-12-12 21:22:51 +00:00
dd129f8544 reset the add torrent window on hide 2009-12-12 15:56:49 +00:00
af18bdb72a Fix typo 2009-12-11 23:29:21 +00:00
85b3546c47 Fix printing 'rm' command usage when called with no arguments 2009-12-11 21:19:48 +00:00
07cd4ba83d fix #1075 (changing priority on a whole folder) 2009-12-11 20:45:59 +00:00
361f2b2390 Fix possible exception when upgrading from a 0.5 state file 2009-12-10 21:28:51 +00:00
97a69b8c0c update some of the icons in the webui 2009-12-10 18:24:31 +00:00
29f2ed84dd disable the files and options tabs until a torrent has been selected 2009-12-10 17:45:40 +00:00
9e9fbd6242 fix uploading plugins when the daemon is not localhost 2009-12-10 17:35:35 +00:00
2840d43ccb fix the onResize method for the togglefield so it resizes the textfield to the correct size 2009-12-10 17:26:45 +00:00
14454791c4 implement installing plugins via the webui 2009-12-10 15:40:37 +00:00
fcbaa1f4f2 fix the upload_plugin method as it wasn't upgraded to deluge-rpc spec 2009-12-10 15:31:27 +00:00
c716f55dda fix incorrect class name 2009-12-10 14:39:14 +00:00
a1e950d6e5 remove the margin and padding from the bottom of the options fieldset 2009-12-10 14:35:10 +00:00
e646a5ef87 fix enabling plugins on the fly 2009-12-10 14:32:55 +00:00
3c703d2ef7 Fix using the console in Windows, but only in command-line mode 2009-12-10 01:26:26 +00:00
429f7064fa tweak the download prefs page so no scrollbars appear 2009-12-10 00:38:48 +00:00
47b5274f34 improve the ToggleField widget, having the checkbox to the left of the
field rather than above it
2009-12-10 00:32:24 +00:00
a6c6d5b2b4 add tooltips to the statusbar items
add disk free space to the statusbar
2009-12-10 00:06:34 +00:00
3d3edccdac Apply cookie date fix from adios 2009-12-09 13:13:04 +00:00
36b2a82561 fix a bug in the execute plugin when move_completed is used the incorrect path was passed in 2009-12-08 11:24:56 +00:00
f5096c6e7c tweak the sizes of some form elements to improve the layout 2009-12-08 02:09:03 +00:00
76bc32d72a begin the makings of the togglefield widget that allows enabling of move completed/copy torrent files/auto add torrent files fields 2009-12-08 01:56:15 +00:00
581517c8e9 Change the compressed js script to deluge-all.js to avoid naming conflicts on case sensitive filesystems 2009-12-01 17:11:07 +00:00
b41a025217 Add button to Other preferences to associate magnet links with Deluge 2009-11-30 03:50:28 +00:00
7a91b96183 Fix #1085 only use ints for specific options to prevent unhandled exception 2009-11-29 21:32:56 +00:00
a317fdec48 Fix crash in Windows when creating a torrent 2009-11-29 07:38:29 +00:00
20b6791670 Properly show 100.00% and reduce number of progress bar updates during a torrent creation 2009-11-29 07:37:29 +00:00
411ee123f1 Register just 'deluge' not '/usr/bin/deluge' in the magnet uri handler 2009-11-29 01:45:46 +00:00
2bc13a3fb7 Attempt to register as the default magnet uri handler in GNOME on startup 2009-11-29 01:40:30 +00:00
dda893e2b7 Add some checks for OSX since it doesn't support SVG icons 2009-11-29 00:52:00 +00:00
00de8311fd swap the order of the buttons in the remove window 2009-11-26 14:59:04 +00:00
6d7572c444 update the build script and rebuild deluge.js and ext-extensions.js 2009-11-25 18:52:00 +00:00
3e1a6a1fb8 exclude some more things from the sdist 2009-11-25 18:37:40 +00:00
dbbc1124f4 prevent undefineds ending up as a key in the option manager 2009-11-25 18:37:06 +00:00
13d949381a Set batch_write to False in __init__ 2009-11-25 18:24:29 +00:00
bfea47c8bd add the updated manifest to 1.2 2009-11-25 18:00:15 +00:00
f858e271ad Add magnet link association to the installer 2009-11-25 03:09:42 +00:00
708f052153 Prep for release 2009-11-25 02:41:15 +00:00
8d6bc283d0 Fix loading the saved metadata when loading state with magnet uris 2009-11-25 01:55:51 +00:00
313c73c40c Fix files list when using magnet uris 2009-11-25 01:41:50 +00:00
012dd1919c Update translations 2009-11-23 18:16:51 +00:00
d7e83d922c Re-commit. This change got lost on the mess of merging HG and SVN branches. 2009-11-22 06:17:55 +00:00
860ceccd52 Fix adding torrents with different metadata by storing the bencoded dict too. If we bencode the stored metadata dict, there is a chance the order of the dict will be different and change the info-hash. 2009-11-20 19:13:12 +00:00
907f618607 add the beginnings of a MANIFEST.in file for specifying which files to include in the sdist command
tweak setup.py
2009-11-19 21:20:17 +00:00
abcb76e959 rename deluge-yc.js to deluge.js 2009-11-19 20:27:36 +00:00
825edb1fd8 Update changelog 2009-11-19 04:51:28 +00:00
4cdf919076 Fix issues adding magnet uris 2009-11-19 04:51:19 +00:00
90a143a4ef Fix issue where some torrents with special characters could not be added 2009-11-19 02:39:07 +00:00
04f8f80b9b Fix exception on startup when the system tray icon is not enabled 2009-11-19 02:29:37 +00:00
266693a6f9 Remove maketorrent.py in 1.2 since we're not using it 2009-11-18 19:02:05 +00:00
2b9e1e5ff4 Change the event_list to be a dictionary of known_events and their docstrings 2009-11-16 00:39:15 +00:00
68fe9512df Add way to get a list of DelugeEvent classes 2009-11-14 23:42:04 +00:00
b10ea808fd Fix endless loop when trying to autoconnect to an offline daemon 2009-11-13 05:26:54 +00:00
234863b664 Fix autoconnecting to the next host in the list if the selected one isn't available 2009-11-13 05:22:38 +00:00
940bc4a3dc Add bug number to changelog 2009-11-13 01:08:41 +00:00
aa5f69b203 Fix #1071 issue where Deluge will fail to start if there is a stale ipc lockfile 2009-11-13 01:07:36 +00:00
5f4b39aeb8 Comment out a debug log 2009-11-10 18:28:27 +00:00
0e07cdf8b1 Reduce height of Add Torrent Dialog by ~80 pixels 2009-11-10 02:45:36 +00:00
64eb296c9d remove alpha from the title 2009-11-09 23:48:32 +00:00
fa40f38c1b Allow for colons in PeerGuardian/SafePeer lists' descriptions.
Check that the start & end range resembles an ip when checking a list's validity.
2009-11-09 08:54:13 +00:00
1b46d99620 Force blocklist to re-detect the format when a download is forced.
Move remove_zeros to common.py and simplify / speed up.
Change debug logging lines to be more uniform.
2009-11-09 01:52:58 +00:00
441c20491c Fix printing info, help, etc.. on the command line 2009-11-08 17:22:57 +00:00
8c7bbbf398 Fix disabling/enabling plugins after switching daemons 2009-11-08 04:04:40 +00:00
267905295c Fix #1067 import os 2009-11-06 19:22:13 +00:00
fb4495fc33 Only try to stop LoopingCalls if they are running 2009-11-05 04:35:17 +00:00
447da3b1b4 Fix deleting old .fastresume files with fresh configs 2009-11-05 03:44:46 +00:00
9af41fd457 allow commands that are .pyc files to be used 2009-11-04 23:49:07 +00:00
7e19a11521 run the path from the add command through os.path.expanduser 2009-11-04 23:36:27 +00:00
4da33c72cc add a basic move storage window that fixes #1057 2009-11-04 20:45:21 +00:00
d411f90a67 remove the connected call upon an update to reduce the number of ajax calls 2009-11-04 19:54:59 +00:00
3179ff2060 Allow the 'help' command to be called when not connected to a daemon 2009-11-03 22:08:18 +00:00
b6dc72218d remove accidentally left in debug line 2009-11-03 14:48:24 +00:00
e884d2cbfa make the delete key work in the console ui 2009-11-03 12:47:23 +00:00
fc3ae8229f Fix adding torrents from the Queued Torrents dialog 2009-11-03 03:25:57 +00:00
48e3df5ff3 Fix showing the 'Other' speed dialogs in Windows 2009-11-03 03:19:30 +00:00
464e11abe6 Remove debian/ folder 2009-11-02 17:50:32 +00:00
0fbaecbc00 escape the hyphens in the console and web manpages 2009-11-02 17:33:06 +00:00
4c86ed7535 forward port the setup.py fix to trunk and update the 1.2 branch ChangeLog 2009-11-02 15:35:56 +00:00
6f333cefc9 include the deluge-web manpage 2009-11-02 15:28:08 +00:00
750f8fb794 Update translations 2009-11-02 01:35:13 +00:00
be529c1518 Prep rc3 release 2009-11-02 01:23:15 +00:00
e32bd8f4d8 Add the clear() back 2009-11-01 17:49:32 +00:00
87723a3354 Use batch writing mode in the help command 2009-10-31 22:05:35 +00:00
0cb52cca23 Do not include an 'announce-list' key in torrents when there is only one tracker 2009-10-31 19:49:14 +00:00
09ae4fbb72 Improve 'info' command draw speed 2009-10-31 18:52:52 +00:00
6e063e7c85 Don't bother clearing the screen during a refresh 2009-10-31 18:44:27 +00:00
41db357084 Fix crash when string length makes line longer than terminal width 2009-10-31 18:43:48 +00:00
3611584b0b Fix crash when removing multiple torrents 2009-10-31 05:50:01 +00:00
6e695d8410 Fix adding torrents when not showing the add torrent dialog in Windows 2009-10-31 00:08:53 +00:00
60fdcb3bd8 Fix #1052 crash when issuing commands while not connected to a daemon 2009-10-30 20:06:07 +00:00
d110b23a9f Fix improper dos line endings 2009-10-30 19:08:23 +00:00
2457fd0aea Fix displaying non-ascii strings in the console ui -- patch from Ian Martin 2009-10-30 18:00:13 +00:00
92afb8ab80 Fix torrent name being blank when root folder is renamed to /
Update changelog (for previous commit as well)
2009-10-30 00:15:22 +00:00
eba82457bd Make sure renamed files are utf-8 encoded. 2009-10-30 00:02:25 +00:00
099415776d Consider 0 unlimited when displaying limits in the statusbar 2009-10-28 17:43:29 +00:00
145f402aa2 fix #990, showing 0 as a limit when it means unlimited in the statusbar 2009-10-28 17:38:35 +00:00
d44efe8a66 use tag_build in setup.cfg and change version to rc3 in setup.py 2009-10-28 15:55:19 +00:00
1a9f715353 need to actually continue from the loop otherwise another error still occurs 2009-10-28 13:33:27 +00:00
2308961a04 create a method to escape all possible forms of newlines in a translation as well as the quotes 2009-10-28 13:24:37 +00:00
0cdd779396 remove new lines from any translations and replace them with \n characters so javascript can interupt it 2009-10-27 22:32:31 +00:00
db54dada9d fix displaying the protocol speed in the webui
also update the update_ui method in json_api so it gets the status more like how the gtkui does
2009-10-27 11:39:53 +00:00
7c5582a21e fix the eta sort order in the webui 2009-10-27 11:16:55 +00:00
7055a96930 update the ChangeLog so andar doesn't beat me up
fix displaying tracker urls with & in the url in the gtkui
2009-10-27 11:07:30 +00:00
e7a45f6dd1 add < and > escaping to the html escape method 2009-10-27 10:57:47 +00:00
ea4dcfbb5e fix actually escaping (foolish me had left it commented out) and also only update if there has been a change 2009-10-27 10:34:07 +00:00
5c506121a4 create a simple html escape method and escape the fields in the details tab 2009-10-27 10:26:59 +00:00
cc7aaa1ae0 fix a couple of bugs in the M.O.M and fix the options tab and file priorities in the torrent add window (success!) 2009-10-27 10:25:16 +00:00
cca2a4ab60 fixes #1046, changing the option name in the ui rather than the core which would break the other uis 2009-10-27 09:12:22 +00:00
197027510e Fix #1047 move completed does not work if saving to non default path 2009-10-27 03:08:47 +00:00
b12b5a8403 Fix typo in changelog. 2009-10-26 03:10:29 +00:00
0b5a0ac9a0 Update translations 2009-10-26 01:54:19 +00:00
65e50d1992 Prep 1.2_rc2 release 2009-10-25 23:32:13 +00:00
ecd75ba424 modify the M.O.M so the API is practically the same as the O.M, so the only method that accepts an id is changeId 2009-10-25 17:48:28 +00:00
694051e876 Fix typo, update plugin info. 2009-10-25 13:42:11 +00:00
350c523e7d Fix exception when using the 'halt' command 2009-10-24 19:49:20 +00:00
dbece6a6f6 Fix create_plugin.py script 2009-10-24 05:51:26 +00:00
5c26a4b1cf Fix #215 ETA sort order 2009-10-24 04:56:10 +00:00
475a880dd9 Fix previous commit [5864] 2009-10-24 03:47:35 +00:00
b0702e6a6d Fix #799 translate connection status 2009-10-24 02:04:19 +00:00
e506147289 Fix starting plugins when the pluginmanager is started 2009-10-23 23:23:38 +00:00
3c4144e01a Remove '-dev' tag 2009-10-23 22:08:14 +00:00
d4f2c2eda8 Fix possible exception when trying to load pre-1.2 plugins 2009-10-23 01:15:30 +00:00
4a996a1b41 Fix 'autostart localhost if needed' option 2009-10-23 01:07:46 +00:00
e39d1ab679 fix removing torrents that fail to download when added via url 2009-10-21 20:30:07 +00:00
c49ab0c6b1 fix switching the options when a different torrent is selected
fix showing the private flag
2009-10-21 20:15:16 +00:00
9c412e8aff fixes to the M.O.M and the options details tab, still not 100% though 2009-10-21 19:48:44 +00:00
83f42f5bf8 show the infinity symbol if ratio is less than 0 2009-10-20 22:45:32 +00:00
039a41def3 just a slight tweak adding some trac wiki formatting 2009-10-20 22:35:02 +00:00
b94060c60d fix setting bandwidth limits via the statusbar 2009-10-20 22:33:18 +00:00
33301b3433 fix setting the add options in the mom 2009-10-20 21:53:54 +00:00
b9ae412d06 add support for returning all options for an id in the get() method 2009-10-20 21:32:32 +00:00
f55bc6c9d8 Add option to create torrent name sub-folders in extract folder
Fix issue where the plugin would not stop extracting files after being disabled
2009-10-19 02:10:43 +00:00
511bfd5d84 use os._exit() rather than exit() when forking 2009-10-14 15:53:31 +00:00
89f88f3beb big bunch of fixes to the M.O.M along with a couple to the O.M 2009-10-13 16:21:28 +00:00
b1b09fbe89 move the parameter type converting into a seperate method 2009-10-13 15:22:24 +00:00
07b50730a2 improve the forking code 2009-10-13 15:20:45 +00:00
5607e0f6fa change all the 2 space gaps to tabs so vim hightlighting doesn't look irritating 2009-10-13 12:01:20 +00:00
9ab638377b add the option to fork the webui 2009-10-13 11:32:40 +00:00
4b79ae8280 fix a bug in the connection manager where the host list wasn't refreshed after a host was added 2009-10-13 09:56:49 +00:00
f1a1fdb630 update the changelog 2009-10-12 16:46:14 +00:00
d1f52a7051 allow setting of the value without firing an event 2009-10-12 16:45:28 +00:00
000297dc95 give names to both the radio buttons so they end up in a group (duh) 2009-10-12 16:44:30 +00:00
81eeb6edf7 fix a bug in converting non-boolean values back to boolean in the options manager 2009-10-12 09:53:13 +00:00
7d67da4371 Fix saving torrent state on fresh configs
Do not try to call doIteration() on the reactor if it has already stopped
2009-10-11 18:46:35 +00:00
ef5739a6a8 change the value of torrent.queue from -1 to 99999 so the grid sorting works the same as the gtk ui 2009-10-10 14:06:08 +00:00
4f968b9887 add a man page for deluge-web 2009-10-10 13:45:31 +00:00
22447993fa Add man pages for deluge-console and deluge-gtk
Update the other man pages
2009-10-09 16:26:10 +00:00
58be1f08fc Fix localclient authentication by stripping the lines read from the auth file 2009-10-09 00:39:57 +00:00
41d26bbbce Fix path errors when adding torrents externally in Windows 2009-10-09 00:19:07 +00:00
f1383f9655 Update ChangeLog 2009-10-08 02:59:24 +00:00
644f0ecb38 Modify setup.py to allow building without libtorrent/ 2009-10-08 02:51:49 +00:00
42a57b1f41 Fix quitting in Windows 2009-10-08 02:42:22 +00:00
8f50687034 Make sure libtorrent.pyd is included in bbfreeze 2009-10-08 02:42:00 +00:00
9f661caee8 Update win32 folder 2009-10-08 02:18:48 +00:00
044306cb3d Update gettextize.sh script to use proper encoding
Regenerate deluge.pot
2009-10-05 00:00:37 +00:00
ebd0290e44 Add script to regenerate the POTFILES.in file
Update the POTFILES.in file
2009-10-04 23:48:18 +00:00
4f1713734f Remove feeder, example and stats plugin from 1.2 2009-10-04 23:24:45 +00:00
f86e319d49 Update setup.py 2009-10-04 23:19:34 +00:00
95bb8fb4c3 Branch 1.2 2009-10-04 23:13:40 +00:00
2018 changed files with 253935 additions and 358630 deletions

25
.gitattributes vendored
View File

@ -1,25 +0,0 @@
/libtorrent/ export-ignore
/win32/ export-ignore
/osx/ export-ignore
docs/build/ export-ignore
docs/source/ export-ignore
/tests/ export-ignore
deluge/scripts/ export-ignore
setup.cfg export-ignore
check_glade.sh export-ignore
createicons.sh export-ignore
create_potfiles_in.py export-ignore
gettextize.sh export-ignore
deluge/i18n/deluge.pot export-ignore
deluge/ui/web/css/*-debug.css export-ignore
deluge/ui/web/js/*-debug.js export-ignore
deluge/ui/web/js/deluge-all/ export-ignore
deluge/ui/web/js/ext-extensions/ export-ignore
deluge/ui/web/gen_gettext.py export-ignore
deluge/ui/web/build export-ignore
deluge/ui/web/docs/ export-ignore
.gitattributes export-ignore
.gitmodules export-ignore
.gitignore export-ignore
*.py diff=python

14
.gitignore vendored
View File

@ -1,14 +0,0 @@
*~
build
dist
*egg-info
*.egg
*.log
*.pyc
*.tar.*
_trial_temp
deluge/i18n/*/
*.desktop
.build_data*
osx/app
RELEASE-VERSION

797
AUTHORS
View File

@ -1,797 +0,0 @@
Authors:
* Andrew Resch ('andar') <andrewresch@gmail.com>
* Damien Churchill ('damoxc') <damoxc@gmail.com>
Main Developers:
* Andrew Resch
* Damien Churchill
* John Garland ('johnnyg') <johnnybg+deluge@gmail.com>
* Calum Lind ('cas') <calumlind+deluge@gmail.com>
libtorrent (http://www.libtorrent.org):
* Arvid Norberg
Contributors (and Past Developers):
* Zach Tibbitts <zach@collegegeek.org>
* Alon Zakai ('Kripken') <kripkensteiner@gmail.com>
* Marcos Pinto ('markybob') <markybob@gmail.com>
* Alex Dedul
* Sadrul Habib Chowdhury
* Ido Abramovich <ido.deluge@gmail.com>
* Martijn Voncken <mvoncken@gmail.com>
* Mark Stahler ('kramed') <markstahler@gmail.com>
* Pedro Algarvio ('s0undt3ch') <ufs@ufsoft.org>
* Cristian Greco ('cgreco') <cristian@regolo.cc>
* Chase Sterling ('gazpachoKing') <chase.sterling@gmail.com>
Plugin Developers:
* Autoadd : Chase Sterling
* Blocklist : John Garland
* Execute : Damien Churchill
* Extractor : Andrew Resch
* Label : Martijn Voncken
* Notifications : Pedro Algarvio
* Scheduler : Andrew Resch
* Webui : Damien Churchill
Images Authors:
* files: deluge/ui/data/pixmaps/*.svg, *.png
deluge/ui/web/icons/active.png, alert.png, all.png, checking.png, dht.png,
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
exceptions: deluge/ui/data/pixmaps/deluge.svg and derivatives
copyright: Andrew Resch
license: GPLv3
* files: deluge/ui/data/pixmaps/deluge.svg and derivatives
deluge/ui/web/icons/apple-pre-*.png, deluge*.png
deluge/ui/web/images/deluge*.png
copyright: Andrew Wedderburn
license: GPLv3
* files: deluge/plugins/blocklist/blocklist/data/*.png
deluge/ui/data/pixmaps/tracker_warning16.png, tracker_all16.png, lock48.png
copyright: Gnome Icon Theme
license: GPLv2
url: http://ftp.acc.umu.se/pub/GNOME/sources/gnome-icon-theme
* files: deluge/ui/data/pixmaps/magnet.png
copyright: Woothemes
license: Freeware
icon pack: WP Woothemes Ultimate
url: http://www.woothemes.com/
* files: deluge/ui/data/pixmaps/flags/*.png
copyright: Mark James <mjames@gmail.com>
license: Public Domain
url: http://famfamfam.com/lab/icons/flags/
* files: deluge/ui/web/icons/*.png
exceptions: apple-pre-*.png, active.png, alert.png, all.png, deluge.png, dht.png,
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
copyright: Yusuke Kamiyamane <p@yusukekamiyamane.com>
license: Creative Commons Attribution 3.0 License
url: http://p.yusukekamiyamane.com/
* files: deluge/ui/web/images/spinner.gif, spinner-split.gif
copyright: Steven Chim
license: BSD license
url: http://members.upc.nl/j.chim/ext/spinner2/ext-spinner.html
Translation Contributors:
* files: deluge/i18n/*.po
Aaron Wang Shi
abbigss
ABCdatos
Abcx
Actam
Adam
adaminikisi
adi_oporanu
Adrian Goll
afby
Ahmades
Ahmad Farghal
Ahmad Gharbeia أحمد غربية
akira
Aki Sivula
Alan Pepelko
Alberto
Alberto Ferrer
alcatr4z
AlckO
Aleksej Korgenkov
Alessio Treglia
Alexander Ilyashov
Alexander Matveev
Alexander Saltykov
Alexander Taubenkorb
Alexander Telenga
Alexander Yurtsev
Alexandre Martani
Alexandre Rosenfeld
Alexandre Sapata Carbonell
Alexey Osipov
Alin Claudiu Radut
allah
AlSim
Alvaro Carrillanca P.
A.Matveev
Andras Hipsag
András Kárász
Andrea Ratto
Andreas Johansson
Andreas Str
André F. Oliveira
AndreiF
andrewh
Angel Guzman Maeso
Aníbal Deboni Neto
animarval
Antonio Cono
antoniojreyes
Anton Shestakov
Anton Yakutovich
antou
Arkadiusz Kalinowski
Artin
artir
Astur
Athanasios Lefteris
Athmane MOKRAOUI (ButterflyOfFire)
Augusta Carla Klug
Avoledo Marco
axaard
AxelRafn
Axezium
Ayont
b3rx
Bae Taegil
Bajusz Tamás
Balaam's Miracle
Ballestein
Bent Ole Fosse
berto89
bigx
Bjorn Inge Berg
blackbird
Blackeyed
blackmx
BlueSky
Blutheo
bmhm
bob00work
boenki
Bogdan Bădic-Spătariu
bonpu
Boone
boss01
Branislav Jovanović
bronze
brownie
Brus46
bumper
butely
BXCracer
c0nfidencal
Can Kaya
Carlos Alexandro Becker
cassianoleal
Cédric.h
César Rubén
chaoswizard
Chen Tao
chicha
Chien Cheng Wei
Christian Kopac
Christian Widell
Christoffer Brodd-Reijer
christooss
CityAceE
Clopy
Clusty
cnu
Commandant
Constantinos Koniaris
Coolmax
cosmix
Costin Chirvasuta
CoVaLiDiTy
cow_2001
Crispin Kirchner
crom
Cruster
Cybolic
Dan Bishop
Danek
Dani
Daniel Demarco
Daniel Ferreira
Daniel Frank
Daniel Holm
Daniel Høyer Iversen
Daniel Marynicz
Daniel Nylander
Daniel Patriche
Daniel Schildt
Daniil Sorokin
Dante Díaz
Daria Michalska
DarkenCZ
Darren
Daspah
David Eurenius
davidhjelm
David Machakhelidze
Dawid Dziurdzia
Daya Adianto
dcruz
Deady
Dereck Wonnacott
Devgru
Devid Antonio FiloniDevilDogTG
di0rz`
Dialecti Valsamou
Diego Medeiros
Dkzoffy
Dmitrij D. Czarkoff
Dmitriy Geels
Dmitry Olyenyov
Dominik Kozaczko
Dominik Lübben
doomster
Dorota Król
Doyen Philippe
Dread Knight
DreamSonic
duan
Duong Thanh An
DvoglavaZver
dwori
dylansmrjones
Ebuntor
Edgar Alejandro Jarquin Flores
Eetu
ekerazha
Elias Julkunen
elparia
Emberke
Emiliano Goday Caneda
EndelWar
eng.essam
enubuntu
ercangun
Erdal Ronahi
ergin üresin
Eric
Éric Lassauge
Erlend Finvåg
Errdil
ethan shalev
Evgeni Spasov
ezekielnin
Fabian Ordelmans
Fabio Mazanatti
Fábio Nogueira
FaCuZ
Felipe Lerena
Fernando Pereira
fjetland
Florian Schäfer
FoBoS
Folke
Force
fosk
fragarray
freddeg
Frédéric Perrin
Fredrik Kilegran
FreeAtMind
Fulvio Ciucci
Gabor Kelemen
Galatsanos Panagiotis
Gaussian
gdevitis
Georg Brzyk
George Dumitrescu
Georgi Arabadjiev
Georg Sieber
Gerd Radecke
Germán Heusdens
Gianni Vialetto
Gigih Aji Ibrahim
Giorgio Wicklein
Giovanni Rapagnani
Giuseppe
gl
glen
granjerox
Green Fish
greentea
Greyhound
G. U.
Guillaume BENOIT
Guillaume Pelletier
Gustavo Henrique Klug
gutocarvalho
Guybrush88
Hans Rødtang
HardDisk
Hargas Gábor
Heitor Thury Barreiros Barbosa
helios91940
helix84
Helton Rodrigues
Hendrik Luup
Henrique Ferreiro
Henry Goury-Laffont
Hezy Amiel
hidro
hoball
hokten
Holmsss
hristo.num
Hubert Życiński
Hyo
Iarwain
ibe
ibear
Id2ndR
Igor Zubarev
IKON (Ion)
imen
Ionuț Jula
Isabelle STEVANT
István Nyitrai
Ivan Petrovic
Ivan Prignano
IvaSerge
jackmc
Jacks0nxD
Jack Shen
Jacky Yeung
Jacques Stadler
Janek Thomaschewski
Jan Kaláb
Jan Niklas Hasse
Jasper Groenewegen
Javi Rodríguez
Jayasimha (ಜಯಸಿಂಹ)
jeannich
Jeff Bailes
Jesse Zilstorff
Joan Duran
João Santos
Joar Bagge
Joe Anderson
Joel Calado
Johan Linde
John Garland
Jojan
jollyr0ger
Jonas Bo Grimsgaard
Jonas Granqvist
Jonas Slivka
Jonathan Zeppettini
Jørgen
Jørgen Tellnes
josé
José Geraldo Gouvêa
José Iván León Islas
José Lou C.
Jose Sun
Jr.
Jukka Kauppinen
Julián Alarcón
julietgolf
Jusic
Justzupi
Kaarel
Kai Thomsen
Kalman Tarnay
Kamil Páral
Kane_F
kaotiks@gmail.com
Kateikyoushii
kaxhinaz
Kazuhiro NISHIYAMA
Kerberos
Keresztes Ákos
kevintyk
kiersie
Kimbo^
Kim Lübbe
kitzOgen
Kjetil Rydland
kluon
kmikz
Knedlyk
koleoptero
Kőrösi Krisztián
Kouta
Krakatos
Krešo Kunjas
kripken
Kristaps
Kristian Øllegaard
Kristoffer Egil Bonarjee
Krzysztof Janowski
Krzysztof Zawada
Larry Wei Liu
laughterwym
Laur Mõtus
lazka
leandrud
lê bình
Le Coz Florent
Leo
liorda
LKRaider
LoLo_SaG
Long Tran
Lorenz
Low Kian Seong
Luca Andrea Rossi
Luca Ferretti
Lucky LIX
Luis Gomes
Luis Reis
Łukasz Wyszyński
luojie-dune
maaark
Maciej Chojnacki
Maciej Meller
Mads Peter Rommedahl
Major Kong
Malaki
malde
Malte Lenz
Mantas Kriaučiūnas
Mara Sorella
Marcin
Marcin Falkiewicz
marcobra
Marco da Silva
Marco de Moulin
Marco Rodrigues
Marcos
Marcos Escalier
Marcos Pinto
Marcus Ekstrom
Marek Dębowski
Mário Buči
Mario Munda
Marius Andersen
Marius Hudea
Marius Mihai
Mariusz Cielecki
Mark Krapivner
marko-markovic
Markus Brummer
Markus Sutter
Martin
Martin Dybdal
Martin Iglesias
Martin Lettner
Martin Pihl
Masoud Kalali
mat02
Matej Urbančič
Mathias-K
Mathieu Arès
Mathieu D. (MatToufoutu)
Mathijs
Matrik
Matteo Renzulli
Matteo Settenvini
Matthew Gadd
Matthias Benkard
Matthias Mailänder
Mattias Ohlsson
Mauro de Carvalho
Max Molchanov
Me
MercuryCC
Mert Bozkurt
Mert Dirik
MFX
mhietar
mibtha
Michael Budde
Michael Kaliszka
Michalis Makaronides
Michał Tokarczyk
Miguel Pires da Rosa
Mihai Capotă
Miika Metsälä
Mikael Fernblad
Mike Sierra
mikhalek
Milan Prvulović
Milo Casagrande
Mindaugas
Miroslav Matejaš
misel
mithras
Mitja Pagon
M.Kitchen
Mohamed Magdy
moonkey
MrBlonde
muczy
Münir Ekinci
Mustafa Temizel
mvoncken
Mytonn
NagyMarton
neaion
Neil Lin
Nemo
Nerijus Arlauskas
Nicklas Larsson
Nicolaj Wyke
Nicola Piovesan
Nicolas Sabatier
Nicolas Velin
Nightfall
NiKoB
Nikolai M. Riabov
Niko_Thien
niska
Nithir
noisemonkey
nomemohes
nosense
null
Nuno Estêvão
Nuno Santos
nxxs
nyo
obo
Ojan
Olav Andreas Lindekleiv
oldbeggar
Olivier FAURAX
orphe
osantana
Osman Tosun
OssiR
otypoks
ounn
Oz123
Özgür BASKIN
Pablo Carmona A.
Pablo Ledesma
Pablo Navarro Castillo
Paco Molinero
Pål-Eivind Johnsen
pano
Paolo Naldini
Paracelsus
Patryk13_03
Patryk Skorupa
PattogoTehen
Paul Lange
Pavcio
Paweł Wysocki
Pedro Brites Moita
Pedro Clemente Pereira Neto
Pekka "PEXI" Niemistö
Penegal
Penzo
perdido
Peter Kotrcka
Peter Skov
Peter Van den Bosch
Petter Eklund
Petter Viklund
phatsphere
Phenomen
Philipi
Philippides Homer
phoenix
pidi
Pierre Quillery
Pierre Rudloff
Pierre Slamich
Pietrao
Piotr Strębski
Piotr Wicijowski
Pittmann Tamás
Playmolas
Prescott
Prescott_SK
pronull
Przemysław Kulczycki
Pumy
pushpika
PY
qubicllj
r21vo
Rafał Barański
rainofchaos
Rajbir
ras0ir
Rat
rd1381
Renato
Rene Hennig
Rene Pärts
Ricardo Duarte
Richard
Robert Hrovat
Roberth Sjonøy
Robert Lundmark
Robin Jakobsson
Robin Kåveland
Rodrigo Donado
Roel Groeneveld
rohmaru
Rolf Christensen
Rolf Leggewie
Roni Kantis
Ronmi
Rostislav Raykov
royto
RuiAmaro
Rui Araújo
Rui Moura
Rune Svendsen
Rusna
Rytis
Sabirov Mikhail
salseeg
Sami Koskinen
Samir van de Sand
Samuel Arroyo Acuña
Samuel R. C. Vale
Sanel
Santi
Santi Martínez Cantelli
Sardan
Sargate Kanogan
Sarmad Jari
Saša Bodiroža
sat0shi
Saulius Pranckevičius
Savvas Radevic
Sebastian Krauß
Sebastián Porta
Sedir
Sefa Denizoğlu
sekolands
Selim Suerkan
semsomi
Sergii Golovatiuk
setarcos
Sheki
Shironeko
Shlomil
silfiriel
Simone Tolotti
Simone Vendemia
sirkubador
Sławomir Więch
slip
slyon
smoke
Sonja
spectral
spin_555
spitf1r3
Spiziuz
Spyros Theodoritsis
SqUe
Squigly
srtck
Stefan Horning
Stefano Maggiolo
Stefano Roberto Soleti
steinberger
Stéphane Travostino
Stephan Klein
Steven De Winter
Stevie
Stian24
stylius
Sukarn Maini
Sunjae Park
Susana Pereira
szymon siglowy
takercena
TAS
Taygeto
temy4
texxxxxx
thamood
Thanos Chatziathanassiou
Tharawut Paripaiboon
Theodoor
Théophane Anestis
Thor Marius K. Høgås
Tiago Silva
Tiago Sousa
Tikkel
tim__b
Tim Bordemann
Tim Fuchs
Tim Kornhammar
Timo
Timo Jyrinki
Timothy Babych
TitkosRejtozo
Tom
Tomas Gustavsson
Tomas Valentukevičius
Tomasz Dominikowski
Tomislav Plavčić
Tom Mannerhagen
Tommy Mikkelsen
Tom Verdaat
Tony Manco
Tor Erling H. Opsahl
Toudi
tqm_z
Trapanator
Tribaal
Triton
TuniX12
Tuomo Sipola
turbojugend_gr
Turtle.net
twilight
tymmej
Ulrik
Umarzuki Mochlis
unikob
Vadim Gusev
Vagi
Valentin Bora
Valmantas Palikša
VASKITTU
Vassilis Skoullis
vetal17
vicedo
viki
villads hamann
Vincent Garibal
Vincent Ortalda
vinchi007
Vinícius de Figueiredo Silva
Vinzenz Vietzke
virtoo
virtual_spirit
Vitor Caike
Vitor Lamas Gatti
Vladimir Lazic
Vladimir Sharshov
Wanderlust
Wander Nauta
Ward De Ridder
WebCrusader
webdr
Wentao Tang
wilana
Wilfredo Ernesto Guerrero Campos
Wim Champagne
World Sucks
Xabi Ezpeleta
Xavi de Moner
XavierToo
XChesser
Xiaodong Xu
xyb
Yaron
Yasen Pramatarov
YesPoX
Yuren Ju
Yves MATHIEU
zekopeko
zhuqin
Zissan
Γιάννης Κατσαμπίρης
Артём Попов
Миша
Шаймарданов Максим
蔡查理

717
ChangeLog
View File

@ -1,427 +1,190 @@
=== Deluge 1.4.0 (In Development) ===
* Improved Logging
* Removed the AutoAdd feature on the core. It's now handled with the AutoAdd
plugin, which is also shipped with Deluge, and it does a better job and
now, it even supports multiple users perfectly.
* Authentication/Permission exceptions are now sent to clients and recreated
there to allow acting upon them.
* Enforced the use of the "deluge.plugins" namespace to reduce package
names clashing beetween regular packages and deluge plugins.
=== Deluge 1.2.0 - "Bursting like an infected kidney" (09 January 2010) ===
==== Core ====
* Make the distinction between adding to the session new unmanaged torrents
and torrents loaded from state. This will break backwards compatability.
* Pass a copy of an event instead of passing the event arguments to the
event handlers. This will break backwards compatability.
* Allow changing ownership of torrents.
* File modifications on the auth file are now detected and when they happen,
the file is reloaded. Upon finding an old auth file with an old format, an
upgrade to the new format is made, file saved, and reloaded.
* Authentication no longer requires a username/password. If one or both of
these is missing, an authentication error will be sent to the client
which sould then ask the username/password to the user.
* Implemented sequential downloads.
* #378: Provide information about a torrent's pieces states
* Fix file renaming
* Fix tracker host filtering (Closes #1106)
* Fix exceptions when gettext/locale cannot be initialized properly
* Change share ratio calculation to use the total done instead of the all time
downloaded value. This change will make the share ratio calculation not
use data downloaded in failed hash checks.
* Fix torrent info name not being utf-8 decoded when root file/folder name
is blank
==== GtkUI ====
* Fix uncaught exception when closing deluge in classic mode
* Allow changing ownership of torrents
* Host entries in the Connection Manager UI are now editable. They're
now also migrated from the old format were automatic localhost logins were
possible, which no longer is, this fixes #1814.
* Implemented sequential downloads UI handling.
* #378: Allow showing a pieces bar instead of a regular progress bar in a
torrent's status tab.
* #2093: Make torrent opening compatible with all unicode paths.
==== Blocklist Plugin ====
* #1382: Implemented whitelist support to both core and GTK UI.
* Implemented ip filter cleaning before each update. Restarting the deluge
daemon is no longer needed.
* If "check_after_days" is 0(zero), the timer is not started anymore. It
would keep updating one call after the other. If the value changed, the
timer is now stopped and restarted using the new value.
=== Deluge 1.3.7 (In Development) ===
==== GtkUI ====
* Fix issue with Plugins that add Tab to torrentdetails
* Fix the scalable icon install directory
==== Extractor ====
* #2290: Fix dotted filenames being rejected
=== Deluge 1.3.6 (25 Feburary 2013) ===
==== Core ====
* Catch & log KeyError when removing a torrent from the queued torrents set
* Fix moving/renaming torrents issues when using libtorrent 0.16
* Make sure queue order is preserved when restarting
* #2160: Disable use of python bindings for libtorrent extensions and replace with session flag
* #2163: Fix unable add torrent file with empty (0:) encoding tag
* #2201: Fix error in authmanager if auth file has extra newlines
* #2109: Fix the Proxy settings not being cleared by setting None
* #2110: Fix accepting magnet uris with xt param anywhere within them
* #2204: Fix daemon shutdown hang with large numbers of torrents
==== Client ====
* Fix keyerrors after removing torrents from UIs
==== GtkUI ====
* Add move completed option to add torrent dialog
* Prevent jitter in torrent view
* Fix torrent creation with non-ascii characters
* Fix #2100 : Add option not to bring main window to front when adding torrents through ipcinterface
* Add Quit Dialog when toggling classic mode in preferences and only show connection manager when not in classic mode.
* #2169: Fix 'Download Location' in the Add Torrent Dialog not set correctly when folder typed into Other->Location field
* #2171: Fix the Add Peer dialog not responding if empty or invalid values entered
* #2104: Fix no title set for the appindicator
* #2086: Fix submenus and icons for appindicator
* #2146: Fix missing translations in View|Tabs submenu
* Fix torrent names on libtorrent 0.16 on windows
* #2147: Fix missing translations for plugin preferences page
* #1474: Fix the on_show_prefs hook not being executed immediatly after enabling a plugin
* #1946: Fix ReactorNotRestartable error when set as startup application
* #2130: Fix same name can be given to different files in Add Torrent dialog
* #2129: Fix empty filename able to be set in AddTorrent dialog
* #2228: Fix Apply-To-All in AddTorrent Dialog copying file renames to other torrents
* #2260: Fix the Add Torrent dialog also bringing the main window to active workspace
* Fix showing exception error to user in Classic Mode with no libtorrent installed
* Fix #1104, #735 use path.utf-8 if available
* Fix #1114 test active port not working in classic mode
==== Console ====
* LP#1004793: Enable use of connect command in non-interactive mode
* Ensure console commands are executed in order
* #2065: Fix crash with missing closing quote
* #1397: Add support for -s STATE in info command
* Fix #1115 not showing usage for the 'debug' command
* Fix #1116 not being able to use command aliases when not connected to a daemon
* Fix #1117 can't use the '3' key
==== WebUI ====
* Add move completed option to add torrent dialog
* #2112: Fix world readable tmp directory in json_api
* #2069: Fix login window layout problem when using larger than default font size
* #1890: Fix columns in files and peers view could use some spacing
* #2103: Fix sorting by name is case-sensitive [sedulous]
* #2120: Fix manually entered values not being saved in spinners
* #2212: Fix unable to scroll in proxy preferences page
* Fix autoconnecting to the default host
* #2046: Fix plugins not enabling properly until after refreshing page
* #2125: Fix plugin methods not being available when enabled until restart
* #2085: Fix not showing torrents in sidebar for categories other than 'All' in classic mode
* #2232: Fix flag icon path in Peers Tab missing deluge.config.base
* Fix submenus closing upon mouse click
* Add failed login log message, including IP address, to enable use with fail2ban
* #2261: Fix Proxy settings not being saved in preferences
==== Windows OS ====
* Hide the cmd windows when running deluged.exe or deluge-web.exe
* Add deluged-debug.exe and deluge-web-debug.exe that still show the cmd window
* Add gtk locale files to fix untranslated text
* Fix the Open Folder option not working with non-ascii paths
* Fix the daemon starting with config dir containing spaces
* Fix Windows tray submenu items requiring right-click instead of left-click
* Fix issue with adding some torrents with illegal characters via url in gtk client
* #2240: Fix freespace issue with large capacity drives
==== OS X ====
* Fix Open File/Folder option
* Add OS X Menu for GTK Quartz
==== Execute ====
* Fix execute plugin not working with unicode torrent names
==== Extractor ====
* Add Windows support, using 7-zip
* Added support for more extensions
* Disabled extracting 'Move Completed' torrents due to race condition
=== Deluge 1.3.5 (09 April 2012) ===
==== GtkUI ====
* Modified fix for #1957, keyerror with non-acsii columns
* Fix translation of items in Sidebar and Torrent Menu
* #2052: Fix translation of Progress bar text
* #2071: Fix KeyError in gtkui when file priority set to value '3'
* #2064: Fix files treeview height in Create Dialog
* Fix missing semi-colon in deluge.desktop
* Disable setting file priorities for seeding torrents
* Bring MainWindow to front when opening another instance
==== WebUI ====
* #2050: Fix 'Up Speed' column not sorting
* Hide unused Infohash button in WebUI
==== Label ====
* Disable unusable items for 'All' in sidebar menu
* Fix items for translation
==== Console ====
* Fix prefixed space for tab completing commands
* Fix missing trailing space for command options with tab complete
==== Windows ====
* Fix displaying folders in the add torrent dialog
* Fix displaying the new release dialog
==== Blocklist ====
* Use (documented) formatdate over format_date_time
=== Deluge 1.3.4 (03 March 2012) ===
==== Core ====
* #1921: Free disk space reporting incorrectly in FreeBSD
* #1964: Fix unhandled UnpicklingErrors
* #1967: Fix unhandled IndexError when trying to open a non-json conf file
* Fix setting daemon listen interface from command line
* #2021: Fix share ratio limit not obeyed for seeded torrents added to session
* Add optparse custom version to prevent unnecessary loading of libtorrent
* #1554: Fix seeding on share ratio failing for partially selected torrents
* Add proper process title naming in ps, top etc. (Depends: setproctitle)
==== GtkUI ====
* #1918: Fix Drag'n'Drop not working in Windows
* #1941: Increase maximum Cache Size to 999999 (15GiB)
* #1940: File & folder renaming issue when using Add Torrent dialog in Windows
* LP#821577: Fix UnpicklingError when external selection dragged onto Files Tab
* #1934: Fix Unicode error in AddTorrent Dialog
* #1957: Fix keyerror when adding columns for non-latin languages
* #1969: Fix menu item 'Quit & Shutdown' still available when not connected to daemon
* #1895: Fix Files Tab showing wrong files due to torrent_info race condition
* #2010: Move speed text in titlebar to the beginning
* #2032: Wait for client to shutdown/disconnect before stopping reactor
* Fix compatibility with Python 2.5
* Fix collapsed treeview in Create Torrent dialog
* Ignore unmaximise event when window isn't visible
* #1976: Fixed text entry with trailing newline characters causing issues for Move Storage
==== WebUI ====
* Fix Webui files-tab menu setting wrong priority
* Update to ExtJS 3.4.0
* #1960: Fix statustab showing total_payload_download for upload as well
* Remove uneeded Titlebar to save space
* Fix clipped Browse button in WebUI
* #1915: Fix being unable to stop the status bar from autohiding
* Fix password box focus issue in Firefox
* Fix plugin uploads from behind a reverse proxy
* #2010: Move speed text in titlebar to the beginning
* #1936: Fix Referenced before assignment error in json_api
* Changes are now applied when clicking OK in Preferences
* Added Download,Uploaded,Down Limit, Up Limit & Seeder/Peeds columns
* Add magnet uri support to Add Url
* Add keymaps for torrents - Ctrl-A (select all) and Delete
* #2037: Fix 'Add Torrents' torrents list not scrolling
* #2038: Fix Chrome 17 disconnecting from webui
==== Console ====
* #1953: Fix flickering on every update
* #1954: Fix 'invalid literal for float' when setting listen interface
* #1945: Fix UnicodeDecodeError when using non-ascii chars in info
==== Label ====
* #1961: Add missing 'All' filter option
* #2035: Fix label options dialog in webui
* #2036: Fix newly added labels not being sorted in torrent right click menu
==== Notification ====
* #1905: Fix no email sent to second email address
* #1898: Fix email notifications not including date/time they were sent
==== Scheduler ====
* Add plugin page for WebUi
==== Execute ====
* Commands now run scripts asynchronous to prevent Deluge from hanging
==== AutoAdd ====
* Added watch folder support for '.magnet' text file containing single or multiple magnet uris
* Fix glade object issue when re-enabling plugin in same session
* Fix plugin not showing as enabled in webui
=== Deluge 1.3.3 (22 July 2011) ===
==== Core ====
* Properly show the 'Checking Resume Data' state instead of just 7
* #1788: Added ability to use XDG_DOWNLOAD_DIR as default download folder
* Fix path error with torrent files prefixed with 'file://' from Firefox
* #1869: Fix setting the disk io read/write to bypass OS cache in Windows
* #1504: Fix win32 running deluged as not logged in user via runas or service
* #890: If added torrent already exists, append extra trackers to it
* #1338: Fix Seeds and Peers totals not updating
* #1239: Fix translated Tracker Error text not counted in sidebar Error status
* Fix httpdownloader error with existing filename
* #1505: Add libtorrent info to version output
* #1637 Fix UnicodeDecodeError from 'deluge-* --help' with non-english languages
* #1714 Fix handling of backslashes when renaming files/folders
==== GtkUI ====
* Show the checking icon for torrents in the 'Checking Resume Data' state
* #1195: Fix right-click selecting issue when switching between folders and files
* Add F2 key shortcut for renaming filenames in the Files Tab
* Increase max piece size to 16 MiB in create torrent dialog
* #1475: Fix save and restore Preferences dialog size from config
* Add search as you type to the torrent view
* #1456: Fix no ETA showing with multiple files
* #1560: Fix FilesTab Progress value sorting by int instead of float
* #1263: Fix not remembering column widths
* #948: New Release Dialog now shows the server version
* Fix peers in PeersTab showing non-zero download rate when seeding
==== AutoAdd ====
* #1861: Fix AutoAdd Warning (column number is a boolean)
==== Label ====
* #1246: Fix losing Labels upon restart
==== Execute ====
* #1477: Fix ignore Added events from state file on startup
==== ConsoleUI ====
* #1258: Add support for urls and magnet uris in add command
* #1801: Fix unhandled defered error and missing error message upon failed connect
=== Deluge 1.3.2 (24 May 2011) ===
==== Core ====
* #1527: Fix Converting unicode to unicode error in move_storage
* #1373: Fix creating and moving non-ascii folder names in MS Windows
* #1507: Fix temporary file race condition in core/core.py:add_torrent_url
* Fix a bug that can occur when upgrading 1.1 config files
* #1517: Fix isohunt urls not loading
* Handle redirection when adding a torrent by url
* #1614: Fix autoadd matching a directory called "torrent"
* #1742: Fix failure in Event handler prevents further emissions
==== GtkUI ====
* #1514: Added Indicator Applet
* #1494: Add torrent columns Downloaded and Uploaded
* #1308: Add torrent column Seeds/Peers ratio
* #1646: Add torrent columns for per torrent upload and download speed limits
* Add missing icons for Trackers filter
* Fix inconsistancies in the text for translation
* #1510: Fix cannot create a torrent with only non-zero tier trackers
* #1513: Fix unhandled Twisted Error in test_listen_port
* #690: Fix renaming folders does not remove old empty folders
* #1336: Fix uneeded horizontal scrollbar showing in Files & Peers Tab
* #1508: Fix TypeError in cell_data_queue() could not convert argument to correct param type
* #1498: Fix double slashes appearing when renaming
* #1283: Fix consistent icons for Files tab
* #1282: Text for AutoManaged changed to 'On/Off' and localized
* Fix Up/Down buttons in Edit Trackers Dialog
* Add Key Shortcuts for main menu functions
==== WebUI ====
* #1194: Fix infinite login prompt in web ui through reverse proxy
* #1355: Fix slow changing states in webUI
* #1536: Fix Edit Trackers window not scrolling and not being resizable
* #1799: Fix Missing textbox for "Move completed" in torrent options
* #1562: Fix Javascript error in Web UI when re-opening preferences
* #1567: Fix js from plugins does not work with different 'base' setting
* #1268: Fix torrent errors not displayed in webui
* #1323: Fix filter panels not scrollable
* Fix file uploads from behind a reverse proxy.
* #1333: Fix peer list doesn't update automatically
* #1537: Fix editing trackers list, trackers have to be reselected
==== ConsoleUI ====
* #755: Fix can't set listen_ports through console UI
* #1500: Fix Console crashes on command longer than terminal width
* #1248: Fix deluge-console unicode support on redirected stdout
* Fix for deluge-console not adding torrent files on MS Windows
* #1450: Fix trailing white space in paths
* Misc: Updated help text for deluge-console on MS Windows
* #1484: Fix trying to access the screen object when not using interactive mode
* #1548: Fix cli argument processing
* #1856: Add --sort option to info command
* #1857: Add seeding_time, active_time and tracker_status to info command
==== Scheduler ====
* #1506: Fix max speed not restored on a yellow->green transition
=== Deluge 1.3.1 (31 October 2010) ===
==== Core ====
* #1369: Fix non-ascii config folders not working in windows
==== GtkUI ====
* #1365: Fix sidebar not updating show/hide trackers
* #1247: Fix hang on quit
==== WebUI ====
* #1364: Fix preferences not saving when the web ui plugin is enabled in classic mode
* #1377: Fix bug when enabling plugins
* #1370: Fix issues with preferences
* #1312: Fix deluge-web using 100% CPU
=== Deluge 1.3.0 (18 September 2010) ===
==== Core ====
* Fix issue where the save_timer is cancelled when it's not active
* Fix unhandled exception when adding a torrent to the session
* Moved xdg import so it is not called on Windows, where it is unused. fixes #1343
* Fix key error after enabling a plugin that introduces a new status key
* Ignore global stop ratio related settings in logic, so per torrent ones are used.
* Ensure preferencesmanager only changes intended libtorrent session settings.
* Fix issue when adding torrents without a 'session'. This can happen when a plugin adds a torrent, like how the AutoAdd plugin works. The user that adds this torrent will be an empty string.
* Add TorrentFileCompleted event
==== GtkUI ====
* Increase max piece size to 8 MiB in create torrent dialog (closes #1358)
==== Scheduler ====
* Add max active downloading and seeding options to scheduler.
* Fix scheduler so that it keeps current state, even after global settings change.
==== AutoAdd ====
* AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception.
* Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled.
* Fix bugs with unicode torrents in AutoAdd plugin.
=== Deluge 1.3.0-rc2 (20 August 2010) ===
==== Core ====
* Fix tracker_icons failing on windows
* Fix #1302 an uncaught exception in an state_changed event handler in SessionProxy was preventing the TorrentManager's stop method from properly saving all the resume data
* Fix issue with SessionProxy not updating the torrent status correctly when get_torrent_status calls take place within the cache_expiry time
==== ConsoleUI ====
* #1307: Fix not being able to add torrents
* #1293: Fix not being able to add paths that contain backslashes
==== GtkUI ====
* Fix uncaught exception when closing deluge in classic mode
==== Execute ====
* #1306: Fix always executing last event
==== Label ====
* Fix being able to remove labels in web ui
==== WebUI ====
* #1319: Fix shift selecting in file trees
=== Deluge 1.3.0-rc1 (08 May 2010) ===
==== Core ====
* Implement #1063 option to delete torrent file copy on torrent removal - patch from Ghent
* Implement #457 progress bars for folders
* Implement #1012 httpdownloader supports gzip decoding
* #496: Remove deprecated functions in favour of get_session_status()
* #1112: Fix renaming files in add torrent dialog
* #1247: Fix deluge-gtk from hanging on shutdown
* #995: Rewrote tracker_icons
* Add AutoAdd plugin
* Add Notifications plugin
==== GtkUI ====
* Use new SessionProxy class for caching torrent status client-side
* Use torrent status diffs to reduce RPC traffic
==== Blocklist ====
* Implement local blocklist support
* #861: Pause transfers until blocklist is imported
* Fix redirection not working with relative paths
==== Execute ====
* Fix running commands with the TorrentAdded event
* Fix the web interface
==== Label ====
* Fix the web interface (#733)
* Fix blocklist status icon not opening the blocklist preference
page in certain locales
* Fix blocklist not recognising comments that begin with whitespace
* Minor speedup in parsing blocklists
* Blocklist now attempts to download the URL multiple times before giving
up
* Fix blocklist not being able to open zipped blocklists with python 2.5
==== Web ====
* Migrate to ExtJS 3.1
* Add gzip compression of HTTP data to the server
* Improve the efficiency of the TorrentGrid with lots of torrents (#1026)
* Add a base parameter to allow reverse proxying (#1076)
* Fix showing all the peers in the details tab (#1054)
* Fix uploading torrent files in Opera or IE (#1087)
* Complete IE support
* Put the default password in the manpage.
=== Deluge 1.2.0 - "Bursting like an infected kidney" (10 January 2010) ===
=== Deluge 1.2.0_rc5 (17 December 2009) ===
==== Web ====
* Swap order of buttons in Remove window (Closes #1083)
* Change the compressed js script to deluge-all.js to avoid naming
conflicts on case-sensitive filesystems.
* Apply patch from adios fixing the cookie date
* Add tooltips to the statusbar items
* Add disk usage to the statusbar
* Add a ToggleField widget and use this on the Downloads preferences
page allowing the movecom/copytorrent/autoadd boxes to be enabled.
* Fix enabling plugins.
* Implement installing plugins.
* Update some icons
* Fixed #1075 (changing priority on a whole folder doesn't work)
==== GtkUI ====
* Attempt to register as the default magnet uri handler in GNOME on startup
* Properly show 100.00% and reduce number of progress bar updates during a
torrent creation
* Fix crash in Windows when creating a torrent
* Add button to Other preferences to associate magnet links with Deluge
* Fix uploading plugins when the daemon is not localhost
* Fix #692 no longer require tray password when quitting from the tray icon
while the window is visible.
* Fix #782 do not ask for tray password when window is not minimized to tray
* Fix #1036 autoconnecting to localhost daemon on start-up
* Fix issue where hosts will show up erroneously as Offline
* Add #891 remove torrents by pressing the Delete key
* Fix issue where stoping a daemon that you aren't connected to causes the
gtkui to shutdown the currently connected daemon.
* Fix #594 tray password dialog freeze in Windows
* Made the password dialog prettier
* Fix #1086 deprecated gtk.Tooltips usage
* Fix #768 save tracker list for create torrent dialog
* Fix #1095 incorrect piece size used when using some non-English languages
==== Console ====
* Fix using the console in Windows, but only in command-line mode
* Fix #823 setting config values to -1.0
==== Label ====
* Fix #1085 only use ints for specific options to prevent unhandled exception
==== Execute ====
* Use the move_completed path if it is different to the save path
in the completed event.
==== Core ====
* Fix the upload_plugin rpc method (was still using XML-RPC stuff)
* Fix possible exception when upgrading from a 0.5 state file
* Use metavar to modify the help output by optparse.
* Partial fix for #1103 if the per-torrent option for stopping at a ratio has
been unchecked, then do not stop it at the global setting.
==== Blocklist ====
* Fix blocklist not working for all locales
* Fix blocklist checking for updates when it shouldn't
=== Deluge 1.2.0_rc4 (24 November 2009) ===
==== Core ====
* Fix deleting old .fastresume files with fresh configs
* Fix files list when using magnet uris
* Fix loading the saved metadata when loading state with magnet uris
==== GtkUI ====
* Fix showing the 'Other' speed dialogs in Windows
* Fix adding torrents from the Queued Torrents dialog
* Fix disabling/enabling plugins after switching daemons
* Reduce height of Add Torrent Dialog by ~80 pixels
* Fix #1071 issue where Deluge will fail to start if there is a stale ipc lockfile
* Fix autoconnecting to the next host in the list if the selected one isn't available
* Fix endless loop when trying to autoconnect to an offline daemon
* Fix exception on startup when the system tray icon is not enabled
* Fix issue where some torrents with special characters could not be added
* Fix issues adding magnet uris
==== Web ====
* Fix installing the deluge-web manpage
* Escape hyphens in the manpage
==== Console ====
* Escape hyphens in the manpage
* Make the delete key work
* Allow ~ to be used in the path in the add command
* Allow commands that are .pyc files to be used
* Fix printing info, help, etc.. on the command line
==== Blocklist ====
* Force blocklist to auto-detect format when a download / import is forced
* Fix blocklist failing on certain PeerGuardian/SafePeer lists
=== Deluge 1.2.0_rc3 (01 November 2009) ===
==== Core ====
* Fix #1047 move completed does not work if saving to non default path
* Fix renamed files not being utf-8 encoded
* Fix torrent name being blank when renaming root folder to /
* Do not include an 'announce-list' key in torrents when there is only one tracker
==== GtkUI ====
* Replace & with &amp; in the details tab to ensure there are no markup errors
* Consider 0 unlimited when displaying limits in the statusbar
* Fix adding torrents when not showing the add torrent dialog in Windows
* Fix crash when removing multiple torrents
==== Web ====
* Fix #1046 changing auto managed via the details tab
* Fix setting torrent options when adding
* Fix setting file priorities when adding
* HTML escape the field values on the details tab
* Fix #215, make infinite eta values display in the correct order
* Fix displaying the protocol upload speed
* Fix #990, showing 0 as a limit when it means unlimited in the statusbar
==== Console ====
* Fix displaying non-ascii strings
* Fix #1052 crash when issuing commands while not connected to a daemon
* Fix crash when string length makes line longer than terminal width
* Improve 'info' command draw speed
=== Deluge 1.2.0_rc2 (25 October 2009) ===
==== GtkUI ====
* Fix path errors when adding torrents externally in Windows
* Fix localclient authentication by stripping the lines read from the auth file
* Do not try to call doIteration() on the reactor if it has already stopped
* Fix 'autostart localhost if needed' option
* Fix starting plugins when the pluginmanager is started
* Fix #799 translate connection status
* Fix #215 ETA sort order
==== Core ====
* Fix saving torrent state on fresh configs
==== Web ====
* Fix changing of the allocation in the preferences.
* Fix updating the Connection Manager when a host is added.
* Add a `--fork` option to allow forking the webui to the background
* Fix the statusbar menu limits
* Fix setting the torrent options via the options tab
* Fix the private flag in the options tab
==== Console ====
* Fix exception when using the 'halt' command
==== Misc ====
* Add man pages for deluge-console, deluge-gtk and deluge-web
==== Extractor ====
* Fix issue where the plugin would not stop extracting files after being disabled
* Add option to create torrent name sub-folders in extract folder
=== Deluge 1.2.0_rc1 (07 October 2009) ===
==== Core ====
* Implement new RPC protocol DelugeRPC replacing XMLRPC
* Move to a twisted framework
@ -459,6 +222,134 @@
* Add new scripts for invoking UIs: deluge-gtk, deluge-web, deluge-console
* Remove GeoIP database from the source tree
=== Deluge 1.1.9 - (15 June 2009) ===
==== Core ====
* Only move a torrent due to 'move on complete' when some data has been downloaded
* Update libtorrent for CVE-2009-1760
==== GtkUI ====
* Fix #950 renaming a parent folder into multiple folders
==== WebUI ====
* Fix remote torrent add
=== Deluge 1.1.8 - (21 May 2009) ===
==== Core ====
* Fix pause all/resume all
* Torrent name is now changed when the root folder or file is renamed
==== GtkUI ====
* Fix high cpu usage when displaying speeds in titlebar
* Fix showing non-utf8 encoded torrents in add torrent dialog -- this adds
an additional dependency on chardet.
* Fix exception when timing out trying to send notification email
* Set some sane defaults for peers/file tabs column widths
==== WebUI ====
* Fix starting when -l option is used
=== Deluge 1.1.7 - (25 April 2009) ===
==== Core ====
* Fix issue where cannot resume torrent after doing a 'Pause All'
* Add workaround for 'address_v4 from unsigned long' bug experienced by users
with 64-bit machines. This bug is fixed in libtorrent 0.14.3.
==== GtkUI ====
* Fix #883 segfault if locale is not using UTF-8 encoding
* Fix for adding torrents with invalid filename encodings
* Fix displaying IPv6 peers in the Peers tab
* Fix starting the daemon in OS X
* Fix loading improperly created torrents with mismatched encodings
* Fix displaying improper progress when creating torrent
==== Windows ====
* Fix freezing in create torrent dialog
* Fix creating torrents in Windows
* Fix free space check
=== Deluge 1.1.6 - (06 April 2009) ===
==== Core ====
* Fix udp trackers being classified as DHT source
* Fix #855 force a resume on a torrent if a 'Force Recheck' is initiated
* Fix #862 deluged crash when access http://localhost:58846
==== GtkUI ====
* Fix displaying torrents with non-utf8 encodings in add torrent dialog
==== WebUI ====
* Fix #870 use proper config location for loading ssl cert
==== Misc ====
* Add OpenSSL exception to license
=== Deluge 1.1.5 - (16 March 2009) ===
==== Core ====
* Fix config file saving when no current config file exists
==== GtkUI ====
* Add 'Comments' field to the Details tab
* Fix #841 maximum upload slots tooltip
=== Deluge 1.1.4 - (08 March 2009) ===
==== Core ====
* Fix displaying file errors when the torrent isn't paused
* Fix issue where torrents being check would get removed due to "stop at ratio" rules
* Fix #790 tracker hosts not correct for some .uk trackers
* Make sure config files, resume data and state are fsync'd when saved. This should help prevent data losses on crashes/improper shutdowns.
==== GtkUI ====
* Fix hiding bottom pane when no tabs are enabled upon restart
* Fix saving file priorities when switching torrents in the addtorrentdialog
* Fix the allocate mode not being preserved when selecting different torrents in addtorrentdialog
* Fix #655 issue where default torrent options wouldn't be set for new torrents added to the addtorrentdialog
* Fix #817 email notifications fail to substitute format strings
==== Plugins ====
* Label: Fix setting 'Move on completed' folder when connected to a remote daemon
=== Deluge 1.1.3 - (15 February 2009) ===
==== Core ====
* Fix issue where checking queue would stop
* Fix announcing to SSL trackers
==== Misc ====
* Fix issue when initializing gettext that would prevent deluge from starting
* Fix logging exceptions when starting the daemon
* Fix displaying errors when a torrent is Checking
* Fix #790 tracker hosts not correct for some 3rd-level domain names
=== Deluge 1.1.2 - (31 January 2009) ===
==== Core ====
* Fix issue where torrents get stuck Checking
==== GtkUI ====
* Fix #761 use proper theme colours in sidebar
* Fix saving files/peers tab state when no column is sorted
=== Deluge 1.1.1 - (24 January 2009) ===
==== Core ====
* Fix oldstateupgrader for those upgrading from 0.5.x
* Fix setting Peer TOS byte
* Fix setting outgoing ports
==== GtkUI ====
* Fix opening links from Help menu and others
* Fix remembering sorted column in the torrent list
* Fix saving Files tab and Peers tab state
* Disable popup notification in preferences on Windows
* Fix crashing in Add Torrent Dialog when removing torrents from the list
* Do not allow duplicate torrents in the Add Torrent Dialog
* Fix translating speed units in status tab when a per-torrent limit is set
* Fix torrents not displaying properly after disconnecting and reconnecting to the daemon
* Fix when sorting # column, downloads should be on top
==== Misc ====
* Fix bdecoding some torrent files
* Fix the -l, --logfile option
* Fix #729 tracker icons not being saved in the correct location
* Add support for more tracker icons
* Fix being able to connect to a local daemon from another user account
=== Deluge 1.1.0 - "Time gas!" (10 January 2009) ===
==== Core ====
* Implement #79 ability to change outgoing port range

22
DEPENDS
View File

@ -1,30 +1,24 @@
=== Core ===
* python >= 2.6
* python >= 2.5
* twisted >= 8.1
* twisted-web >= 8.1
* pyopenssl
* simplejson (if python < 2.6)
* setuptools
* gettext
* intltool
* pyxdg
* chardet
* geoip-database (optional)
* setproctitle (optional)
* rencode >= 1.0.2 (optional), a Python port is already included
* libtorrent >= 0.14.5
* libtorrent (rasterbar) >= 0.16.7
* If building libtorrent:
* boost >= 1.40
* openssl
* zlib
=== UIs ===
* chardet
=== Gtk ===
* pygtk >= 2.16
* python-notify (libnotify python wrapper)
* pygame
* pygtk >= 2.12
* librsvg
* xdg-utils
* python-notify (optional)
* pygame (optional)
=== Web ===
* mako

View File

@ -1,23 +0,0 @@
include AUTHORS ChangeLog DEPENDS ez_setup.py LICENSE msgfmt.py RELEASE-VERSION version.py
graft docs/man
include deluge/i18n/*.po
graft deluge/plugins
recursive-exclude deluge/plugins create_dev_link.sh *.pyc
prune deluge/tests
graft deluge/ui/data
graft deluge/ui/gtkui/glade
include deluge/ui/web/index.html
include deluge/ui/web/gettext.js
include deluge/ui/web/css/*.css
exclude deluge/ui/web/css/*-debug.css
include deluge/ui/web/js/*.js
exclude deluge/ui/web/js/*-debug.js
exclude deluge/ui/web/gen_gettext.py
graft deluge/ui/web/themes
graft deluge/ui/web/render
graft deluge/ui/web/icons
graft deluge/ui/web/images

75
README
View File

@ -1,3 +1,4 @@
==========================
Deluge BitTorrent Client
==========================
@ -8,50 +9,86 @@ Authors:
Andrew Resch
Damien Churchill
For contributors and past developers see:
AUTHORS
For past developers and contributers see: http://dev.deluge-torrent.org/wiki/About
==========================
Installation Instructions:
License
==========================
For detailed instructions see: http://dev.deluge-torrent.org/wiki/Installing/Source
Ensure build dependencies are installed, see DEPENDS for a full listing.
Build and install by running:
$ python setup.py build
$ sudo python setup.py install
Deluge is under the GNU GPLv3 license.
Icon data/pixmaps/deluge.svg and derivatives in data/icons are copyright
Andrew Wedderburn and are under the GNU GPLv3.
All other icons in data/pixmaps are copyright Andrew Resch and are under
the GNU GPLv3.
==========================
Contact/Support:
==========================
Forum: http://forum.deluge-torrent.org
IRC Channel: #deluge on irc.freenode.net
We have two options available for support:
Our Forum, at: http://forum.deluge-torrent.org
or
Our IRC Channel, at #deluge on Freenode: http://freenode.net
==========================
Installation Instructions:
==========================
For more detailed instructions see: http://dev.deluge-torrent.org/wiki/Installing/Source
See: DEPENDS for a full list of dependencies.
First, make sure you have the proper build dependencies installed. On a normal
Debian or Ubuntu system:
sudo apt-get install g++ make python-all-dev python-all python-dbus \
python-gtk2 python-notify librsvg2-common python-xdg python-support \
subversion libboost-dev libboost-python-dev \
libboost-thread-dev libboost-date-time-dev libboost-filesystem-dev \
libssl-dev zlib1g-dev python-setuptools \
python-mako python-twisted-web python-chardet python-simplejson
The names of the packages may vary depending on your OS / distro.
Once you have the needed libraries installed, build and install by running:
$ python setup.py build
$ sudo python setup.py install
==========================
FAQ
==========================
For the full FAQ see: http://dev.deluge-torrent.org/wiki/Faq
How to start the various user-interfaces
Gtk:
deluge or deluge-gtk
deluge-gtk
Console:
deluge-console
Web:
deluge-web
Go to http://localhost:8112/ default-password = "deluge"
Why is deluge still listed in my system tray even after I close it ?
You closed the gtk user-interface but you did not close the daemon. Choose "Quit & Shutdown Daemon" to close both Daemon and gtk-ui.
How do I start the daemon?
deluged
How do I start the daemon with logging to console?
deluged -d -L <log level>
I can't connect to the daemon from another machine
See: http://dev.deluge-torrent.org/wiki/UserGuide/ThinClient
* Configure the daemon to allow remote connections
* Add a user to the auth file located in the config folder: ~/.config/deluge/auth
* Restart the daemon.
I upgraded from 0.5 and plugin x is missing
1.0 is a rewrite, all old 0.5 plugins have to be rewritten.
For the full FAQ see: http://dev.deluge-torrent.org/wiki/Faq

View File

@ -1,21 +0,0 @@
#!/bin/sh
# Fixes glade files which may have set gtk stock labels set to translatable
for x in `find . -name '*.glade' |grep -v '.git\|build'` ; do \
for y in gtk-add gtk-apply gtk-bold gtk-cancel gtk-cdrom gtk-clear \
gtk-close gtk-color-picker gtk-connect gtk-convert gtk-copy gtk-cut \
gtk-delete gtk-dialog-error gtk-dialog-info gtk-dialog-question \
gtk-dialog-warning gtk-dnd gtk-dnd-multiple gtk-edit gtk-execute gtk-find \
gtk-find-and-replace gtk-floppy gtk-goto-bottom gtk-goto-first \
gtk-goto-last gtk-goto-top gtk-go-back gtk-go-down gtk-go-forward \
gtk-go-up gtk-help gtk-home gtk-index gtk-italic gtk-jump-to \
gtk-justify-center gtk-justify-fill gtk-justify-left gtk-missing-image \
gtk-new gtk-no gtk-ok gtk-open gtk-paste gtk-preferences gtk-print \
gtk-print-preview gtk-properties gtk-quit gtk-redo gtk-refresh \
gtk-remove gtk-revert-to-saved gtk-save gtk-save-as gtk-select-color \
gtk-select-font gtk-sort-descending gtk-spell-check gtk-stop \
gtk-strikethrough gtk-undelete gtk-underline gtk-undo gtk-yes \
gtk-zoom-100 gtk-zoom-fit gtk-zoom-in gtk-zoom-out; do \
sed -i "s/<property\ name\=\"label\"\ translatable\=\"yes\">$y<\/property>/<property\ name\=\"label\"\ translatable\=\"no\">$y<\/property>/g" $x; \
done;\
done

17
create_potfiles_in.py Executable file → Normal file
View File

@ -1,26 +1,17 @@
#!/usr/bin/env python
import os
import re
import sys
# Paths to exclude
EXCLUSIONS = [
"deluge/scripts",
"deluge/i18n",
"deluge/scripts"
]
POTFILE_IN = "deluge/i18n/POTFILES.in"
pattern = "deluge\/plugins\/.*\/build"
compiled = re.compile(pattern)
sys.stdout.write("Creating " + POTFILE_IN + " ... ")
sys.stdout.flush()
print "Creating " + POTFILE_IN + " .."
to_translate = []
for (dirpath, dirnames, filenames) in os.walk("deluge"):
for filename in filenames:
if os.path.splitext(filename)[1] in (".py", ".glade", ".in") \
and dirpath not in EXCLUSIONS \
and not compiled.match(dirpath):
if os.path.splitext(filename)[1] in (".py", ".glade") and dirpath not in EXCLUSIONS:
to_translate.append(os.path.join(dirpath, filename))
f = open(POTFILE_IN, "wb")

View File

@ -1,6 +1,6 @@
#!/bin/bash
for size in 16 22 24 32 36 48 64 72 96 128 192 256; do mkdir -p deluge/ui/data/\
for size in 16 22 24 32 36 48 64 72 96 128 192 256; do mkdir -p deluge/data/\
icons/hicolor/${size}x${size}/apps; rsvg-convert -w ${size} -h ${size} \
-o deluge/ui/data/icons/hicolor/${size}x${size}/apps/deluge.png deluge/ui/data/pixmaps\
/deluge.svg; mkdir -p deluge/ui/data/icons/scalable/apps/; cp deluge/ui/data/pixmaps/\
deluge.svg deluge/ui/data/icons/scalable/apps/deluge.svg; done
-o deluge/data/icons/hicolor/${size}x${size}/apps/deluge.png deluge/data/pixmaps\
/deluge.svg; mkdir -p deluge/data/icons/scalable/apps/; cp deluge/data/pixmaps/\
deluge.svg deluge/data/icons/scalable/apps/deluge.svg; done

View File

@ -1,4 +1 @@
"""Deluge"""
# this is a namespace package
import pkg_resources
pkg_resources.declare_namespace(__name__)

View File

@ -45,9 +45,9 @@ supports.
"""
REQUIRED_VERSION = "0.16.7.0"
REQUIRED_VERSION = "0.14.5.0"
def check_version(lt):
def check_version(LT):
from deluge.common import VersionSplit
if VersionSplit(lt.version) < VersionSplit(REQUIRED_VERSION):
raise ImportError("This version of Deluge requires libtorrent >=%s!" % REQUIRED_VERSION)
@ -57,4 +57,4 @@ try:
check_version(lt)
except ImportError:
import libtorrent as lt
check_version(lt)
check_version(lt)

View File

@ -17,9 +17,9 @@
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
@ -37,25 +37,16 @@
"""Common functions for various parts of Deluge to use."""
import os
import sys
import time
import subprocess
import platform
import chardet
import logging
import pkg_resources
import gettext
import locale
import sys
try:
import json
except ImportError:
import simplejson as json
from deluge.error import *
log = logging.getLogger(__name__)
# Do a little hack here just in case the user has json-py installed since it
# has a different api
if not hasattr(json, "dumps"):
@ -71,6 +62,11 @@ if not hasattr(json, "dumps"):
json.dump = dump
json.load = load
import pkg_resources
import xdg, xdg.BaseDirectory
from deluge.error import *
LT_TORRENT_STATE = {
"Queued": 0,
"Checking": 1,
@ -90,6 +86,7 @@ LT_TORRENT_STATE = {
7: "Checking Resume Data"
}
TORRENT_STATE = [
"Allocating",
"Checking",
@ -104,15 +101,11 @@ FILE_PRIORITY = {
0: "Do Not Download",
1: "Normal Priority",
2: "High Priority",
3: "High Priority",
4: "High Priority",
5: "High Priority",
6: "High Priority",
7: "Highest Priority",
5: "Highest Priority",
"Do Not Download": 0,
"Normal Priority": 1,
"High Priority": 5,
"Highest Priority": 7
"High Priority": 2,
"Highest Priority": 5
}
def get_version():
@ -133,26 +126,16 @@ def get_default_config_dir(filename=None):
:rtype: string
"""
if windows_check():
def save_config_path(resource):
appDataPath = os.environ.get("APPDATA")
if not appDataPath:
import _winreg
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
appDataReg = _winreg.QueryValueEx(hkey, "AppData")
appDataPath = appDataReg[0]
_winreg.CloseKey(hkey)
return os.path.join(appDataPath, resource)
if filename:
return os.path.join(os.environ.get("APPDATA"), "deluge", filename)
else:
return os.path.join(os.environ.get("APPDATA"), "deluge")
else:
from xdg.BaseDirectory import save_config_path
if not filename:
filename = ''
try:
return os.path.join(save_config_path("deluge"), filename)
except OSError, e:
log.error("Unable to use default config directory, exiting... (%s)", e)
sys.exit(1)
if filename:
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
else:
return xdg.BaseDirectory.save_config_path("deluge")
def get_default_download_dir():
"""
@ -161,20 +144,8 @@ def get_default_download_dir():
"""
if windows_check():
return os.path.join(os.path.expanduser("~"), 'Downloads')
return os.path.expanduser("~")
else:
from xdg.BaseDirectory import xdg_config_home
userdir_file = os.path.join(xdg_config_home, 'user-dirs.dirs')
try:
for line in open(userdir_file, 'r'):
if not line.startswith('#') and 'XDG_DOWNLOAD_DIR' in line:
download_dir = os.path.expandvars(\
line.partition("=")[2].rstrip().strip('"'))
if os.path.isdir(download_dir):
return download_dir
except IOError:
pass
return os.environ.get("HOME")
def windows_check():
@ -209,7 +180,7 @@ def osx_check():
def get_pixmap(fname):
"""
Provides easy access to files in the deluge/ui/data/pixmaps folder within the Deluge egg
Provides easy access to files in the deluge/data/pixmaps folder within the Deluge egg
:param fname: the filename to look for
:type fname: string
@ -217,18 +188,8 @@ def get_pixmap(fname):
:rtype: string
"""
return resource_filename("deluge", os.path.join("ui", "data", "pixmaps", fname))
def resource_filename(module, path):
# While developing, if there's a second deluge package, installed globally
# and another in develop mode somewhere else, while pkg_resources.require("Deluge")
# returns the proper deluge instance, pkg_resources.resource_filename does
# not, it returns the first found on the python path, which is not good
# enough.
# This is a work-around that.
return pkg_resources.require("Deluge>=%s" % get_version())[0].get_resource_filename(
pkg_resources._manager, os.path.join(*(module.split('.')+[path]))
)
return pkg_resources.resource_filename("deluge", os.path.join("data", \
"pixmaps", fname))
def open_file(path):
"""
@ -239,9 +200,7 @@ def open_file(path):
"""
if windows_check():
os.startfile(path.decode("utf8"))
elif osx_check():
subprocess.Popen(["open", "%s" % path])
os.startfile("%s" % path)
else:
subprocess.Popen(["xdg-open", "%s" % path])
@ -251,7 +210,7 @@ def open_url_in_browser(url):
:param url: the url to open
:type url: string
"""
import webbrowser
webbrowser.open(url)
@ -275,36 +234,12 @@ def fsize(fsize_b):
"""
fsize_kb = fsize_b / 1024.0
if fsize_kb < 1024:
return "%.1f %s" % (fsize_kb, _("KiB"))
return "%.1f KiB" % fsize_kb
fsize_mb = fsize_kb / 1024.0
if fsize_mb < 1024:
return "%.1f %s" % (fsize_mb, _("MiB"))
return "%.1f MiB" % fsize_mb
fsize_gb = fsize_mb / 1024.0
return "%.1f %s" % (fsize_gb, _("GiB"))
def fsize_short(fsize_b):
"""
Formats the bytes value into a string with K, M or G units
:param fsize_b: the filesize in bytes
:type fsize_b: int
:returns: formatted string in K, M or G units
:rtype: string
**Usage**
>>> fsize(112245)
'109.6 K'
"""
fsize_kb = fsize_b / 1024.0
if fsize_kb < 1024:
return "%.1f %s" % (fsize_kb, _("K"))
fsize_mb = fsize_kb / 1024.0
if fsize_mb < 1024:
return "%.1f %s" % (fsize_mb, _("M"))
fsize_gb = fsize_mb / 1024.0
return "%.1f %s" % (fsize_gb, _("G"))
return "%.1f GiB" % fsize_gb
def fpcnt(dec):
"""
@ -338,14 +273,7 @@ def fspeed(bps):
'42.1 KiB/s'
"""
fspeed_kb = bps / 1024.0
if fspeed_kb < 1024:
return "%.1f %s" % (fspeed_kb, _("KiB/s"))
fspeed_mb = fspeed_kb / 1024.0
if fspeed_mb < 1024:
return "%.1f %s" % (fspeed_mb, _("MiB/s"))
fspeed_gb = fspeed_mb / 1024.0
return "%.1f %s" % (fspeed_gb, _("GiB/s"))
return '%s/s' % (fsize(bps))
def fpeer(num_peers, total_peers):
"""
@ -412,21 +340,21 @@ def ftime(seconds):
def fdate(seconds):
"""
Formats a date time string in the locale's date representation based on the systems timezone
Formats a date string in the locale's date representation based on the systems timezone
:param seconds: time in seconds since the Epoch
:type seconds: float
:returns: a string in the locale's datetime representation or "" if seconds < 0
:returns: a string in the locale's date representation or "" if seconds < 0
:rtype: string
"""
if seconds < 0:
return ""
return time.strftime("%x %X", time.localtime(seconds))
return time.strftime("%x", time.localtime(seconds))
def is_url(url):
"""
A simple test to check if the URL is valid
A simple regex test to check if the URL is valid
:param url: the url to test
:type url: string
@ -439,7 +367,8 @@ def is_url(url):
True
"""
return url.partition('://')[0] in ("http", "https", "ftp", "udp")
import re
return bool(re.search('^(https?|ftp|udp)://', url))
def is_magnet(uri):
"""
@ -456,12 +385,34 @@ def is_magnet(uri):
True
"""
magnet_scheme = 'magnet:?'
xt_param = 'xt=urn:btih:'
if uri.startswith(magnet_scheme) and xt_param in uri:
if uri[:20] == "magnet:?xt=urn:btih:":
return True
return False
def fetch_url(url):
"""
Downloads a torrent file from a given URL and checks the file's validity
:param url: the url of the .torrent file to fetch
:type url: string
:returns: the filepath to the downloaded file
:rtype: string
"""
import urllib
from deluge.log import LOG as log
try:
filename, headers = urllib.urlretrieve(url)
except IOError:
log.debug("Network error while trying to fetch torrent from %s", url)
else:
if filename.endswith(".torrent") or headers["content-type"] ==\
"application/x-bittorrent":
return filename
else:
log.debug("URL doesn't appear to be a valid torrent file: %s", url)
return None
def create_magnet_uri(infohash, name=None, trackers=[]):
"""
Creates a magnet uri
@ -518,19 +469,20 @@ def free_space(path):
:type path: string
:returns: the free space at path in bytes
:rtype: int
:raises InvalidPathError: if the path is not valid
"""
if not os.path.exists(path):
raise InvalidPathError("%s is not a valid path" % path)
if windows_check():
from win32file import GetDiskFreeSpaceEx
return GetDiskFreeSpaceEx(path)[0]
import win32file
sectors, bytes, free, total = map(long, win32file.GetDiskFreeSpace(path))
return (free * sectors * bytes)
else:
disk_data = os.statvfs(path.encode("utf8"))
block_size = disk_data.f_frsize
disk_data = os.statvfs(path)
block_size = disk_data.f_bsize
return disk_data.f_bavail * block_size
def is_ip(ip):
@ -551,290 +503,58 @@ def is_ip(ip):
import socket
#first we test ipv4
try:
if windows_check():
if socket.inet_aton("%s" % (ip)):
return True
else:
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
return True
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
return True
except socket.error:
if not socket.has_ipv6:
return False
#now test ipv6
try:
if windows_check():
log.warning("ipv6 check unavailable on windows")
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
return True
else:
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
return True
except socket.error:
return False
def path_join(*parts):
"""
An implementation of os.path.join that always uses / for the separator
to ensure that the correct paths are produced when working with internal
paths on Windows.
"""
path = ''
for part in parts:
if not part:
continue
elif part[0] == '/':
path = part
elif not path:
path = part
else:
path += '/' + part
return path
XML_ESCAPES = (
('&', '&amp;'),
('<', '&lt;'),
('>', '&gt;'),
('"', '&quot;'),
("'", '&apos;')
)
def xml_decode(string):
"""
Unescape a string that was previously encoded for use within xml.
:param string: The string to escape
:type string: string
:returns: The unescaped version of the string.
:rtype: string
"""
for char, escape in XML_ESCAPES:
string = string.replace(escape, char)
return string
def xml_encode(string):
"""
Escape a string for use within an xml element or attribute.
:param string: The string to escape
:type string: string
:returns: An escaped version of the string.
:rtype: string
"""
for char, escape in XML_ESCAPES:
string = string.replace(char, escape)
return string
def decode_string(s, encoding="utf8"):
"""
Decodes a string and return unicode. If it cannot decode using
`:param:encoding` then it will try latin1, and if that fails,
try to detect the string encoding. If that fails, decode with
ignore.
:param s: string to decode
:type s: string
:keyword encoding: the encoding to use in the decoding
:type encoding: string
:returns: s converted to unicode
:rtype: unicode
"""
if not s:
return u''
elif isinstance(s, unicode):
return s
encodings = [lambda: ("utf8", 'strict'),
lambda: ("iso-8859-1", 'strict'),
lambda: (chardet.detect(s)["encoding"], 'strict'),
lambda: (encoding, 'ignore')]
if not encoding is "utf8":
encodings.insert(0, lambda: (encoding, 'strict'))
for l in encodings:
try:
return s.decode(*l())
except UnicodeDecodeError:
pass
return u''
def utf8_encoded(s, encoding="utf8"):
"""
Returns a utf8 encoded string of s
:param s: (unicode) string to (re-)encode
:type s: basestring
:keyword encoding: the encoding to use in the decoding
:type encoding: string
:returns: a utf8 encoded string of s
:rtype: str
"""
if isinstance(s, str):
s = decode_string(s, encoding).encode("utf8")
elif isinstance(s, unicode):
s = s.encode("utf8")
return s
class VersionSplit(object):
"""
Used for comparing version numbers.
:param ver: the version
:type ver: string
"""
def __init__(self, ver):
import re
VERSION_RE = re.compile(r'''
^
(?P<version>\d+\.\d+) # minimum 'N.N'
(?P<extraversion>(?:\.\d+)*) # any number of extra '.N' segments
(?:
(?P<prerel>[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate
# 'rc'= alias for release candidate
(?P<prerelversion>\d+(?:\.\d+)*)
)?
(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
$''', re.VERBOSE)
# Check for PEP 386 compliant version
match = re.search(VERSION_RE, ver)
if match:
group = [(x if x is not None else '') for x in match.group(1,2,3,4,8)]
vs = [''.join(group[0:2]),''.join(group[2:4]), group[4].lstrip('.')]
else:
ver = ver.lower()
vs = ver.replace("_", "-").split("-")
self.version = [int(x) for x in vs[0].split(".")]
ver = ver.lower()
vs = ver.split("_") if "_" in ver else ver.split("-")
self.version = vs[0]
self.suffix = None
self.dev = False
if len(vs) > 1:
if vs[1].startswith(("rc", "a", "b", "c")):
self.suffix = vs[1]
if vs[-1].startswith('dev'):
self.dev = vs[-1]
for s in ("rc", "alpha", "beta", "dev"):
if s in vs[1][:len(s)]:
self.suffix = vs[1]
def __cmp__(self, ver):
"""
The comparison method.
:param ver: the version to compare with
:type ver: VersionSplit
"""
# PEP 386 versions with .devN precede release version
if (bool(self.dev) != bool(ver.dev)):
if self.dev != 'dev':
self.dev = not self.dev
if ver.dev != 'dev':
ver.dev = not ver.dev
if self.version > ver.version or (self.suffix and self.suffix[:3] == "dev"):
return 1
if self.version < ver.version:
return -1
# If there is no suffix we use z because we want final
# to appear after alpha, beta, and rc alphabetically.
v1 = [self.version, self.suffix or 'z', self.dev]
v2 = [ver.version, ver.suffix or 'z', ver.dev]
return cmp(v1, v2)
# Common AUTH stuff
AUTH_LEVEL_NONE = 0
AUTH_LEVEL_READONLY = 1
AUTH_LEVEL_NORMAL = 5
AUTH_LEVEL_ADMIN = 10
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
def create_auth_file():
import stat, configmanager
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
fd = open(auth_file, "w")
fd.flush()
os.fsync(fd.fileno())
fd.close()
# Change the permissions on the file so only this user can read/write it
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
def create_localclient_account(append=False):
import configmanager, random
auth_file = configmanager.get_config_dir("auth")
if not os.path.exists(auth_file):
create_auth_file()
try:
from hashlib import sha1 as sha_hash
except ImportError:
from sha import new as sha_hash
fd = open(auth_file, "a" if append else "w")
fd.write(":".join([
"localclient",
sha_hash(str(random.random())).hexdigest(),
str(AUTH_LEVEL_ADMIN)
]) + '\n')
fd.flush()
os.fsync(fd.fileno())
fd.close()
# Initialize gettext
def setup_translations(setup_pygtk=False):
translations_path = resource_filename("deluge", "i18n")
log.info("Setting up translations from %s", translations_path)
try:
if hasattr(locale, "bindtextdomain"):
locale.bindtextdomain("deluge", translations_path)
if hasattr(locale, "textdomain"):
locale.textdomain("deluge")
gettext.install("deluge", translations_path, unicode=True)
if setup_pygtk:
# Even though we're not using glade anymore, let's set it up so that
# plugins still using it get properly translated.
log.info("Setting up GTK translations from %s", translations_path)
import gtk
import gtk.glade
gtk.glade.bindtextdomain("deluge", translations_path)
gtk.glade.textdomain("deluge")
except Exception, e:
log.error("Unable to initialize gettext/locale!")
log.exception(e)
import __builtin__
__builtin__.__dict__["_"] = lambda x: x
def 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
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(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)]
else:
# On other platforms, we have to find the likely encoding of the args and decode
# First check if sys.stdout or stdin have encoding set
encoding = getattr(sys.stdout, "encoding") or getattr(sys.stdin, "encoding")
# If that fails, check what the locale is set to
encoding = encoding or locale.getpreferredencoding()
# As a last resort, just default to utf-8
encoding = encoding or "utf-8"
return [arg.decode(encoding) for arg in sys.argv]
if self.version == ver.version:
if self.suffix == ver.suffix:
return 0
if self.suffix is None:
return 1
if ver.suffix is None:
return -1
if self.suffix < ver.suffix:
return -1
if self.suffix > ver.suffix:
return 1

View File

@ -1,7 +1,7 @@
#
# component.py
#
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -33,413 +33,213 @@
#
#
import logging
from collections import defaultdict
from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail
from twisted.internet.task import LoopingCall
from deluge.log import LOG as log
log = logging.getLogger(__name__)
class ComponentAlreadyRegistered(Exception):
pass
COMPONENT_STATE = [
"Stopped",
"Started",
"Paused"
]
class Component(object):
"""
Component objects are singletons managed by the :class:`ComponentRegistry`.
When a new Component object is instantiated, it will be automatically
registered with the :class:`ComponentRegistry`.
The ComponentRegistry has the ability to start, stop, pause and shutdown the
components registered with it.
**Events:**
**start()** - This method is called when the client has connected to a
Deluge core.
**stop()** - This method is called when the client has disconnected from a
Deluge core.
**update()** - This method is called every 1 second by default while the
Componented is in a *Started* state. The interval can be
specified during instantiation. The update() timer can be
paused by instructing the :class:`ComponentRegistry` to pause
this Component.
**shutdown()** - This method is called when the client is exiting. If the
Component is in a "Started" state when this is called, a
call to stop() will be issued prior to shutdown().
**States:**
A Component can be in one of these 5 states.
**Started** - The Component has been started by the :class:`ComponentRegistry`
and will have it's update timer started.
**Starting** - The Component has had it's start method called, but it hasn't
fully started yet.
**Stopped** - The Component has either been stopped or has yet to be started.
**Stopping** - The Component has had it's stop method called, but it hasn't
fully stopped yet.
**Paused** - The Component has had it's update timer stopped, but will
still be considered in a Started state.
"""
def __init__(self, name, interval=1, depend=None):
self._component_name = name
self._component_interval = interval
self._component_depend = depend
self._component_state = "Stopped"
self._component_timer = None
self._component_starting_deferred = None
self._component_stopping_deferred = None
_ComponentRegistry.register(self)
# Register with the ComponentRegistry
register(name, self, depend)
self._interval = interval
self._timer = None
self._state = COMPONENT_STATE.index("Stopped")
self._name = name
def __del__(self):
if _ComponentRegistry:
_ComponentRegistry.deregister(self)
def get_state(self):
return self._state
def _component_start_timer(self):
if hasattr(self, "update"):
self._component_timer = LoopingCall(self.update)
self._component_timer.start(self._component_interval)
def _component_start(self):
def on_start(result):
self._component_state = "Started"
self._component_starting_deferred = None
self._component_start_timer()
return True
def on_start_fail(result):
self._component_state = "Stopped"
self._component_starting_deferred = None
log.error(result)
return result
if self._component_state == "Stopped":
if hasattr(self, "start"):
self._component_state = "Starting"
d = maybeDeferred(self.start)
d.addCallback(on_start)
d.addErrback(on_start_fail)
self._component_starting_deferred = d
else:
d = maybeDeferred(on_start, None)
elif self._component_state == "Starting":
return self._component_starting_deferred
elif self._component_state == "Started":
d = succeed(True)
else:
d = fail("Cannot start a component not in a Stopped state!")
return d
def _component_stop(self):
def on_stop(result):
self._component_state = "Stopped"
if self._component_timer and self._component_timer.running:
self._component_timer.stop()
return True
def on_stop_fail(result):
self._component_state = "Started"
self._component_stopping_deferred = None
log.error(result)
return result
if self._component_state != "Stopped" and self._component_state != "Stopping":
if hasattr(self, "stop"):
self._component_state = "Stopping"
d = maybeDeferred(self.stop)
d.addCallback(on_stop)
d.addErrback(on_stop_fail)
self._component_stopping_deferred = d
else:
d = maybeDeferred(on_stop, None)
if self._component_state == "Stopping":
return self._component_stopping_deferred
return succeed(None)
def _component_pause(self):
def on_pause(result):
self._component_state = "Paused"
if self._component_state == "Started":
if self._component_timer and self._component_timer.running:
d = maybeDeferred(self._component_timer.stop)
d.addCallback(on_pause)
else:
d = succeed(None)
elif self._component_state == "Paused":
d = succeed(None)
else:
d = fail("Cannot pause a component in a non-Started state!")
return d
def _component_resume(self):
def on_resume(result):
self._component_state = "Started"
if self._component_state == "Paused":
d = maybeDeferred(self._component_start_timer)
d.addCallback(on_resume)
else:
d = fail("Component cannot be resumed from a non-Paused state!")
return d
def _component_shutdown(self):
def on_stop(result):
if hasattr(self, "shutdown"):
return maybeDeferred(self.shutdown)
return succeed(None)
d = self._component_stop()
d.addCallback(on_stop)
return d
def get_component_name(self):
return self._name
def start(self):
pass
def _start(self):
self._state = COMPONENT_STATE.index("Started")
if hasattr(self, "update"):
self._timer = LoopingCall(self.update)
self._timer.start(self._interval)
def stop(self):
pass
def update(self):
pass
def _stop(self):
self._state = COMPONENT_STATE.index("Stopped")
try:
self._timer.stop()
except:
pass
def _pause(self):
self._state = COMPONENT_STATE.index("Paused")
try:
self._timer.stop()
except:
pass
def _resume(self):
self._start()
def shutdown(self):
pass
class ComponentRegistry(object):
"""
The ComponentRegistry holds a list of currently registered
:class:`Component` objects. It is used to manage the Components by
starting, stopping, pausing and shutting them down.
"""
class ComponentRegistry:
def __init__(self):
self.components = {}
# Stores all of the components that are dependent on a particular component
self.dependents = defaultdict(list)
self.depend = {}
def register(self, obj):
"""
Registers a component object with the registry. This is done
automatically when a Component object is instantiated.
def register(self, name, obj, depend):
"""Registers a component.. depend must be list or None"""
log.debug("Registered %s with ComponentRegistry..", name)
self.components[name] = obj
if depend != None:
self.depend[name] = depend
:param obj: the Component object
:type obj: object
:raises ComponentAlreadyRegistered: if a component with the same name is already registered.
"""
name = obj._component_name
def deregister(self, name):
"""Deregisters a component"""
if name in self.components:
raise ComponentAlreadyRegistered(
"Component already registered with name %s" % name)
log.debug("Deregistering Component: %s", name)
self.stop_component(name)
del self.components[name]
self.components[obj._component_name] = obj
if obj._component_depend:
for depend in obj._component_depend:
self.dependents[depend].append(name)
def get(self, name):
"""Returns a reference to the component 'name'"""
return self.components[name]
def deregister(self, obj):
"""
Deregisters a component from the registry. A stop will be
issued to the component prior to deregistering it.
def start(self):
"""Starts all components"""
for component in self.components.keys():
self.start_component(component)
:param obj: the Component object
:type obj: object
def start_component(self, name):
"""Starts a component"""
# Check to see if this component has any dependencies
if self.depend.has_key(name):
for depend in self.depend[name]:
self.start_component(depend)
"""
# Only start if the component is stopped.
if self.components[name].get_state() == \
COMPONENT_STATE.index("Stopped"):
log.debug("Starting component %s..", name)
self.components[name].start()
self.components[name]._start()
if obj in self.components.values():
log.debug("Deregistering Component: %s", obj._component_name)
d = self.stop([obj._component_name])
def on_stop(result, name):
del self.components[name]
return d.addCallback(on_stop, obj._component_name)
else:
return succeed(None)
def stop(self):
"""Stops all components"""
# We create a separate list of the keys and do an additional check to
# make sure the key still exists in the components dict.
# This is because components could be deregistered during a stop and
# the dictionary would get modified while iterating through it.
components = self.components.keys()
for component in components:
if component in self.components:
self.stop_component(component)
def start(self, names=[]):
"""
Starts Components that are currently in a Stopped state and their
dependencies. If *names* is specified, will only start those
Components and their dependencies and if not it will start all
registered components.
def stop_component(self, component):
if self.components[component].get_state() != \
COMPONENT_STATE.index("Stopped"):
log.debug("Stopping component %s..", component)
self.components[component].stop()
self.components[component]._stop()
:param names: a list of Components to start
:type names: list
def pause(self):
"""Pauses all components. Stops calling update()"""
for component in self.components.keys():
self.pause_component(component)
:returns: a Deferred object that will fire once all Components have been sucessfully started
:rtype: twisted.internet.defer.Deferred
def pause_component(self, component):
if self.components[component].get_state() not in \
[COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]:
log.debug("Pausing component %s..", component)
self.components[component]._pause()
"""
# Start all the components if names is empty
if not names:
names = self.components.keys()
elif isinstance(names, str):
names = [names]
def resume(self):
"""Resumes all components. Starts calling update()"""
for component in self.components.keys():
self.resume_component(component)
def on_depends_started(result, name):
return self.components[name]._component_start()
deferreds = []
for name in names:
if self.components[name]._component_depend:
# This component has depends, so we need to start them first.
d = self.start(self.components[name]._component_depend)
d.addCallback(on_depends_started, name)
deferreds.append(d)
else:
deferreds.append(self.components[name]._component_start())
return DeferredList(deferreds)
def stop(self, names=[]):
"""
Stops Components that are currently not in a Stopped state. If
*names* is specified, then it will only stop those Components,
and if not it will stop all the registered Components.
:param names: a list of Components to start
:type names: list
:returns: a Deferred object that will fire once all Components have been sucessfully stopped
:rtype: twisted.internet.defer.Deferred
"""
if not names:
names = self.components.keys()
elif isinstance(names, str):
names = [names]
def on_dependents_stopped(result, name):
return self.components[name]._component_stop()
stopped_in_deferred = set()
deferreds = []
for name in names:
if name in stopped_in_deferred:
continue
if name in self.components:
if name in self.dependents:
# If other components depend on this component, stop them first
d = self.stop(self.dependents[name]).addCallback(on_dependents_stopped, name)
deferreds.append(d)
stopped_in_deferred.update(self.dependents[name])
else:
deferreds.append(self.components[name]._component_stop())
return DeferredList(deferreds)
def pause(self, names=[]):
"""
Pauses Components that are currently in a Started state. If
*names* is specified, then it will only pause those Components,
and if not it will pause all the registered Components.
:param names: a list of Components to pause
:type names: list
:returns: a Deferred object that will fire once all Components have been sucessfully paused
:rtype: twisted.internet.defer.Deferred
"""
if not names:
names = self.components.keys()
elif isinstance(names, str):
names = [names]
deferreds = []
for name in names:
if self.components[name]._component_state == "Started":
deferreds.append(self.components[name]._component_pause())
return DeferredList(deferreds)
def resume(self, names=[]):
"""
Resumes Components that are currently in a Paused state. If
*names* is specified, then it will only resume those Components,
and if not it will resume all the registered Components.
:param names: a list of Components to resume
:type names: list
:returns: a Deferred object that will fire once all Components have been successfully resumed
:rtype: twisted.internet.defer.Deferred
"""
if not names:
names = self.components.keys()
elif isinstance(names, str):
names = [names]
deferreds = []
for name in names:
if self.components[name]._component_state == "Paused":
deferreds.append(self.components[name]._component_resume())
return DeferredList(deferreds)
def shutdown(self):
"""
Shutdowns all Components regardless of state. This will call
:meth:`stop` on call the components prior to shutting down. This should
be called when the program is exiting to ensure all Components have a
chance to properly shutdown.
:returns: a Deferred object that will fire once all Components have been successfully shut down
:rtype: twisted.internet.defer.Deferred
"""
def on_stopped(result):
return DeferredList(map(lambda c: c._component_shutdown(), self.components.values()))
return self.stop(self.components.keys()).addCallback(on_stopped)
def resume_component(self, component):
if self.components[component].get_state() == COMPONENT_STATE.index("Paused"):
log.debug("Resuming component %s..", component)
self.components[component]._resume()
def update(self):
"""
Updates all Components that are in a Started state.
"""Updates all components"""
for component in self.components.keys():
# Only update the component if it's started
if self.components[component].get_state() == \
COMPONENT_STATE.index("Started"):
self.components[component].update()
return True
def shutdown(self):
"""Shuts down all components. This should be called when the program
exits so that components can do any necessary clean-up."""
# Stop all components first
self.stop()
for component in self.components.keys():
log.debug("Shutting down component %s..", component)
try:
self.components[component].shutdown()
except Exception, e:
log.debug("Unable to call shutdown()")
log.exception(e)
"""
for component in self.components.items():
component.update()
_ComponentRegistry = ComponentRegistry()
deregister = _ComponentRegistry.deregister
start = _ComponentRegistry.start
stop = _ComponentRegistry.stop
pause = _ComponentRegistry.pause
resume = _ComponentRegistry.resume
update = _ComponentRegistry.update
shutdown = _ComponentRegistry.shutdown
def register(name, obj, depend=None):
"""Registers a component with the registry"""
_ComponentRegistry.register(name, obj, depend)
def get(name):
"""
Return a reference to a component.
def deregister(name):
"""Deregisters a component"""
_ComponentRegistry.deregister(name)
:param name: the Component name to get
:type name: string
def start(component=None):
"""Starts all components"""
if component == None:
_ComponentRegistry.start()
else:
_ComponentRegistry.start_component(component)
:returns: the Component object
:rtype: object
def stop(component=None):
"""Stops all or specified components"""
if component == None:
_ComponentRegistry.stop()
else:
_ComponentRegistry.stop_component(component)
:raises KeyError: if the Component does not exist
def pause(component=None):
"""Pauses all or specificed components"""
if component == None:
_ComponentRegistry.pause()
else:
_ComponentRegistry.pause_component(component)
"""
return _ComponentRegistry.components[name]
def resume(component=None):
"""Resumes all or specificed components"""
if component == None:
_ComponentRegistry.resume()
else:
_ComponentRegistry.resume_component(component)
def update():
"""Updates all components"""
_ComponentRegistry.update()
def shutdown():
"""Shutdowns all components"""
_ComponentRegistry.shutdown()
def get(component):
"""Return a reference to the component"""
return _ComponentRegistry.get(component)

View File

@ -45,9 +45,9 @@ The format of the config file is two json encoded dicts:
<version dict>
<content dict>
The version dict contains two keys: file and format. The format version is
controlled by the Config class. It should only be changed when anything below
it is changed directly by the Config class. An example of this would be if we
The version dict contains two keys: file and format. The format version is
controlled by the Config class. It should only be changed when anything below
it is changed directly by the Config class. An example of this would be if we
changed the serializer for the content to something different.
The config file version is changed by the 'owner' of the config file. This is
@ -68,16 +68,14 @@ version as this will be done internally.
"""
import cPickle as pickle
import logging
import shutil
import os
import deluge.common
from deluge.log import LOG as log
json = deluge.common.json
log = logging.getLogger(__name__)
def prop(func):
"""Function decorator for defining property attributes
@ -95,13 +93,13 @@ def prop(func):
def find_json_objects(s):
"""
Find json objects in a string.
:param s: the string to find json objects in
:type s: string
:returns: a list of tuples containing start and end locations of json objects in the string `s`
:rtype: [(start, end), ...]
"""
objects = []
opens = 0
@ -121,8 +119,8 @@ def find_json_objects(s):
start = index + offset + 1
return objects
class Config(object):
"""
This class is used to access/create/modify config files
@ -148,8 +146,7 @@ class Config(object):
self._save_timer = None
if defaults:
for key, value in defaults.iteritems():
self.set_item(key, value)
self.__config = dict(defaults)
# Load the config from file in the config_dir
if config_dir:
@ -159,9 +156,6 @@ class Config(object):
self.load()
def __contains__(self, item):
return item in self.__config
def __setitem__(self, key, value):
"""
See
@ -190,10 +184,6 @@ what is currently in the config and it could not convert the value
5
"""
if isinstance(value, basestring):
value = deluge.common.utf8_encoded(value)
if not self.__config.has_key(key):
self.__config[key] = value
log.debug("Setting '%s' to %s of %s", key, value, type(value))
@ -207,10 +197,7 @@ what is currently in the config and it could not convert the value
if value is not None and oldtype != type(None) and oldtype != newtype:
try:
if oldtype == unicode:
value = oldtype(value, "utf8")
else:
value = oldtype(value)
value = oldtype(value)
except ValueError:
log.warning("Type '%s' invalid for '%s'", newtype, key)
raise
@ -260,38 +247,7 @@ what is currently in the config and it could not convert the value
5
"""
if isinstance(self.__config[key], str):
try:
return self.__config[key].decode("utf8")
except UnicodeDecodeError:
return self.__config[key]
else:
return self.__config[key]
def __delitem__(self, key):
"""
See
:meth:`del_item`
"""
self.del_item(key)
def del_item(self, key):
"""
Deletes item with a specific key from the configuration.
:param key: the item which you wish to delete.
:raises KeyError: if 'key' is not in the config dictionary
**Usage**
>>> config = Config("test.conf", defaults={"test": 5})
>>> del config["test"]
"""
del self.__config[key]
# We set the save_timer for 5 seconds if not already set
from twisted.internet import reactor
if not self._save_timer or not self._save_timer.active():
self._save_timer = reactor.callLater(5, self.save)
return self.__config[key]
def register_change_callback(self, callback):
"""
@ -389,21 +345,21 @@ what is currently in the config and it could not convert the value
return
objects = find_json_objects(data)
if not len(objects):
# No json objects found, try depickling it
try:
self.__config.update(pickle.loads(data))
except Exception, e:
log.exception(e)
log.warning("Unable to load config file: %s", filename)
log.warning("Unable to load config file: %s", filename)
elif len(objects) == 1:
start, end = objects[0]
try:
self.__config.update(json.loads(data[start:end]))
except Exception, e:
log.exception(e)
log.warning("Unable to load config file: %s", filename)
log.warning("Unable to load config file: %s", filename)
elif len(objects) == 2:
try:
start, end = objects[0]
@ -412,8 +368,8 @@ what is currently in the config and it could not convert the value
self.__config.update(json.loads(data[start:end]))
except Exception, e:
log.exception(e)
log.warning("Unable to load config file: %s", filename)
log.warning("Unable to load config file: %s", filename)
log.debug("Config %s version: %s.%s loaded: %s", filename,
self.__version["format"], self.__version["file"], self.__config)
@ -437,24 +393,26 @@ what is currently in the config and it could not convert the value
version = json.loads(data[start:end])
start, end = objects[1]
loaded_data = json.loads(data[start:end])
if self.__config == loaded_data and self.__version == version:
# The config has not changed so lets just return
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return True
except (IOError, IndexError), e:
log.warning("Unable to open config file: %s because: %s", filename, e)
self._save_timer.cancel()
return
except Exception, e:
log.warning("Unable to open config file: %s", filename)
# Save the new config and make sure it's written to disk
try:
log.debug("Saving new config file %s", filename + ".new")
f = open(filename + ".new", "wb")
json.dump(self.__version, f, indent=2)
json.dump(self.__version, f, indent=2)
json.dump(self.__config, f, indent=2)
f.flush()
os.fsync(f.fileno())
f.close()
except IOError, e:
except Exception, e:
log.error("Error writing new config file: %s", e)
return False
@ -463,7 +421,7 @@ what is currently in the config and it could not convert the value
log.debug("Backing up old config file to %s~", filename)
shutil.move(filename, filename + "~")
except Exception, e:
log.warning("Unable to backup old config...")
log.error("Error backing up old config..")
# The new config file has been written successfully, so let's move it over
# the existing one.

View File

@ -34,14 +34,11 @@
#
import os
import logging
import deluge.common
import deluge.log
from deluge.log import LOG as log
from deluge.config import Config
log = logging.getLogger(__name__)
class _ConfigManager:
def __init__(self):
log.debug("ConfigManager started..")
@ -55,6 +52,7 @@ class _ConfigManager:
return self.__config_directory
def __del__(self):
log.debug("ConfigManager stopping..")
del self.config_files
def set_config_dir(self, directory):
@ -88,7 +86,6 @@ class _ConfigManager:
# to reload based on the new config directory
self.save()
self.config_files = {}
deluge.log.tweak_logging_levels()
return True

View File

@ -41,14 +41,12 @@ This should typically only be used by the Core. Plugins should utilize the
"""
import logging
from twisted.internet import reactor
import deluge.component as component
from deluge._libtorrent import lt
from deluge.common import decode_string
log = logging.getLogger(__name__)
from deluge.log import LOG as log
class AlertManager(component.Component):
def __init__(self):
@ -67,18 +65,9 @@ class AlertManager(component.Component):
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {}
self.delayed_calls = []
self.wait_on_handler = False
def update(self):
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
self.handle_alerts(wait=self.wait_on_handler)
def stop(self):
for dc in self.delayed_calls:
if dc.active():
dc.cancel()
self.delayed_calls = []
self.handle_alerts()
def register_handler(self, alert_type, handler):
"""
@ -118,17 +107,18 @@ class AlertManager(component.Component):
:param wait: bool, if True then the handler functions will be run right
away and waited to return before processing the next alert
"""
alerts = self.session.pop_alerts()
alert = self.session.pop_alert()
# Loop through all alerts in the queue
for alert in alerts:
while alert is not None:
alert_type = type(alert).__name__
# Display the alert message
if log.isEnabledFor(logging.DEBUG):
log.debug("%s: %s", alert_type, decode_string(alert.message()))
log.debug("%s: %s", alert_type, alert.message())
# Call any handlers for this alert type
if alert_type in self.handlers:
for handler in self.handlers[alert_type]:
if not wait:
self.delayed_calls.append(reactor.callLater(0, handler, alert))
reactor.callLater(0, handler, alert)
else:
handler(alert)
alert = self.session.pop_alert()

View File

@ -2,7 +2,6 @@
# authmanager.py
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -37,56 +36,27 @@
import os
import random
import stat
import shutil
import logging
import deluge.component as component
import deluge.configmanager as configmanager
from deluge.common import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE, AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY, AUTH_LEVEL_DEFAULT,
create_localclient_account)
import deluge.error
from deluge.error import AuthManagerError, AuthenticationRequired, BadLoginError
from deluge.log import LOG as log
log = logging.getLogger(__name__)
AUTH_LEVEL_NONE = 0
AUTH_LEVEL_READONLY = 1
AUTH_LEVEL_NORMAL = 5
AUTH_LEVEL_ADMIN = 10
AUTH_LEVELS_MAPPING = {
'NONE': AUTH_LEVEL_NONE,
'READONLY': AUTH_LEVEL_READONLY,
'DEFAULT': AUTH_LEVEL_NORMAL,
'NORMAL': AUTH_LEVEL_DEFAULT,
'ADMIN': AUTH_LEVEL_ADMIN
}
AUTH_LEVELS_MAPPING_REVERSE = {}
for key, value in AUTH_LEVELS_MAPPING.iteritems():
AUTH_LEVELS_MAPPING_REVERSE[value] = key
class Account(object):
__slots__ = ('username', 'password', 'authlevel')
def __init__(self, username, password, authlevel):
self.username = username
self.password = password
self.authlevel = authlevel
def data(self):
return {
'username': self.username,
'password': self.password,
'authlevel': AUTH_LEVELS_MAPPING_REVERSE[self.authlevel],
'authlevel_int': self.authlevel
}
def __repr__(self):
return ('<Account username="%(username)s" authlevel=%(authlevel)s>' %
self.__dict__)
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
class BadLoginError(deluge.error.DelugeError):
pass
class AuthManager(component.Component):
def __init__(self):
component.Component.__init__(self, "AuthManager", interval=10)
component.Component.__init__(self, "AuthManager")
self.__auth = {}
self.__auth_modification_time = None
def start(self):
self.__load_auth_file()
@ -97,19 +67,6 @@ class AuthManager(component.Component):
def shutdown(self):
pass
def update(self):
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
log.info("Authfile not found, recreating it.")
self.__load_auth_file()
return
auth_file_modification_time = os.stat(auth_file).st_mtime
if self.__auth_modification_time != auth_file_modification_time:
log.info("Auth file changed, reloading it!")
self.__load_auth_file()
def authorize(self, username, password):
"""
Authorizes users based on username and password
@ -119,127 +76,55 @@ class AuthManager(component.Component):
:returns: int, the auth level for this user
:rtype: int
:raises AuthenticationRequired: if aditional details are required to
authenticate.
:raises BadLoginError: if the username does not exist or password does
not match.
:raises BadLoginError: if the username does not exist or password does not match
"""
if not username:
raise AuthenticationRequired(
"Username and Password are required.", username
)
if username not in self.__auth:
# Let's try to re-load the file.. Maybe it's been updated
self.__load_auth_file()
if username not in self.__auth:
raise BadLoginError("Username does not exist", username)
raise BadLoginError("Username does not exist")
if self.__auth[username].password == password:
if self.__auth[username][0] == password:
# Return the users auth level
return self.__auth[username].authlevel
elif not password and self.__auth[username].password:
raise AuthenticationRequired("Password is required", username)
return int(self.__auth[username][1])
else:
raise BadLoginError("Password does not match", username)
raise BadLoginError("Password does not match")
def has_account(self, username):
return username in self.__auth
def get_known_accounts(self):
def __create_localclient_account(self):
"""
Returns a list of known deluge usernames.
Returns the string.
"""
self.__load_auth_file()
return [account.data() for account in self.__auth.values()]
def create_account(self, username, password, authlevel):
if username in self.__auth:
raise AuthManagerError("Username in use.", username)
# We create a 'localclient' account with a random password
try:
self.__auth[username] = Account(username, password,
AUTH_LEVELS_MAPPING[authlevel])
self.write_auth_file()
return True
except Exception, err:
log.exception(err)
raise err
def update_account(self, username, password, authlevel):
if username not in self.__auth:
raise AuthManagerError("Username not known", username)
try:
self.__auth[username].username = username
self.__auth[username].password = password
self.__auth[username].authlevel = AUTH_LEVELS_MAPPING[authlevel]
self.write_auth_file()
return True
except Exception, err:
log.exception(err)
raise err
def remove_account(self, username):
if username not in self.__auth:
raise AuthManagerError("Username not known", username)
elif username == component.get("RPCServer").get_session_user():
raise AuthManagerError(
"You cannot delete your own account while logged in!", username
)
del self.__auth[username]
self.write_auth_file()
return True
def write_auth_file(self):
old_auth_file = configmanager.get_config_dir("auth")
new_auth_file = old_auth_file + '.new'
bak_auth_file = old_auth_file + '.bak'
# Let's first create a backup
if os.path.exists(old_auth_file):
shutil.copy2(old_auth_file, bak_auth_file)
try:
fd = open(new_auth_file, "w")
for account in self.__auth.values():
fd.write(
"%(username)s:%(password)s:%(authlevel_int)s\n" %
account.data()
)
fd.flush()
os.fsync(fd.fileno())
fd.close()
os.rename(new_auth_file, old_auth_file)
except:
# Something failed, let's restore the previous file
if os.path.exists(bak_auth_file):
os.rename(bak_auth_file, old_auth_file)
self.__load_auth_file()
from hashlib import sha1 as sha_hash
except ImportError:
from sha import new as sha_hash
return "localclient:" + sha_hash(str(random.random())).hexdigest() + ":" + str(AUTH_LEVEL_ADMIN) + "\n"
def __load_auth_file(self):
save_and_reload = False
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
create_localclient_account()
return self.__load_auth_file()
auth_file_modification_time = os.stat(auth_file).st_mtime
if self.__auth_modification_time is None:
self.__auth_modification_time = auth_file_modification_time
elif self.__auth_modification_time == auth_file_modification_time:
# File didn't change, no need for re-parsing's
return
# Load the auth file into a dictionary: {username: Account(...)}
f = open(auth_file, "r").readlines()
localclient = self.__create_localclient_account()
fd = open(auth_file, "w")
fd.write(localclient)
fd.flush()
os.fsync(fd.fileno())
fd.close()
# Change the permissions on the file so only this user can read/write it
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
f = [localclient]
else:
# Load the auth file into a dictionary: {username: password, ...}
f = open(auth_file, "r").readlines()
for line in f:
line = line.strip()
if line.startswith("#") or not line:
# This line is a comment or empty
if line.startswith("#"):
# This is a comment line
continue
line = line.strip()
try:
lsplit = line.split(":")
except Exception, e:
@ -247,43 +132,15 @@ class AuthManager(component.Component):
continue
if len(lsplit) == 2:
username, password = lsplit
log.warning("Your auth entry for %s contains no auth level, "
"using AUTH_LEVEL_DEFAULT(%s)..", username,
AUTH_LEVEL_DEFAULT)
if username == 'localclient':
authlevel = AUTH_LEVEL_ADMIN
else:
authlevel = AUTH_LEVEL_DEFAULT
# This is probably an old auth file
save_and_reload = True
log.warning("Your auth entry for %s contains no auth level, using AUTH_LEVEL_DEFAULT(%s)..", username, AUTH_LEVEL_DEFAULT)
level = AUTH_LEVEL_DEFAULT
elif len(lsplit) == 3:
username, password, authlevel = lsplit
username, password, level = lsplit
else:
log.error("Your auth file is malformed: "
"Incorrect number of fields!")
log.error("Your auth file is malformed: Incorrect number of fields!")
continue
username = username.strip()
password = password.strip()
try:
authlevel = int(authlevel)
except ValueError:
try:
authlevel = AUTH_LEVELS_MAPPING[authlevel]
except KeyError:
log.error("Your auth file is malformed: %r is not a valid auth "
"level" % authlevel)
continue
self.__auth[username] = Account(username, password, authlevel)
self.__auth[username.strip()] = (password.strip(), level)
if "localclient" not in self.__auth:
create_localclient_account(True)
return self.__load_auth_file()
if save_and_reload:
log.info("Re-writing auth file (upgrade)")
self.write_auth_file()
self.__auth_modification_time = auth_file_modification_time
open(auth_file, "a").write(self.__create_localclient_account())

131
deluge/core/autoadd.py Normal file
View File

@ -0,0 +1,131 @@
#
# autoadd.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
import os
from deluge._libtorrent import lt
import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.log import LOG as log
MAX_NUM_ATTEMPTS = 10
class AutoAdd(component.Component):
def __init__(self):
component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5)
# Get the core config
self.config = ConfigManager("core.conf")
# A list of filenames
self.invalid_torrents = []
# Filename:Attempts
self.attempts = {}
# Register set functions
self.config.register_set_function("autoadd_enable",
self._on_autoadd_enable, apply_now=True)
self.config.register_set_function("autoadd_location",
self._on_autoadd_location)
def update(self):
if not self.config["autoadd_enable"]:
# We shouldn't be updating because autoadd is not enabled
component.pause("AutoAdd")
return
# Check the auto add folder for new torrents to add
if not os.path.isdir(self.config["autoadd_location"]):
log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"])
component.pause("AutoAdd")
return
for filename in os.listdir(self.config["autoadd_location"]):
if filename.split(".")[-1] == "torrent":
filepath = os.path.join(self.config["autoadd_location"], filename)
try:
filedump = self.load_torrent(filepath)
except (RuntimeError, Exception), e:
# If the torrent is invalid, we keep track of it so that we
# can try again on the next pass. This is because some
# torrents may not be fully saved during the pass.
log.debug("Torrent is invalid: %s", e)
if filename in self.invalid_torrents:
self.attempts[filename] += 1
if self.attempts[filename] >= MAX_NUM_ATTEMPTS:
os.rename(filepath, filepath + ".invalid")
del self.attempts[filename]
self.invalid_torrents.remove(filename)
else:
self.invalid_torrents.append(filename)
self.attempts[filename] = 1
continue
# The torrent looks good, so lets add it to the session
component.get("TorrentManager").add(filedump=filedump, filename=filename)
os.remove(filepath)
def load_torrent(self, filename):
try:
log.debug("Attempting to open %s for add.", filename)
_file = open(filename, "rb")
filedump = _file.read()
if not filedump:
raise RuntimeError, "Torrent is 0 bytes!"
_file.close()
except IOError, e:
log.warning("Unable to open %s: %s", filename, e)
raise e
# Get the info to see if any exceptions are raised
info = lt.torrent_info(lt.bdecode(filedump))
return filedump
def _on_autoadd_enable(self, key, value):
log.debug("_on_autoadd_enable")
if value:
component.resume("AutoAdd")
else:
component.pause("AutoAdd")
def _on_autoadd_location(self, key, value):
log.debug("_on_autoadd_location")
# We need to resume the component just incase it was paused due to
# an invalid autoadd location.
if self.config["autoadd_enable"]:
component.resume("AutoAdd")

View File

@ -2,7 +2,6 @@
# core.py
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -18,9 +17,9 @@
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
@ -39,34 +38,36 @@ from deluge._libtorrent import lt
import os
import glob
import base64
import logging
import shutil
import threading
import tempfile
from urlparse import urljoin
import pkg_resources
import warnings
from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall
import twisted.web.client
import twisted.web.error
from deluge.httpdownloader import download_file
from deluge.log import LOG as log
import deluge.configmanager
import deluge.common
import deluge.component as component
from deluge.event import *
from deluge.error import *
from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE
from deluge.core.authmanager import AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE
from deluge.core.torrentmanager import TorrentManager
from deluge.core.pluginmanager import PluginManager
from deluge.core.alertmanager import AlertManager
from deluge.core.filtermanager import FilterManager
from deluge.core.preferencesmanager import PreferencesManager
from deluge.core.autoadd import AutoAdd
from deluge.core.authmanager import AuthManager
from deluge.core.eventmanager import EventManager
from deluge.core.rpcserver import export
log = logging.getLogger(__name__)
class Core(component.Component):
def __init__(self, listen_interface=None):
log.debug("Core init..")
@ -76,41 +77,27 @@ class Core(component.Component):
log.info("Starting libtorrent %s session..", lt.version)
# Create the client fingerprint
version = deluge.common.VersionSplit(deluge.common.get_version()).version
version = [int(value.split("-")[0]) for value in deluge.common.get_version().split(".")]
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)
self.session = lt.session(lt.fingerprint("DE", *version), flags=0)
# Load the session state if available
self.__load_session_state()
# Set the user agent
self.settings = lt.session_settings()
self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \
{ 'deluge_version': deluge.common.get_version(),
'lt_version': self.get_libtorrent_version().rpartition(".")[0] }
# Increase the alert queue size so that alerts don't get lost
self.settings.alert_queue_size = 10000
self.settings.user_agent = "Deluge %s" % deluge.common.get_version()
# Set session settings
self.settings.send_redundant_have = True
if deluge.common.windows_check():
self.settings.disk_io_write_mode = \
lt.io_buffer_mode_t.disable_os_cache
self.settings.disk_io_read_mode = \
lt.io_buffer_mode_t.disable_os_cache
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)
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)
# Create the components
self.eventmanager = EventManager()
@ -119,6 +106,7 @@ class Core(component.Component):
self.pluginmanager = PluginManager(self)
self.torrentmanager = TorrentManager()
self.filtermanager = FilterManager(self)
self.autoadd = AutoAdd()
self.authmanager = AuthManager()
# New release check information
@ -126,7 +114,6 @@ class Core(component.Component):
# Get the core config
self.config = deluge.configmanager.ConfigManager("core.conf")
self.config.save()
# If there was an interface value from the command line, use it, but
# store the one in the config so we can restore it on shutdown
@ -141,12 +128,9 @@ class Core(component.Component):
self.__new_release = None
def stop(self):
log.debug("Core stopping...")
# Save the DHT state if necessary
if self.config["dht"]:
self.save_dht_state()
# Save the libtorrent session state
self.__save_session_state()
@ -163,16 +147,16 @@ class Core(component.Component):
def __save_session_state(self):
"""Saves the libtorrent session state"""
try:
session_state = deluge.configmanager.get_config_dir("session.state")
open(session_state, "wb").write(lt.bencode(self.session.save_state()))
open(deluge.configmanager.get_config_dir("session.state"), "wb").write(
lt.bencode(self.session.state()))
except Exception, e:
log.warning("Failed to save lt state: %s", e)
def __load_session_state(self):
"""Loads the libtorrent session state"""
try:
session_state = deluge.configmanager.get_config_dir("session.state")
self.session.load_state(lt.bdecode(open(session_state, "rb").read()))
self.session.load_state(lt.bdecode(
open(deluge.configmanager.get_config_dir("session.state"), "rb").read()))
except Exception, e:
log.warning("Failed to load lt state: %s", e)
@ -228,9 +212,7 @@ class Core(component.Component):
log.exception(e)
try:
torrent_id = self.torrentmanager.add(
filedump=filedump, options=options, filename=filename
)
torrent_id = self.torrentmanager.add(filedump=filedump, options=options, filename=filename)
except Exception, e:
log.error("There was an error adding the torrent file %s", filename)
log.exception(e)
@ -254,44 +236,20 @@ class Core(component.Component):
:returns: a Deferred which returns the torrent_id as a str or None
"""
log.info("Attempting to add url %s", url)
def on_download_success(filename):
def on_get_file(filename):
# We got the file, so add it to the session
f = open(filename, "rb")
data = f.read()
f.close()
try:
os.remove(filename)
except Exception, e:
log.warning("Couldn't remove temp file: %s", e)
return self.add_torrent_file(
filename, base64.encodestring(data), options
)
data = open(filename, "rb").read()
return self.add_torrent_file(filename, base64.encodestring(data), options)
def on_download_fail(failure):
if failure.check(twisted.web.error.PageRedirect):
new_url = urljoin(url, failure.getErrorMessage().split(" to ")[1])
result = download_file(
new_url, tempfile.mkstemp()[1], headers=headers,
force_filename=True
)
result.addCallbacks(on_download_success, on_download_fail)
elif failure.check(twisted.web.client.PartialDownloadError):
result = download_file(
url, tempfile.mkstemp()[1], headers=headers,
force_filename=True, allow_compression=False
)
result.addCallbacks(on_download_success, on_download_fail)
else:
# Log the error and pass the failure onto the client
log.error("Error occured downloading torrent from %s", url)
log.error("Reason: %s", failure.getErrorMessage())
result = failure
return result
def on_get_file_error(failure):
# Log the error and pass the failure onto the client
log.error("Error occured downloading torrent from %s", url)
log.error("Reason: %s", failure.getErrorMessage())
return failure
d = download_file(
url, tempfile.mkstemp()[1], headers=headers, force_filename=True
)
d.addCallbacks(on_download_success, on_download_fail)
d = download_file(url, url.split("/")[-1], headers=headers)
d.addCallback(on_get_file)
d.addErrback(on_get_file_error)
return d
@export
@ -330,6 +288,28 @@ class Core(component.Component):
log.debug("Removing torrent %s from the core.", torrent_id)
return self.torrentmanager.remove(torrent_id, remove_data)
@export
def get_stats(self):
"""
Deprecated: please use get_session_status()
"""
warnings.warn("Use get_session_status() instead of get_stats()", DeprecationWarning)
stats = self.get_session_status(["payload_download_rate", "payload_upload_rate",
"dht_nodes", "has_incoming_connections", "download_rate", "upload_rate"])
stats.update({
#dynamic stats:
"num_connections":self.session.num_connections(),
"free_space":deluge.common.free_space(self.config["download_location"]),
#max config values:
"max_download":self.config["max_download_speed"],
"max_upload":self.config["max_upload_speed"],
"max_num_connections":self.config["max_connections_global"],
})
return stats
@export
def get_session_status(self, keys):
"""
@ -426,43 +406,30 @@ class Core(component.Component):
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].resume()
def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False):
try:
status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update)
except KeyError:
import traceback
traceback.print_exc()
# Torrent was probaly removed meanwhile
return {}
@export
def get_torrent_status(self, torrent_id, keys):
# Build the status dictionary
status = self.torrentmanager[torrent_id].get_status(keys)
# Ask the plugin manager to fill in the plugin keys
if len(plugin_keys) > 0:
status.update(self.pluginmanager.get_status(torrent_id, plugin_keys))
# Get the leftover fields and ask the plugin manager to fill them
leftover_fields = list(set(keys) - set(status.keys()))
if len(leftover_fields) > 0:
status.update(self.pluginmanager.get_status(torrent_id, leftover_fields))
return status
@export
def get_torrent_status(self, torrent_id, keys, diff=False):
torrent_keys, plugin_keys = self.torrentmanager.separate_keys(keys, [torrent_id])
return self.create_torrent_status(torrent_id, torrent_keys, plugin_keys, diff=diff, update=True)
@export
def get_torrents_status(self, filter_dict, keys, diff=False):
def get_torrents_status(self, filter_dict, keys):
"""
returns all torrents , optionally filtered by filter_dict.
"""
torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict)
status_dict = {}.fromkeys(torrent_ids)
d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=diff)
def add_plugin_fields(args):
status_dict, plugin_keys = args
# Ask the plugin manager to fill in the plugin keys
if len(plugin_keys) > 0:
for key in status_dict.keys():
status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys))
return status_dict
d.addCallback(add_plugin_fields)
return d
# Get the torrent status for each torrent_id
for torrent_id in torrent_ids:
status_dict[torrent_id] = self.get_torrent_status(torrent_id, keys)
return status_dict
@export
def get_filter_tree(self , show_zero_hits=True, hide_cat=None):
@ -509,7 +476,7 @@ class Core(component.Component):
"""Set the config with values from dictionary"""
# Load all the values into the configuration
for key in config.keys():
if isinstance(config[key], basestring):
if isinstance(config[key], unicode) or isinstance(config[key], str):
config[key] = config[key].encode("utf8")
self.config[key] = config[key]
@ -523,6 +490,24 @@ class Core(component.Component):
"""Returns the current number of connections"""
return self.session.num_connections()
@export
def get_dht_nodes(self):
"""Returns the number of dht nodes"""
warnings.warn("Use get_session_status().", DeprecationWarning)
return self.session.status().dht_nodes
@export
def get_download_rate(self):
"""Returns the payload download rate"""
warnings.warn("Use get_session_status().", DeprecationWarning)
return self.session.status().payload_download_rate
@export
def get_upload_rate(self):
"""Returns the payload upload rate"""
warnings.warn("Use get_session_status().", DeprecationWarning)
return self.session.status().payload_upload_rate
@export
def get_available_plugins(self):
"""Returns a list of plugins available in the core"""
@ -590,11 +575,6 @@ class Core(component.Component):
"""Sets a higher priority to the first and last pieces"""
return self.torrentmanager[torrent_id].set_prioritize_first_last(value)
@export
def set_torrent_sequential_download(self, torrent_id, value):
"""Toggle sequencial pieces download"""
return self.torrentmanager[torrent_id].set_sequential_download(value)
@export
def set_torrent_auto_managed(self, torrent_id, value):
"""Sets the auto managed flag for queueing purposes"""
@ -625,31 +605,11 @@ class Core(component.Component):
"""Sets the path for the torrent to be moved when completed"""
return self.torrentmanager[torrent_id].set_move_completed_path(value)
@export(AUTH_LEVEL_ADMIN)
def set_torrents_owner(self, torrent_ids, username):
"""Set's the torrent owner.
:param torrent_id: the torrent_id of the torrent to remove
:type torrent_id: string
:param username: the new owner username
:type username: string
:raises DelugeError: if the username is not known
"""
if not self.authmanager.has_account(username):
raise DelugeError("Username \"%s\" is not known." % username)
if isinstance(torrent_ids, basestring):
torrent_ids = [torrent_ids]
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].set_owner(username)
return None
@export
def set_torrents_shared(self, torrent_ids, shared):
if isinstance(torrent_ids, basestring):
torrent_ids = [torrent_ids]
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].set_options({"shared": shared})
def get_health(self):
"""Returns True if we have established incoming connections"""
warnings.warn("Use get_session_status().", DeprecationWarning)
return self.session.status().has_incoming_connections
@export
def get_path_size(self, path):
@ -662,7 +622,7 @@ class Core(component.Component):
webseeds, private, created_by, trackers, add_to_session):
log.debug("creating torrent..")
threading.Thread(target=self._create_torrent_thread,
threading.Thread(target=_create_torrent_thread,
args=(
path,
tracker,
@ -765,8 +725,7 @@ class Core(component.Component):
@export
def queue_top(self, torrent_ids):
log.debug("Attempting to queue %s to top", torrent_ids)
# torrent_ids must be sorted in reverse before moving to preserve order
for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True):
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_top(torrent_id):
@ -777,48 +736,35 @@ class Core(component.Component):
@export
def queue_up(self, torrent_ids):
log.debug("Attempting to queue %s to up", torrent_ids)
torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
torrent_moved = True
prev_queue_position = None
#torrent_ids must be sorted before moving.
for queue_position, torrent_id in sorted(torrents):
# Move the torrent if and only if there is space (by not moving it we preserve the order)
if torrent_moved or queue_position - prev_queue_position > 1:
try:
torrent_moved = self.torrentmanager.queue_up(torrent_id)
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
# If the torrent moved, then we should emit a signal
if torrent_moved:
component.get("EventManager").emit(TorrentQueueChangedEvent())
else:
prev_queue_position = queue_position
torrent_ids = list(torrent_ids)
torrent_ids.sort(key = lambda id: self.torrentmanager.torrents[id].get_queue_position())
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_up(torrent_id):
component.get("EventManager").emit(TorrentQueueChangedEvent())
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export
def queue_down(self, torrent_ids):
log.debug("Attempting to queue %s to down", torrent_ids)
torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
torrent_moved = True
prev_queue_position = None
#torrent_ids must be sorted before moving.
for queue_position, torrent_id in sorted(torrents, reverse=True):
# Move the torrent if and only if there is space (by not moving it we preserve the order)
if torrent_moved or prev_queue_position - queue_position > 1:
try:
torrent_moved = self.torrentmanager.queue_down(torrent_id)
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
# If the torrent moved, then we should emit a signal
if torrent_moved:
component.get("EventManager").emit(TorrentQueueChangedEvent())
else:
prev_queue_position = queue_position
torrent_ids = list(torrent_ids)
torrent_ids.sort(key = lambda id: -self.torrentmanager.torrents[id].get_queue_position())
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_down(torrent_id):
component.get("EventManager").emit(TorrentQueueChangedEvent())
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export
def queue_bottom(self, torrent_ids):
log.debug("Attempting to queue %s to bottom", torrent_ids)
# torrent_ids must be sorted before moving to preserve order
for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position):
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_bottom(torrent_id):
@ -841,27 +787,21 @@ class Core(component.Component):
"""
from twisted.web.client import getPage
d = getPage("http://deluge-torrent.org/test_port.php?port=%s" %
self.get_listen_port(), timeout=30)
d = getPage("http://deluge-torrent.org/test_port.php?port=%s" % self.get_listen_port())
def on_get_page(result):
return bool(int(result))
def logError(failure):
log.warning("Error testing listen port: %s", failure)
d.addCallback(on_get_page)
d.addErrback(logError)
return d
@export
def get_free_space(self, path=None):
def get_free_space(self, path):
"""
Returns the number of free bytes at path
:param path: the path to check free space at, if None, use the default
download location
:param path: the path to check free space at
:type path: string
:returns: the number of free bytes at path
@ -870,12 +810,7 @@ class Core(component.Component):
:raises InvalidPathError: if the path is invalid
"""
if not path:
path = self.config["download_location"]
try:
return deluge.common.free_space(path)
except InvalidPathError:
return 0
return deluge.common.free_space(path)
@export
def get_libtorrent_version(self):
@ -887,23 +822,3 @@ class Core(component.Component):
"""
return lt.version
@export(AUTH_LEVEL_ADMIN)
def get_known_accounts(self):
return self.authmanager.get_known_accounts()
@export(AUTH_LEVEL_NONE)
def get_auth_levels_mappings(self):
return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE)
@export(AUTH_LEVEL_ADMIN)
def create_account(self, username, password, authlevel):
return self.authmanager.create_account(username, password, authlevel)
@export(AUTH_LEVEL_ADMIN)
def update_account(self, username, password, authlevel):
return self.authmanager.update_account(username, password, authlevel)
@export(AUTH_LEVEL_ADMIN)
def remove_account(self, username):
return self.authmanager.remove_account(username)

View File

@ -33,7 +33,9 @@
#
import os
import logging
import gettext
import locale
import pkg_resources
from twisted.internet import reactor
import twisted.internet.error
@ -41,19 +43,16 @@ import deluge.component as component
import deluge.configmanager
import deluge.common
from deluge.core.rpcserver import RPCServer, export
from deluge.log import LOG as log
import deluge.error
log = logging.getLogger(__name__)
class Daemon(object):
def __init__(self, options=None, args=None, classic=False):
# Check for another running instance of the daemon
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(";")
(pid, port) = open(deluge.configmanager.get_config_dir("deluged.pid")).read().strip().split(";")
pid = int(pid)
port = int(port)
except ValueError:
@ -63,8 +62,13 @@ class Daemon(object):
def process_running(pid):
if deluge.common.windows_check():
import win32process
return pid in win32process.EnumProcesses()
# Do some fancy WMI junk to see if the PID exists in Windows
from win32com.client import GetObject
def get_proclist():
WMI = GetObject('winmgmts:')
processes = WMI.InstancesOf('Win32_Process')
return [process.Properties_('ProcessID').Value for process in processes]
return pid in get_proclist()
else:
# We can just use os.kill on UNIX to test if the process is running
try:
@ -87,14 +91,26 @@ class Daemon(object):
else:
# This is a deluged!
s.close()
raise deluge.error.DaemonRunningError(
"There is a deluge daemon running with this config "
"directory!"
)
raise deluge.error.DaemonRunningError("There is a deluge daemon running with this config directory!")
# Initialize gettext
try:
locale.setlocale(locale.LC_ALL, '')
if hasattr(locale, "bindtextdomain"):
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
if hasattr(locale, "textdomain"):
locale.textdomain("deluge")
gettext.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
gettext.textdomain("deluge")
gettext.install("deluge", pkg_resources.resource_filename("deluge", "i18n"))
except Exception, e:
log.error("Unable to initialize gettext/locale: %s", e)
import __builtin__
__builtin__.__dict__["_"] = lambda x: x
# Twisted catches signals to terminate, so just have it call the shutdown
# method.
reactor.addSystemEventTrigger("before", "shutdown", self._shutdown)
reactor.addSystemEventTrigger("after", "shutdown", self.shutdown)
# Catch some Windows specific signals
if deluge.common.windows_check():
@ -104,7 +120,7 @@ class Daemon(object):
def win_handler(ctrl_type):
log.debug("ctrl_type: %s", ctrl_type)
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
self._shutdown()
self.__shutdown()
return 1
SetConsoleCtrlHandler(win_handler)
@ -117,14 +133,9 @@ class Daemon(object):
if options and options.config:
deluge.configmanager.set_config_dir(options.config)
if options and options.listen_interface:
listen_interface = options.listen_interface
else:
listen_interface = ""
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()
port = self.core.config["daemon_port"]
if options and options.port:
@ -166,16 +177,26 @@ class Daemon(object):
reactor.callLater(0, reactor.stop)
def _shutdown(self, *args, **kwargs):
if os.path.exists(deluge.configmanager.get_config_dir("deluged.pid")):
try:
os.remove(deluge.configmanager.get_config_dir("deluged.pid"))
except Exception, e:
log.exception(e)
log.error("Error removing deluged.pid!")
try:
os.remove(deluge.configmanager.get_config_dir("deluged.pid"))
except Exception, e:
log.exception(e)
log.error("Error removing deluged.pid!")
log.info("Waiting for components to shutdown..")
d = component.shutdown()
return d
component.shutdown()
try:
reactor.stop()
except twisted.internet.error.ReactorNotRunning:
log.debug("Tried to stop the reactor but it is not running..")
@export()
def info(self):
"""
Returns some info from the daemon.
:returns: str, the version number
"""
return deluge.common.get_version()
@export()
def get_method_list(self):
@ -183,18 +204,3 @@ class Daemon(object):
Returns a list of the exported methods.
"""
return self.rpcserver.get_method_list()
@export(1)
def authorized_call(self, rpc):
"""
Returns True if authorized to call rpc.
:param rpc: a rpc, eg, "core.get_torrents_status"
:type rpc: string
"""
if not rpc in self.get_method_list():
return False
auth_level = self.rpcserver.get_session_auth_level()
return auth_level >= self.rpcserver.get_rpc_auth_level()

View File

@ -33,10 +33,8 @@
#
#
import logging
import deluge.component as component
log = logging.getLogger(__name__)
from deluge.log import LOG as log
class EventManager(component.Component):
def __init__(self):
@ -55,10 +53,7 @@ class EventManager(component.Component):
if event.name in self.handlers:
for handler in self.handlers[event.name]:
#log.debug("Running handler %s for event %s with args: %s", event.name, handler, event.args)
try:
handler(*event.args)
except Exception, e:
log.error("Event handler %s failed in %s with exception %s", event.name, handler, e)
handler(*event.args)
def register_event_handler(self, event, handler):
"""

View File

@ -33,13 +33,12 @@
#
#
import logging
import deluge.component as component
from deluge.log import LOG as log
STATE_SORT = ["All", "Downloading", "Seeding", "Active", "Paused", "Queued"]
log = logging.getLogger(__name__)
#special purpose filters:
def filter_keywords(torrent_ids, values):
#cleanup.
@ -78,27 +77,6 @@ def filter_one_keyword(torrent_ids, keyword):
yield torrent_id
break
def filter_by_name(torrent_ids, search_string):
all_torrents = component.get("TorrentManager").torrents
try:
search_string, match_case = search_string[0].split('::match')
except ValueError:
search_string = search_string[0]
match_case = False
if match_case is False:
search_string = search_string.lower()
for torrent_id in torrent_ids:
torrent_name = all_torrents[torrent_id].get_name()
if match_case is False:
torrent_name = all_torrents[torrent_id].get_name().lower()
else:
torrent_name = all_torrents[torrent_id].get_name()
if search_string in torrent_name:
yield torrent_id
def tracker_error_filter(torrent_ids, values):
filtered_torrent_ids = []
tm = component.get("TorrentManager")
@ -113,8 +91,9 @@ 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_host"])["tracker_host"]:
if "Error:" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
class FilterManager(component.Component):
@ -128,7 +107,6 @@ class FilterManager(component.Component):
self.torrents = core.torrentmanager
self.registered_filters = {}
self.register_filter("keyword", filter_keywords)
self.register_filter("name", filter_by_name)
self.tree_fields = {}
self.register_tree_field("state", self._init_state_tree)
@ -138,10 +116,6 @@ class FilterManager(component.Component):
self.register_filter("tracker_host", tracker_error_filter)
def _init_users_tree():
return {"": 0}
self.register_tree_field("owner", _init_users_tree)
def filter_torrent_ids(self, filter_dict):
"""
returns a list of torrent_id's matching filter_dict.
@ -152,11 +126,12 @@ class FilterManager(component.Component):
#sanitize input: filter-value must be a list of strings
for key, value in filter_dict.items():
if isinstance(value, basestring):
filter_dict[key] = [value]
if isinstance(value, str):
filter_dict[key] = [value]
if "id"in filter_dict: #optimized filter for id:
torrent_ids = list(filter_dict["id"])
torrent_ids = filter_dict["id"]
del filter_dict["id"]
else:
torrent_ids = self.torrents.get_torrent_list()
@ -191,11 +166,13 @@ class FilterManager(component.Component):
#leftover filter arguments:
#default filter on status fields.
status_func = self.core.get_torrent_status #premature optimalisation..
for torrent_id in list(torrent_ids):
status = self.torrents[torrent_id].get_status(filter_dict.keys()) #status={key:value}
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:
torrent_ids.remove(torrent_id)
return torrent_ids
def get_filter_tree(self, show_zero_hits=True, hide_cat=None):
@ -204,22 +181,22 @@ class FilterManager(component.Component):
for use in sidebar.
"""
torrent_ids = self.torrents.get_torrent_list()
status_func = self.core.get_torrent_status #premature optimalisation..
tree_keys = list(self.tree_fields.keys())
if hide_cat:
for cat in hide_cat:
tree_keys.remove(cat)
torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids)
items = dict((field, self.tree_fields[field]()) for field in tree_keys)
items = dict( (field, self.tree_fields[field]()) for field in tree_keys)
#count status fields.
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys) #status={key:value}
status = status_func(torrent_id, tree_keys) #status={key:value}
for field in tree_keys:
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
if "tracker_host" in items:
items["tracker_host"]["All"] = len(torrent_ids)
items["tracker_host"]["Error"] = len(tracker_error_filter(torrent_ids, ("Error",)))
if "state" in tree_keys and not show_zero_hits:
@ -260,8 +237,9 @@ class FilterManager(component.Component):
del self.tree_fields[field]
def filter_state_active(self, torrent_ids):
get_status = self.core.get_torrent_status
for torrent_id in list(torrent_ids):
status = self.torrents[torrent_id].get_status(["download_payload_rate", "upload_payload_rate"])
status = get_status(torrent_id, ["download_payload_rate", "upload_payload_rate"])
if status["download_payload_rate"] or status["upload_payload_rate"]:
pass #ok
else:

View File

@ -39,14 +39,12 @@ import os.path
import pickle
import cPickle
import shutil
import logging
from deluge._libtorrent import lt
from deluge.configmanager import ConfigManager, get_config_dir
import deluge.core.torrentmanager
log = logging.getLogger(__name__)
from deluge.log import LOG as log
#start : http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
def makeFakeClass(module, name):

View File

@ -36,13 +36,13 @@
"""PluginManager for Core"""
import logging
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from deluge.event import PluginEnabledEvent, PluginDisabledEvent
import deluge.pluginmanagerbase
import deluge.component as component
log = logging.getLogger(__name__)
from deluge.log import LOG as log
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
component.Component):
@ -96,7 +96,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
try:
status[field] = self.status_fields[field](torrent_id)
except KeyError:
pass
log.warning("Status field %s is not registered with the\
PluginManager.", field)
return status
def register_status_field(self, field, function):

View File

@ -1,7 +1,7 @@
#
# preferencesmanager.py
#
# Copyright (C) 2008-2010 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -34,9 +34,10 @@
#
import os
import logging
import os.path
import threading
import pkg_resources
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from deluge._libtorrent import lt
@ -45,8 +46,7 @@ from deluge.event import *
import deluge.configmanager
import deluge.common
import deluge.component as component
log = logging.getLogger(__name__)
from deluge.log import LOG as log
DEFAULT_PREFS = {
"send_info": False,
@ -58,11 +58,9 @@ DEFAULT_PREFS = {
"listen_ports": [6881, 6891],
"listen_interface": "",
"copy_torrent_file": False,
"del_copy_torrent_file": False,
"torrentfiles_location": deluge.common.get_default_download_dir(),
"plugins_location": os.path.join(deluge.configmanager.get_config_dir(), "plugins"),
"prioritize_first_last_pieces": False,
"sequential_download": False,
"random_port": True,
"dht": True,
"upnp": True,
@ -86,6 +84,8 @@ DEFAULT_PREFS = {
"max_upload_speed_per_torrent": -1,
"max_download_speed_per_torrent": -1,
"enabled_plugins": [],
"autoadd_location": deluge.common.get_default_download_dir(),
"autoadd_enable": False,
"add_paused": False,
"max_active_seeding": 5,
"max_active_downloading": 3,
@ -139,9 +139,7 @@ DEFAULT_PREFS = {
"rate_limit_ip_overhead": True,
"geoip_db_location": "/usr/share/GeoIP/GeoIP.dat",
"cache_size": 512,
"cache_expiry": 60,
"auto_manage_prefer_seeds": False,
"shared": False
"cache_expiry": 60
}
class PreferencesManager(component.Component):
@ -149,40 +147,92 @@ class PreferencesManager(component.Component):
component.Component.__init__(self, "PreferencesManager")
self.config = deluge.configmanager.ConfigManager("core.conf", DEFAULT_PREFS)
if 'public' in self.config:
log.debug("Updating configuration file: Renamed torrent's public "
"attribute to shared.")
self.config["shared"] = self.config["public"]
del self.config["public"]
def start(self):
self.core = component.get("Core")
self.session = component.get("Core").session
self.new_release_timer = None
self.settings = component.get("Core").settings
# Set the initial preferences on start-up
for key in DEFAULT_PREFS:
self.do_config_set_func(key, self.config[key])
# Register set functions in the Config
self.config.register_set_function("torrentfiles_location",
self._on_set_torrentfiles_location)
self.config.register_set_function("listen_ports",
self._on_set_listen_ports)
self.config.register_set_function("listen_interface",
self._on_set_listen_interface)
self.config.register_set_function("random_port",
self._on_set_random_port)
self.config.register_set_function("outgoing_ports",
self._on_set_outgoing_ports)
self.config.register_set_function("random_outgoing_ports",
self._on_set_random_outgoing_ports)
self.config.register_set_function("peer_tos",
self._on_set_peer_tos)
self.config.register_set_function("dht", self._on_set_dht)
self.config.register_set_function("upnp", self._on_set_upnp)
self.config.register_set_function("natpmp", self._on_set_natpmp)
self.config.register_set_function("utpex", self._on_set_utpex)
self.config.register_set_function("lsd", self._on_set_lsd)
self.config.register_set_function("enc_in_policy",
self._on_set_encryption)
self.config.register_set_function("enc_out_policy",
self._on_set_encryption)
self.config.register_set_function("enc_level",
self._on_set_encryption)
self.config.register_set_function("enc_prefer_rc4",
self._on_set_encryption)
self.config.register_set_function("max_connections_global",
self._on_set_max_connections_global)
self.config.register_set_function("max_upload_speed",
self._on_set_max_upload_speed)
self.config.register_set_function("max_download_speed",
self._on_set_max_download_speed)
self.config.register_set_function("max_upload_slots_global",
self._on_set_max_upload_slots_global)
self.config.register_set_function("max_half_open_connections",
self._on_set_max_half_open_connections)
self.config.register_set_function("max_connections_per_second",
self._on_set_max_connections_per_second)
self.config.register_set_function("ignore_limits_on_local_network",
self._on_ignore_limits_on_local_network)
self.config.register_set_function("share_ratio_limit",
self._on_set_share_ratio_limit)
self.config.register_set_function("seed_time_ratio_limit",
self._on_set_seed_time_ratio_limit)
self.config.register_set_function("seed_time_limit",
self._on_set_seed_time_limit)
self.config.register_set_function("max_active_downloading",
self._on_set_max_active_downloading)
self.config.register_set_function("max_active_seeding",
self._on_set_max_active_seeding)
self.config.register_set_function("max_active_limit",
self._on_set_max_active_limit)
self.config.register_set_function("dont_count_slow_torrents",
self._on_set_dont_count_slow_torrents)
self.config.register_set_function("send_info",
self._on_send_info)
self.config.register_set_function("proxies",
self._on_set_proxies)
self.new_release_timer = None
self.config.register_set_function("new_release_check",
self._on_new_release_check)
self.config.register_set_function("rate_limit_ip_overhead",
self._on_rate_limit_ip_overhead)
self.config.register_set_function("geoip_db_location",
self._on_geoip_db_location)
self.config.register_set_function("cache_size",
self._on_cache_size)
self.config.register_set_function("cache_expiry",
self._on_cache_expiry)
self.config.register_change_callback(self._on_config_value_change)
def stop(self):
if self.new_release_timer and self.new_release_timer.running:
if self.new_release_timer:
self.new_release_timer.stop()
# Config set functions
def do_config_set_func(self, key, value):
on_set_func = getattr(self, "_on_set_" + key, None)
if on_set_func:
on_set_func(key, value)
def session_set_setting(self, key, value):
settings = self.session.settings()
setattr(settings, key, value)
self.session.set_settings(settings)
def _on_config_value_change(self, key, value):
self.do_config_set_func(key, value)
component.get("EventManager").emit(ConfigValueChangedEvent(key, value))
def _on_set_torrentfiles_location(self, key, value):
@ -196,9 +246,7 @@ class PreferencesManager(component.Component):
# Only set the listen ports if random_port is not true
if self.config["random_port"] is not True:
log.debug("listen port range set to %s-%s", value[0], value[1])
self.session.listen_on(
value[0], value[1], str(self.config["listen_interface"])
)
self.session.listen_on(value[0], value[1], str(self.config["listen_interface"]))
def _on_set_listen_interface(self, key, value):
# Call the random_port callback since it'll do what we need
@ -220,15 +268,13 @@ class PreferencesManager(component.Component):
# Set the listen ports
log.debug("listen port range set to %s-%s", listen_ports[0],
listen_ports[1])
self.session.listen_on(
listen_ports[0], listen_ports[1],
str(self.config["listen_interface"])
)
self.session.listen_on(listen_ports[0], listen_ports[1], str(self.config["listen_interface"]))
def _on_set_outgoing_ports(self, key, value):
if not self.config["random_outgoing_ports"]:
log.debug("outgoing port range set to %s-%s", value[0], value[1])
self.session_set_setting("outgoing_ports", (value[0], value[1]))
self.settings.outgoing_ports = value[0], value[1]
self.session.set_settings(self.settings)
def _on_set_random_outgoing_ports(self, key, value):
if value:
@ -237,11 +283,13 @@ class PreferencesManager(component.Component):
def _on_set_peer_tos(self, key, value):
log.debug("setting peer_tos to: %s", value)
try:
self.session_set_setting("peer_tos", chr(int(value, 16)))
self.settings.peer_tos = chr(int(value, 16))
except ValueError, e:
log.debug("Invalid tos byte: %s", e)
return
self.session.set_settings(self.settings)
def _on_set_dht(self, key, value):
log.debug("dht value set to %s", value)
state_file = deluge.configmanager.get_config_dir("dht.state")
@ -288,22 +336,7 @@ 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
def _on_set_enc_in_policy(self, key, value):
self._on_set_encryption(key, value)
def _on_set_enc_out_policy(self, key, value):
self._on_set_encryption(key, value)
def _on_set_enc_level(self, key, value):
self._on_set_encryption(key, value)
def _on_set_enc_prefer_rc4(self, key, value):
self._on_set_encryption(key, value)
self.session.add_extension(lt.create_ut_pex_plugin)
def _on_set_encryption(self, key, value):
log.debug("encryption value %s set to %s..", key, value)
@ -353,41 +386,53 @@ class PreferencesManager(component.Component):
self.session.set_max_half_open_connections(value)
def _on_set_max_connections_per_second(self, key, value):
self.session_set_setting("connection_speed", value)
self.settings.connection_speed = value
self.session.set_settings(self.settings)
def _on_set_ignore_limits_on_local_network(self, key, value):
self.session_set_setting("ignore_limits_on_local_network", value)
def _on_ignore_limits_on_local_network(self, key, value):
self.settings.ignore_limits_on_local_network = value
self.session.set_settings(self.settings)
def _on_set_share_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("share_ratio_limit", value)
self.settings.share_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("seed_time_ratio_limit", value)
self.settings.seed_time_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_limit(self, key, value):
log.debug("%s set to %s..", key, value)
# This value is stored in minutes in deluge, but libtorrent wants seconds
self.session_set_setting("seed_time_limit", int(value * 60))
self.settings.seed_time_limit = int(value * 60)
self.session.set_settings(self.settings)
def _on_set_max_active_downloading(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("active_downloads", value)
log.debug("active_downloads: %s", self.settings.active_downloads)
self.settings.active_downloads = value
self.session.set_settings(self.settings)
def _on_set_max_active_seeding(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("active_seeds", value)
log.debug("active_seeds: %s", self.settings.active_seeds)
self.settings.active_seeds = value
self.session.set_settings(self.settings)
def _on_set_max_active_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("active_limit", value)
log.debug("active_limit: %s", self.settings.active_limit)
self.settings.active_limit = value
self.session.set_settings(self.settings)
def _on_set_dont_count_slow_torrents(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("dont_count_slow_torrents", value)
self.settings.dont_count_slow_torrents = value
self.session.set_settings(self.settings)
def _on_set_send_info(self, key, value):
def _on_send_info(self, key, value):
log.debug("Sending anonymous stats..")
"""sends anonymous stats home"""
class Send_Info_Thread(threading.Thread):
@ -417,18 +462,18 @@ class PreferencesManager(component.Component):
if value:
Send_Info_Thread(self.config).start()
def _on_set_new_release_check(self, key, value):
def _on_new_release_check(self, key, value):
if value:
log.debug("Checking for new release..")
threading.Thread(target=self.core.get_new_release).start()
if self.new_release_timer and self.new_release_timer.running:
if self.new_release_timer:
self.new_release_timer.stop()
# Set a timer to check for a new release every 3 days
self.new_release_timer = LoopingCall(
self._on_set_new_release_check, "new_release_check", True)
self._on_new_release_check, "new_release_check", True)
self.new_release_timer.start(72 * 60 * 60, False)
else:
if self.new_release_timer and self.new_release_timer.running:
if self.new_release_timer:
self.new_release_timer.stop()
def _on_set_proxies(self, key, value):
@ -443,20 +488,19 @@ class PreferencesManager(component.Component):
log.debug("setting %s proxy settings", k)
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
def _on_set_rate_limit_ip_overhead(self, key, value):
def _on_rate_limit_ip_overhead(self, key, value):
log.debug("%s: %s", key, value)
self.session_set_setting("rate_limit_ip_overhead", value)
self.settings.rate_limit_ip_overhead = value
self.session.set_settings(self.settings)
def _on_set_geoip_db_location(self, key, value):
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(deluge.common.resource_filename("deluge", os.path.join("data", "GeoIP.dat"))):
geoip_db = deluge.common.resource_filename(
"deluge", os.path.join("data", "GeoIP.dat")
)
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"))
else:
log.warning("Unable to find GeoIP database file!")
@ -467,14 +511,12 @@ class PreferencesManager(component.Component):
log.error("Unable to load geoip database!")
log.exception(e)
def _on_set_cache_size(self, key, value):
def _on_cache_size(self, key, value):
log.debug("%s: %s", key, value)
self.session_set_setting("cache_size", value)
self.settings.cache_size = value
self.session.set_settings(self.settings)
def _on_set_cache_expiry(self, key, value):
def _on_cache_expiry(self, key, value):
log.debug("%s: %s", key, value)
self.session_set_setting("cache_expiry", value)
def _on_auto_manage_prefer_seeds(self, key, value):
log.debug("%s set to %s..", key, value)
self.session_set_setting("auto_manage_prefer_seeds", value)
self.settings.cache_expiry = value
self.session.set_settings(self.settings)

View File

@ -39,35 +39,25 @@ import sys
import zlib
import os
import stat
import logging
import traceback
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor, defer
from twisted.internet import ssl, reactor, defer
from OpenSSL import crypto, SSL
from types import FunctionType
try:
import rencode
except ImportError:
import deluge.rencode as rencode
import deluge.rencode as rencode
from deluge.log import LOG as log
import deluge.component as component
import deluge.configmanager
from deluge.core.authmanager import (AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_ADMIN)
from deluge.error import (DelugeError, NotAuthorizedError, WrappedException,
_ClientSideRecreateError, IncompatibleClient)
from deluge.transfer import DelugeTransferProtocol
from deluge.core.authmanager import AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT
RPC_RESPONSE = 1
RPC_ERROR = 2
RPC_EVENT = 3
log = logging.getLogger(__name__)
def export(auth_level=AUTH_LEVEL_DEFAULT):
"""
Decorator function to register an object's method as an RPC. The object
@ -96,31 +86,11 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
else:
return wrap
class DelugeError(Exception):
pass
def format_request(call):
"""
Format the RPCRequest message for debug printing
:param call: the request
:type call: a RPCRequest
:returns: a formatted string for printing
:rtype: str
"""
try:
s = call[1] + "("
if call[2]:
s += ", ".join([str(x) for x in call[2]])
if call[3]:
if call[2]:
s += ", "
s += ", ".join([key + "=" + str(value) for key, value in call[3].items()])
s += ")"
except UnicodeEncodeError:
return "UnicodeEncodeError, call: %s" % call
else:
return s
class NotAuthorizedError(DelugeError):
pass
class ServerContextFactory(object):
def getContext(self):
@ -136,34 +106,70 @@ class ServerContextFactory(object):
ctx.use_privatekey_file(os.path.join(ssl_dir, "daemon.pkey"))
return ctx
class DelugeRPCProtocol(DelugeTransferProtocol):
class DelugeRPCProtocol(Protocol):
__buffer = None
def message_received(self, request):
def dataReceived(self, data):
"""
This method is called whenever a message is received from a client. The
This method is called whenever data is received from a client. The
only message that a client sends to the server is a RPC Request message.
If the RPC Request message is valid, then the method is called in
:meth:`dispatch`.
:param request: the request from the client.
:type data: tuple
If the RPC Request message is valid, then the method is called in a thread
with :meth:`dispatch`.
:param data: the data from the client. It should be a zlib compressed
rencoded string.
:type data: str
"""
if type(request) is not tuple:
log.debug("Received invalid message: type is not tuple")
return
if self.__buffer:
# We have some data from the last dataReceived() so lets prepend it
data = self.__buffer + data
self.__buffer = None
if len(request) < 1:
log.debug("Received invalid message: there are no items")
return
while data:
dobj = zlib.decompressobj()
try:
request = rencode.loads(dobj.decompress(data))
except Exception, e:
#log.debug("Received possible invalid message (%r): %s", data, e)
# This could be cut-off data, so we'll save this in the buffer
# and try to prepend it on the next dataReceived()
self.__buffer = data
return
else:
data = dobj.unused_data
for call in request:
if len(call) != 4:
log.debug("Received invalid rpc request: number of items "
"in request is %s", len(call))
continue
#log.debug("RPCRequest: %s", format_request(call))
reactor.callLater(0, self.dispatch, *call)
if type(request) is not tuple:
log.debug("Received invalid message: type is not tuple")
return
if len(request) < 1:
log.debug("Received invalid message: there are no items")
return
for call in request:
if len(call) != 4:
log.debug("Received invalid rpc request: number of items in request is %s", len(call))
continue
# Format the RPCRequest message for debug printing
try:
s = call[1] + "("
if call[2]:
s += ", ".join([str(x) for x in call[2]])
if call[3]:
if call[2]:
s += ", "
s += ", ".join([key + "=" + str(value) for key, value in call[3].items()])
s += ")"
except UnicodeEncodeError:
pass
#log.debug("RPCRequest had some non-ascii text..")
else:
pass
#log.debug("RPCRequest: %s", s)
reactor.callLater(0, self.dispatch, *call)
def sendData(self, data):
"""
@ -171,18 +177,16 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
:param data: the object that is to be sent to the client. This should
be one of the RPC message types.
:type data: object
"""
self.transfer_message(data)
self.transport.write(zlib.compress(rencode.dumps(data)))
def connectionMade(self):
"""
This method is called when a new client connects.
"""
peer = self.transport.getPeer()
log.info("Deluge Client connection made from: %s:%s",
peer.host, peer.port)
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
@ -204,9 +208,6 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
log.info("Deluge client disconnected: %s", reason.value)
def valid_session(self):
return self.transport.sessionno in self.factory.authorized_sessions
def dispatch(self, request_id, method, args, kwargs):
"""
This method is run when a RPC Request is made. It will run the local method
@ -228,55 +229,33 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
Sends an error response with the contents of the exception that was raised.
"""
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
formated_tb = "".join(traceback.format_tb(exceptionTraceback))
try:
self.sendData((
RPC_ERROR,
request_id,
exceptionType.__name__,
exceptionValue._args,
exceptionValue._kwargs,
formated_tb
))
except Exception, err:
# This most likely not a deluge exception, let's wrap it
log.error("An exception occurred while sending RPC_ERROR to "
"client. Wrapping it and resending. Error to "
"send(causing exception goes next):\n%s", formated_tb)
log.exception(err)
try:
raise WrappedException(str(exceptionValue), exceptionType.__name__, formated_tb)
except:
sendError()
if method == "daemon.info":
# This is a special case and used in the initial connection process
self.sendData((RPC_RESPONSE, request_id, deluge.common.get_version()))
return
elif method == "daemon.login":
self.sendData((
RPC_ERROR,
request_id,
(exceptionType.__name__,
exceptionValue.args[0] if len(exceptionValue.args) == 1 else "",
"".join(traceback.format_tb(exceptionTraceback)))
))
if method == "daemon.login":
# This is a special case and used in the initial connection process
# We need to authenticate the user here
log.debug("RPC dispatch daemon.login")
try:
client_version = kwargs.pop('client_version', None)
if client_version is None:
raise IncompatibleClient(deluge.common.get_version())
ret = component.get("AuthManager").authorize(*args, **kwargs)
if ret:
self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0])
self.factory.authorized_sessions[self.transport.sessionno] = ret
self.factory.session_protocols[self.transport.sessionno] = self
except Exception, e:
sendError()
if not isinstance(e, _ClientSideRecreateError):
log.exception(e)
log.exception(e)
else:
self.sendData((RPC_RESPONSE, request_id, (ret)))
if not ret:
self.transport.loseConnection()
finally:
return
elif method == "daemon.set_event_interest" and self.valid_session():
log.debug("RPC dispatch daemon.set_event_interest")
elif method == "daemon.set_event_interest" and self.transport.sessionno in self.factory.authorized_sessions:
# This special case is to allow clients to set which events they are
# interested in receiving.
# We are expecting a sequence from the client.
@ -291,24 +270,18 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
finally:
return
if method in self.factory.methods and self.valid_session():
log.debug("RPC dispatch %s", method)
if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions:
try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]
auth_level = self.factory.authorized_sessions[self.transport.sessionno]
if auth_level < method_auth_requirement:
# This session is not allowed to call this method
log.debug("Session %s is trying to call a method it is not "
"authorized to call!", self.transport.sessionno)
raise NotAuthorizedError(auth_level, method_auth_requirement)
# Set the session_id in the factory so that methods can know
# which session is calling it.
self.factory.session_id = self.transport.sessionno
log.debug("Session %s is trying to call a method it is not authorized to call!", self.transport.sessionno)
raise NotAuthorizedError("Auth level too low: %s < %s" % (auth_level, method_auth_requirement))
ret = self.factory.methods[method](*args, **kwargs)
except Exception, e:
sendError()
# Don't bother printing out DelugeErrors, because they are just
# for the client
# Don't bother printing out DelugeErrors, because they are just for the client
if not isinstance(e, DelugeError):
log.exception("Exception calling RPC request: %s", e)
else:
@ -351,8 +324,6 @@ class RPCServer(component.Component):
self.factory = Factory()
self.factory.protocol = DelugeRPCProtocol
self.factory.session_id = -1
# Holds the registered methods
self.factory.methods = {}
# Holds the session_ids and auth levels
@ -362,7 +333,6 @@ class RPCServer(component.Component):
# Holds the interested event list for the sessions
self.factory.interested_events = {}
self.listen = listen
if not listen:
return
@ -406,17 +376,6 @@ class RPCServer(component.Component):
log.debug("Registering method: %s", name + "." + d)
self.factory.methods[name + "." + d] = getattr(obj, d)
def deregister_object(self, obj):
"""
Deregisters an objects exported rpc methods.
:param obj: the object that was previously registered
"""
for key, value in self.factory.methods.items():
if value.im_self == obj:
del self.factory.methods[key]
def get_object_method(self, name):
"""
Returns a registered method.
@ -440,66 +399,6 @@ class RPCServer(component.Component):
"""
return self.factory.methods.keys()
def get_session_id(self):
"""
Returns the session id of the current RPC.
:returns: the session id, this will be -1 if no connections have been made
:rtype: int
"""
return self.factory.session_id
def get_session_user(self):
"""
Returns the username calling the current RPC.
:returns: the username of the user calling the current RPC
:rtype: string
"""
if not self.listen:
return "localclient"
session_id = self.get_session_id()
if session_id > -1 and session_id in self.factory.authorized_sessions:
return self.factory.authorized_sessions[session_id][1]
else:
# No connections made yet
return ""
def get_session_auth_level(self):
"""
Returns the auth level of the user calling the current RPC.
:returns: the auth level
:rtype: int
"""
if not self.listen or not self.is_session_valid(self.get_session_id()):
return AUTH_LEVEL_ADMIN
return self.factory.authorized_sessions[self.get_session_id()][0]
def get_rpc_auth_level(self, rpc):
"""
Returns the auth level requirement for an exported rpc.
:returns: the auth level
:rtype: int
"""
self.factory.methods[rpc]._rpcserver_auth_level
def is_session_valid(self, session_id):
"""
Checks if the session is still valid, eg, if the client is still connected.
:param session_id: the session id
:type session_id: int
:returns: True if the session is valid
:rtype: bool
"""
return session_id in self.factory.authorized_sessions
def emit_event(self, event):
"""
Emits the event to interested clients.
@ -509,7 +408,7 @@ class RPCServer(component.Component):
"""
log.debug("intevents: %s", self.factory.interested_events)
# Find sessions interested in this event
for session_id, interest in self.factory.interested_events.items():
for session_id, interest in self.factory.interested_events.iteritems():
if event.name in interest:
log.debug("Emit Event: %s %s", event.name, event.args)
# This session is interested so send a RPC_EVENT
@ -517,29 +416,6 @@ class RPCServer(component.Component):
(RPC_EVENT, event.name, event.args)
)
def emit_event_for_session_id(self, session_id, event):
"""
Emits the event to specified session_id.
:param session_id: the event to emit
:type session_id: int
:param event: the event to emit
:type event: :class:`deluge.event.DelugeEvent`
"""
if not self.is_session_valid(session_id):
log.debug("Session ID %s is not valid. Not sending event \"%s\".", session_id, event.name)
return
if session_id not in self.factory.interested_events:
log.debug("Session ID %s is not interested in any events. Not sending event \"%s\".", session_id, event.name)
return
if event.name not in self.factory.interested_events[session_id]:
log.debug("Session ID %s is not interested in event \"%s\". Not sending it.", session_id, event.name)
return
log.debug("Sending event \"%s\" with args \"%s\" to session id \"%s\".",
event.name, event.args, session_id)
self.factory.session_protocols[session_id].sendData((RPC_EVENT, event.name, event.args))
def check_ssl_keys():
"""
Check for SSL cert/key and create them if necessary
@ -583,12 +459,8 @@ 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)
)
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))
# 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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 722 B

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 742 B

After

Width:  |  Height:  |  Size: 742 B

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 590 B

After

Width:  |  Height:  |  Size: 590 B

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 722 B

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 793 B

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 686 B

View File

@ -0,0 +1,2 @@
From: http://famfamfam.com/lab/icons/flags/
"These flag icons are available for free use for any purpose with no requirement for attribution."

View File

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 643 B

View File

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

View File

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 604 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 643 B

View File

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 600 B

View File

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 497 B

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

View File

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 506 B

View File

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 647 B

View File

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

View File

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

View File

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 524 B

View File

Before

Width:  |  Height:  |  Size: 663 B

After

Width:  |  Height:  |  Size: 663 B

View File

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View File

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 593 B

View File

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 585 B

View File

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 504 B

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 497 B

View File

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 462 B

View File

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 457 B

View File

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

View File

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 486 B

View File

Before

Width:  |  Height:  |  Size: 611 B

After

Width:  |  Height:  |  Size: 611 B

View File

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 639 B

View File

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 500 B

View File

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 593 B

View File

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 526 B

View File

Before

Width:  |  Height:  |  Size: 631 B

After

Width:  |  Height:  |  Size: 631 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 443 B

View File

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

View File

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 600 B

View File

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 628 B

View File

Before

Width:  |  Height:  |  Size: 625 B

After

Width:  |  Height:  |  Size: 625 B

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

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