Compare commits

...

699 Commits

Author SHA1 Message Date
6892834d4d web: minor IE compatibility change 2011-11-25 23:47:08 +00:00
609f54a704 css: move icons out to their own stylesheet 2011-11-24 17:45:42 +00:00
fa08f7de43 details: add the missing items to the details tab
The web interface was missing the Owner and Shared items
that have been added to the gtk ui. Add these to bring the web
interface up to scratch.
2011-11-24 17:20:44 +00:00
a91037843b details: complete the status tab
Fix the remaining data items so they display correctly and tweak
the spacing to make the view a little more pleasant on the eye
2011-11-24 17:11:08 +00:00
4a43d6a635 data: fix type for progress column 2011-11-24 17:09:10 +00:00
1837d833c2 web: few sidebar fixes 2011-11-21 23:05:02 +00:00
e3e20aa14f web: fix using wrong Resource class
The Tracker class was still using the Resource class from Twisted instead
of our own subclass that adds some fun stuff :-)
2011-11-21 22:59:58 +00:00
c865486f82 web: add more jokey headers 2011-11-04 01:06:56 +00:00
c06d4dfea5 web: add a fun header
Add a jokey powered by header for people who like to
look at that sort of thing :-)
2011-11-04 00:50:11 +00:00
c405425993 web: fix details tab taking a while to update
Somewhere the handler for the torrent grid selection change
got removed during the upgrade to ext-4, add it back so the
details tab updating becomes instant once more.
2011-11-04 00:44:56 +00:00
d10752fc1a web: add a better log method
Add a new method, deluge.log which outputs to the javascript
console, including a date and time which will help when debugging
speed issues
2011-11-04 00:44:17 +00:00
ab665384d4 web: implement a better status tab
This new StatusTab uses a custom component, StatusItem and a
bunch of the Ext layout stuff in order to render the info on
the StatusTab, instead of an arbitary html modification. We
gain dynamic layout and more generic way of modifying the tab
2011-11-02 23:42:53 +00:00
f5e6eabee4 web: fix the details status tab
Start porting over the status tab to the new extjs4 format
2011-10-31 20:40:14 +00:00
717e66b836 web: big update for ext4.0.7 compatibility
Lots of changes here:

 * Switched to using Ext.create over new Object()
 * Add torrent window is now half working
 * Begin the makings of DnD add torrent file
2011-10-29 04:35:33 +01:00
fea0b41425 web: update extjs to 4.0.7 2011-10-29 01:28:58 +01:00
d37c3e0213 web: fix country flags
The flags have been moved into the ui folder so
the web server was looking in the wrong place
2011-10-21 21:49:34 +01:00
b724f74700 web: drop JSLoader extension
Ext now includes an Ext.Loader class that can load scripts so this
extra extension to do so is surplurfluous.
2011-10-05 23:31:41 +01:00
d44a357284 web: fix the sidebar and filterpanels
The filters in the sidebar can now be switch between finally. It
was down to the setTitle method overwriting whatever the accordion
layout was adding in.
2011-10-05 02:31:51 +01:00
d6715fcbb9 web: fix the filterpanel column width
Set flex = 1 on the column so it auto expands to take up the
entire available width.
2011-10-05 02:21:06 +01:00
2853d028fd web: more progressbar fixes
Fix displaying the progress bars within the peers tab, also
reduce the modifier so that the bars don't overflow their
containers.
2011-10-05 02:05:58 +01:00
4ab5c6d9de web: fix selecting the active tab
Fix selecting the active tab in the details panel, it seems that
the activeTab property wasn't working so explicitly call
setActiveTab after adding in the various panels.
2011-10-05 01:54:33 +01:00
f53dc5faaf web: random fixes
Just a bunch of random fixes to improve the situation whilst
trying to convert over to extjs4.
2011-10-05 01:41:49 +01:00
c46bc049d1 web: add files resource to the web server
Add a files resource that allows Ext to fetch the file tree via
one of it's ajax proxies.
2011-10-05 01:40:40 +01:00
39d19b5afd web: ensure torrent keys are always str
Make sure that we aren't sending unicode strings as keys, always
map them with str.
2011-10-05 01:40:40 +01:00
e8506b925f web: fix the progressbar renderer
Modify our progress bar renderer so it uses the new ext style
and actually creates a progress bar now :-)
2011-10-05 01:40:40 +01:00
eb9071fcb0 web: add a secure decorator to the auth module
This new decorator will make it easy to secure the render method
of twisted resources as we will be adding a fair few as more of
the interface moves to use ajax requests over json-rpc.
2011-10-05 01:38:37 +01:00
9362ec0103 ui: add a new file tree geared towards ext
Add a new file tree that is more suited to created a tree that
an ext proxy will be able to load and convert into a data store.
This file tree also has an improved file tree walk method that
uses generators instead of callbacks.
2011-10-05 01:38:37 +01:00
fe0332bccf web: allow the peers grid to be stateful
Give the peers grid an id so it can store it's state and be
guaranteed to be able to restore it upon a refresh.
2011-10-05 01:38:37 +01:00
64bacbfbf4 web: support using localStorage for state
This adds support for checking to see if the browser supports
the HTML5 localStorage, if so use that instead of cookies for
storing the UI state.
2011-10-05 01:38:37 +01:00
280377ad6f web: convert the peers tab to use an ext proxy
Instead of fetching the peer information via a rpc call simply
expose it as a fetching page via ajax so we can make use of
the Ext proxy and reader instead of manually loading it into
the store ourselves.
2011-10-05 01:38:37 +01:00
0c3d2322cc web: add loading mask
Add a loading mask that hides the loading of the interface with
some text and a nice ajax spinner. The situation can be further
improved by loading all of the scripts and other resources
dynamically, which will be added later.
2011-10-05 01:36:58 +01:00
07b6db0c98 web: update to extjs 4.0.2a 2011-10-05 01:36:58 +01:00
713953ec03 web: fix the toolbar actions
Use the proper method for getting the torrent ids instead of the
implementation within the toolbar. Using the one on the torrent
grid will be more likely to work.
2011-10-05 01:36:58 +01:00
6324737031 web: simple formatting change
Nicely space out a few dictionaries so they are easier on the
eye when reading the code.
2011-10-05 01:36:58 +01:00
428681aca3 webui: use the getId method in the details panel
Before we were using the id property which seems to have been
deprecated and wasn't returning the actual id. Switching to
use the getId() method returns the correct id (the torrent hash)
so grabbing the torrent details starts working again for the
details tabs.
2011-10-05 01:36:57 +01:00
317e9ee423 webui: fix the details tab
The path to DomHelper isn't working, so fix that, and also update
the copyright notices.
2011-10-05 01:36:57 +01:00
08e774bbda webui: fix displaying the torrent context menu
The event name and arguments has changed, as well as the response
from getPoint, nothing major.
2011-10-05 01:36:57 +01:00
43cb787b44 webui: fix closing the connection manager
Don't override the onHide method in the ConnectionManager, this
was stopping the window from being able to close.
2011-10-05 01:36:57 +01:00
fb8f1e7ebc web: fix the FilterPanel to a degree
This finishes converting the FilterPanel to use the new data stuff
from ExtJS4 as well as switching from a listview to a gridview.
Currently the Sidebar is still broken.
2011-10-05 01:36:57 +01:00
881bcee160 web: fix starting/stopping daemons
Just a simple change from listview to gridview here.
2011-10-05 01:36:57 +01:00
42b8af25aa milestone number 1, web interface loads now 2011-10-05 01:36:57 +01:00
bf4b826809 webui: fix the login window
The login window now functions correctly under ExtJS 4.0.
2011-10-05 01:36:57 +01:00
8ae14de208 webui: fix up the connection manager
Partially fix the connection manager so it now displays correctly
on first load. A lot of the functionality needs to be changed
due to the list -> grid migration.
2011-10-05 01:36:57 +01:00
5b45670a85 webui: first commit where the interface loads
At this point the interface loads, but it is misshapen and needs a lot more
work to get it fully extjs 4.0 compatible.
2011-10-05 01:36:57 +01:00
6cdf9940d3 add extjs javascript files too 2011-10-05 01:31:14 +01:00
8b69d66bae tidy up some deferred stuff 2011-10-05 01:31:13 +01:00
3ee434975c upgrade to extjs 4.0.2 2011-10-05 01:31:13 +01:00
dda4620d98 remove and update the ext-extensions 2011-10-05 01:31:13 +01:00
7ac0083239 remove more extensions that have been moved into core 2011-10-05 01:31:13 +01:00
6ae58248a1 remove the treegrid extensions as they are now included with extjs 2011-10-05 01:31:12 +01:00
1c78bcbb29 upgrade to extjs4 2011-10-05 01:31:12 +01:00
7227c97cac Fix typo in Windows shutdown handler 2011-08-08 10:42:29 -07:00
4fcfb677a4 Allow changing ownership even though the owner column is not visible. 2011-08-03 11:59:42 +01:00
808ff02130 Fix path for desktop file 2011-07-29 20:56:48 -07:00
08a0a2de99 Fix i18n sub-dir issue in gitignore 2011-07-28 22:56:22 +01:00
fd56ccaabf Fix .desktop file creation on Windows by just ignoring it 2011-07-28 22:53:40 +01:00
cebddf9c79 Add my name to author list 2011-07-22 19:29:32 +01:00
e9b602d85f Update windows setting 2011-07-19 16:50:56 -07:00
5b2d37954c Add intltool to dependencies 2011-07-13 23:27:00 +01:00
fcc13f454b Fix torrent file and folder renaming issues
Adds `sanitize_filepath` for use before passing to libtorrent rename_file
2011-07-13 22:44:13 +01:00
15ef668fef Localize the Desktop file
Conflicts:

	setup.py
2011-07-13 21:49:25 +01:00
bf145c0715 Option tab spin buttons connected to key press events 2011-07-11 17:23:42 +01:00
192f3d88e5 set spinbuttons numeric only 2011-07-11 16:27:48 +01:00
d9cf3a8c08 Remove un-needed signal handlers. 2011-07-11 15:15:45 +01:00
a41b1357b5 Update translations files 2011-07-11 14:27:08 +01:00
c3c21dae72 More missing(?) signal handlers. Refs #1891. 2011-07-10 23:24:10 +01:00
4daa7e2470 Fix the options tab. Refs #1891 2011-07-10 22:51:10 +01:00
b301051cdd Fix #1637: UnicodeDecodeError from 'deluge-console --help' with other languages 2011-07-10 16:45:02 +01:00
456f660878 Add some more lost signal on the Glade to GtkBuild migration. 2011-07-10 02:58:46 +01:00
f7ce07c68f Tidy up location of gettext setup_translations 2011-07-10 02:10:14 +01:00
9eb85cb6eb Fix lost signals for peers tab menu, files tab menu and options tab. 2011-07-10 02:05:20 +01:00
40fd945f70 Fix translation string in Freespace plugin 2011-07-10 01:58:08 +01:00
78944f47f3 Allow compiling translations in develop mode.
Translations will compiled into `deluge/i18n` instead of the build lib so that they can be used in a deluge develop mode install.
2011-07-10 01:20:13 +01:00
acb747bfd5 Log from where translations are being loaded. 2011-07-10 00:43:56 +01:00
0c1055511d Raw attempt of fixing the GtkBuilder introduced bugs. 2011-07-10 00:43:01 +01:00
f0c327a024 Fix #1505: Add libtorrent info to --version output 2011-07-09 23:19:57 +01:00
b81159f295 Fix #1801: ConsoleUI failed connect missing error message 2011-07-09 22:11:13 +01:00
ca86aa5714 Fixes keyerror with existing file priorities set to High 2011-07-08 23:34:02 +01:00
fc7fa94319 Add handler for drag_data_received to supress warning 2011-07-08 23:23:03 +01:00
c6ee8cf39d Do not use Ellipsis. 2011-07-08 10:03:04 +01:00
bd7bbc4e33 Add some "now needed" missing imports. 2011-07-08 04:07:04 +01:00
312a57aa50 Remove some left overs. 2011-07-08 00:39:48 +01:00
f87ed6d5a6 Moved the MainWindow to GtkBuilder.
This probably broke some behaviour because converting and splitting from libglade to GtkBuilder is not as perfect as it should be. What I noticed was fixed.
Also, GtkBuilder only allow calling `connect_signals()` once. Some code had to change to handle this and a "handlers proxy class" was created to keep the behaviour we had, ie, connect signals from where it was needed. Then I monkey patch the main windows GtkBuilder to not allow anyone to connect signals through it since it would break behaviour. Connecting signals to the main window builder instance is now done like `component.get("MainWindow").connect_signals()`. The best solution will probably break the main window ui into the needed parts in order to not have to monkey patch main windows builder.
Plugin's trying to get the main windows `main_glade` are now broken, on purpose, ie, the code they have needs to change since the calls to the builder are not the same as the calls to libglade. The plugins we ship with deluge will be fix as soon as possible.
2011-07-07 20:48:02 +01:00
4234311050 Start with a bigger width. 2011-07-07 20:48:02 +01:00
a47da57c0d Make sure the tracker url is of type string and not unicode. Never had this issue previously though. 2011-07-07 20:48:02 +01:00
13528fe7f8 Fix httpdownloader Tests 2011-07-07 01:22:51 +01:00
99358dcbb0 Fix httpdownloader error with existing filename 2011-07-07 00:11:00 +01:00
16cc8f6eea LP Bug #496265: Peers in PeersTab show non-zero download rate when seeding 2011-07-07 00:11:00 +01:00
a384cd70b3 Moved system tray to GtkBuilder. 2011-07-06 22:20:15 +01:00
0e00aa479b Moved queued torrents dialog to GtkBuilder. 2011-07-06 22:14:20 +01:00
807bc095b4 Moved remove torrent dialog to GtkBuilder. 2011-07-06 21:10:24 +01:00
5a81ab3c35 Moved the preferences dialog to GtkBuilder. 2011-07-06 20:56:58 +01:00
bad228645c The Stats plugin should not be using the old logging system. 2011-07-06 20:56:08 +01:00
e016b2106f Fix some broken GtkBuilder files. 2011-07-06 20:03:32 +01:00
f63f247ac5 Forgot to rename a glade from when migrating it to GtkBuilder. 2011-07-06 19:31:47 +01:00
0228af6b50 Add some missing .ui files. 2011-07-06 19:30:01 +01:00
90fb40b741 Moved the menubar to GtkBuilder. 2011-07-06 19:29:27 +01:00
367631c9aa Migrated filtertree_menu.glade to GtkBuilder. 2011-07-06 19:17:44 +01:00
b36d62be9b Fix #1263: GTK UI not remembering column width
Removing a column from the treeview on shutdown causes all the
column widths to be zero which are saved to the state file.

The workaround is to not save the state file if all columns are zero.
2011-07-06 19:16:31 +01:00
b4cc1d4358 Splited dgtkpopups.glade into other_dialog.ui and connect_peer_dialog.ui which are now using GtkBuilder. 2011-07-06 19:09:25 +01:00
39ad5a3596 Moved edit trackers dialog to GtkBuilder. Min PyGTK version is now 2.16. 2011-07-06 18:54:40 +01:00
dbad4684db Create torrent remotely progress information.
Support progress information when creating torrents remotely. For this to be possible, a method was added to the `RPCServer`, `emit_event_for_session_id()`, which does exactly what is says. This is needed because the event created, `CreateTorrentProgressEvent` needs to be addressed to a single session id, not all session ids interested in that event.
Fixed a bug that apparently was not found yet. When creating torrents locally, we defer that task to a thread. Since this thread updates UI widgets, namely the progress bar info and since we can't guarantee that it's the main thread, updating the widgets must be done by calling `gobject.iddle_add()`.
2011-07-06 16:45:02 +01:00
12d0e9574b Migrated create torrent dialog to GtkBuilder.
Additionally creating a torrent and saving it on a remote path now mimics the behaviour on doing it locally. Need to evaluate to see if it's possible to also show a progress when doing this remotely as now, the progress is only seen when doing it locally.
2011-07-06 12:45:50 +01:00
dd50b7bea1 Include deluge/ui/gtkui/glade/*.ui data files in setup.py. 2011-07-06 12:33:44 +01:00
4dc4049851 Migrated the connection manager to GtkBuilder. 2011-07-06 11:33:31 +01:00
27a6e398ee Migrated the "add torrent dialog" to gtkbuilder. 2011-07-06 11:17:13 +01:00
7035b1f166 Specify the plugin's name on logging calls. 2011-07-06 11:10:46 +01:00
a701fddbe8 Fix #948: New Release Dialog does not show server version 2011-07-05 19:49:53 +01:00
b512a664c6 Properly set/restore visibility when torrent is/is not "compact". 2011-07-05 15:46:36 +01:00
5bffa3757d Don't default to "localclient" as owner unconditionally.
Only set "localclient" as a last resort, first try to find out who is logged in.
2011-07-05 15:29:51 +01:00
8b6d6e3836 Fix broken SessionProxy tests. 2011-07-05 09:29:12 +01:00
37b9277c0e Update ubuntu tracker icon test 2011-07-05 18:00:06 +10:00
cf891125e6 Only deregister component if registry still exists 2011-07-05 17:58:18 +10:00
f75ec9d484 Fix translation of KiB/s 2011-07-04 21:39:08 +01:00
9a1ae06033 Fix #1239: Translated Tracker Error text not counted in sidebar Error status 2011-07-04 21:29:58 +01:00
55f456d851 Fix up/down speed labels in status tab 2011-07-02 19:14:26 +01:00
c346687510 Fix #1715: AddTorrentsDialog does not display filename changes when switching between torrents 2011-07-02 19:14:26 +01:00
08ee3d8f69 Fix #1582: Wrong path separator returned when moving storage in Windows 2011-07-02 19:14:18 +01:00
795f633bc4 Fix #491: Add auto_manage_prefer_seeds to prefs manager and UIs 2011-07-02 18:44:26 +01:00
b6596a27bc Add 'Last Seen Complete' to GTK and WebUI Status Tabs 2011-07-02 15:50:04 +01:00
b7fd2d1bf1 Fix #1338 Seeds and Peers totals not updating 2011-07-01 02:40:40 +01:00
0f625943c0 Fix #1477: Execute Plugin should ignore Added events from state file on startup 2011-06-30 19:24:38 +01:00
420447e386 Fix #1232: Improve display of Peers Tab IPv6 addresses 2011-06-30 18:02:06 +01:00
a79520e3ee Fix append trackers error occuring with magnet uris 2011-06-30 17:30:46 +01:00
8ae26c368e Add #890: If added torrent already exists, append extra trackers to it 2011-06-28 01:34:58 +01:00
981ad6d7d2 Fix #1246: Losing Labels upon restart 2011-06-27 22:53:47 +01:00
3b5e70580e Fix from_state in TorrentAddedEvent 2011-06-27 22:50:47 +01:00
71f9ef6499 Adjust file priorities to make Highest actually the highest allowed by libtorrent and High has been changed to what Highest was 2011-06-21 10:50:57 -07:00
7dd54b4b34 Save and restore Preferences dialog size from config 2011-06-19 23:29:54 +01:00
c64ed6adc5 Fix preferencesmanager from failing to stop when trying to stop
loopingcall that wasn't started
2011-06-18 20:13:58 -07:00
a82c753ac0 Fix uri handling when dragged to gtk window 2011-06-18 12:45:21 +01:00
96b5f617f2 Change log level of connect failed to INFO 2011-06-17 18:07:34 +01:00
842734c4e4 Add a file exists check to torrents passed as arg 2011-06-16 21:18:10 +01:00
095f4ff20a Fix path error with torrent files prefixed with 'file://' from Firefox 2011-06-16 21:18:09 +01:00
ed0b017fe1 Increase max piece size to 16 MiB in create torrent dialog
Increasing it beyond this will require changes to
createtorrentdialog.py
2011-06-10 13:24:36 +10:00
ce9b540b97 Initial GTK UI Speedups.
The speedups work is being separated into 2 different phases and possibly branches. The idea is to have this minimal speedup merged into master as soon as possible since it's pretty simple. Reduces initial data transfer to about 10% of what was previously being transfered when client connected.
The second phase is regarding row updates, ie, reduce them to what's actually being seen. This part is way more tricky.
2011-06-09 13:49:07 +01:00
5112ed48d1 Fix starting deluge-web when using osx/windows since the options are presented, the later conditionals fail since the options object does not have those attributes 2011-06-06 14:57:50 -07:00
dfa8834db8 Fix starting deluge-web 2011-06-06 14:57:38 -07:00
5bc63fa910 Change component.deregister to take the object as the parameter, not the name 2011-06-06 14:19:51 -07:00
24c945f139 Add a RPCServer.deregister method
Deregister RPC exports when disabling a Core plugin
2011-06-06 13:55:51 -07:00
2542ad9234 Let the Core fire the PluginDisabled event instead of disabling the plugin right away 2011-06-06 13:55:17 -07:00
acb4ab44d2 Merge branch 'stats_plugin_master' 2011-06-06 21:37:08 +01:00
16fbf27b90 Stats plugin update 2011-06-06 21:36:37 +01:00
3397c2487b Fix bug introduced on previous commit. We need to get a selection first! 2011-06-06 19:46:02 +01:00
66e8b34a54 GTK UI un-select row on torrentview shutdown and filter change.
This is specially useful when multiple torrents are selected. This way, the "changed" signal won't be triggered for every row.
2011-06-06 19:40:14 +01:00
59f9d4e5cc Revert "Work around plugins being garbage collected once enabled twice."
This reverts commit 2f71ef4264.
2011-06-06 18:16:05 +01:00
221dea1f1a GTK UI. Center the custom speed dialog on parent(statusbar). 2011-06-06 17:45:51 +01:00
4420aae092 Late import GTK UI's ConnectionManager so that the translations code can be properly setup. 2011-06-05 22:48:17 +01:00
ddc0957e3e Wrap non deluge exceptions so that they can also be sent to the client. 2011-06-05 22:44:01 +01:00
2f71ef4264 Work around plugins being garbage collected once enabled twice.
When a plugin is enabled, disabled and then enabled again, on that second enable, that instance is being garbage collected causing the loading of the plugin to fail. Work around that until we can narrow down why is this is happening on the second enable.
2011-06-05 22:28:37 +01:00
bc56b749ee Merge branch 'translate_updates' 2011-06-05 17:03:21 +01:00
34c95a08a3 Fix translations texts in glade and python files 2011-06-05 17:02:33 +01:00
9ae19e173f Remove page x from translatable in pref_diaog glade 2011-06-05 17:02:33 +01:00
6672aaba1b Change translatable to No for gtk stock labels 2011-06-05 17:02:33 +01:00
0712fc9dee Add gtk-* items to gettextize 2011-06-05 17:02:33 +01:00
07dc9005f3 Update gettextize to ignore .git folder 2011-06-05 17:02:33 +01:00
274a76ab3b Fix translate string in notifications plugin 2011-06-05 17:02:33 +01:00
777993f74a Fix translated string in addtorrentdialog 2011-06-05 17:02:33 +01:00
d1037ae213 update create_potfiles_in to ignore plugins build dir 2011-06-05 17:02:33 +01:00
15e9f5f218 Add 2 more commands to setup.py
Two more commands were added to setup.py:
 * develop_plugins - Installs each of the plugins in development mode
 * egg_info_plugins - Create the '.egg-info' distribution directories for each plugin. This will make the plugin discoverable by deluge

Both these commands are intended to be used while developing deluge.
2011-06-05 16:58:27 +01:00
4aab110aaf Remove duplicate code. 2011-06-05 16:23:09 +01:00
8933ac3123 Fix #1560 - FilesTab Progress value sorting by int instead of float 2011-06-05 13:23:23 +01:00
2e896b520e Fix #1456 - No ETA showing with multiple files 2011-06-05 13:23:16 +01:00
16d27b9657 Catch snd_path is None error in Notification Plugin 2011-06-04 23:24:39 +01:00
d3e8afdda1 Fix Extractor and Example plugin not starting 2011-06-04 22:56:36 +01:00
b86ba13376 Fix Feeder plugin name 2011-06-04 22:51:41 +01:00
f736576436 Fix Execute plugin not starting 2011-06-04 22:51:15 +01:00
9d1715405f Try to get some more debug info for plugins which are failing to load. 2011-06-04 19:25:53 +01:00
ee0d757b0e Some more fixes for plugins not fetching data from the correct namespace. 2011-06-04 19:23:03 +01:00
32c95fac1e Fix the Label plugin to get resources from the namespace. 2011-06-04 19:19:01 +01:00
df3214168c Cleaner fix for #1874, code clean up and reusability.
Translations are now setup on `deluge.common`. Where they used to be setup, a call to `setup_translations(setup_pygtk=False)` is now made.
Every call to `pkg_resources.resource_filename()` is now made through `deluge.common.resource_filename` to make sure that we're loading data from the right deluge install.
2011-06-04 18:06:45 +01:00
9e9261e6f8 Fix #1874.
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. Work around this issue.
2011-06-04 15:56:14 +01:00
087e94f6a1 Update all columns which use the "state" status field on the GTK UI TorrentView and not just the Progress column. 2011-06-04 10:43:24 +01:00
abe0031c2b Trigger a deprecation warning for code calling "getPluginLogger".
Since the plugins namespace was merged into master, calling "logging.getLogger(__name__)" will result in a properly named logger for plugins which will allow logging filtering.
The previous workaround "getPluginLogger()", is now deprecated.
2011-06-04 09:02:53 +01:00
13db148a11 Now that the plugin's namespace is in use, make the plugins get their own logger instead of using getPluginLogger() 2011-06-03 23:10:43 +01:00
84c5078667 Merge branch 'master' of deluge-torrent.org:deluge 2011-06-03 14:55:34 -07:00
cebdc89b18 Fix systemtray from stopping properly when appindicator is enabled 2011-06-03 14:53:41 -07:00
87e767d4c1 Merge branch 'master' into plugins-namespace 2011-06-03 17:48:22 +01:00
ce406674ec Update setuptools version in ez_setup 2011-06-02 21:50:38 +01:00
ac5f9a2828 Fix up stopping classic mode 2011-06-02 11:55:26 -07:00
6d55c44983 Fix menubar component stopping properly 2011-06-02 11:54:53 -07:00
1557d0da1f Move the log level to the left of the module because this field is a fixed width and its easier to read this way 2011-06-02 11:53:54 -07:00
2f785216f6 Show errors when trying failing to properly stop a component 2011-06-02 11:53:34 -07:00
8f1730591b While clearing the search filter, restore from pre-filter if available. 2011-06-02 11:16:11 +01:00
9ec44894d4 Fix #1873. Re-add the files and peers tab menus. 2011-06-02 10:58:45 +01:00
bb981127db spaces FTW 2011-06-01 20:29:38 +01:00
a96aeed706 Fix #1869: Set the disk io read/write to bypass OS cache in Windows as suggested in http://code.google.com/p/libtorrent/issues/detail?id=166 2011-05-31 09:56:23 -07:00
f14de6553a GTK UI search box grab_focus() on show. 2011-05-31 15:20:15 +01:00
b521b3065b GTK UI search box pre-filter implementation.
Implement a pre-filter for the search-box which will filter the currently visible torrents while waiting for the filter request to sent to the demon. This will make the searches seem way faster :)
2011-05-31 14:36:05 +01:00
ea438609bf GTK UI search by torrent name filter as a "toolbar".
Now, instead of permanently having a search box to filter the visible torrents by name, we now, mimic a toolbar just for that, mapped to CTRL-F. There's also a menu item in the "View" menu and a toolbar icon to toggle it. Implemented "Match Case" for the search.
2011-05-31 13:38:48 +01:00
0ba51d0e51 Fix peers tab flagsos.path.join 2011-05-30 22:14:36 +01:00
53370e4639 Fix peers tab flags missing 2011-05-30 21:33:17 +01:00
c70c8ea45d Add check to key_press_event for keyname returning None 2011-05-30 18:18:27 +01:00
937b53b355 pixmaps and icons moved to ui/data, all necessary references changed. 2011-05-30 17:57:39 +01:00
27cd89c4ad Remove setting the resume_data key to '' in the add_torrent_params as this causes libtorrent 0.16 to crash 2011-05-29 18:09:04 -07:00
c4dbf017a5 Merge branch 'master' into pieces-progress-bar
Conflicts:
	deluge/ui/gtkui/glade/main_window.glade
2011-05-29 20:00:25 +01:00
ec74f9aae3 Merge branch 'gtkui-search-by-name' 2011-05-29 19:21:12 +01:00
cfd955a605 AutoAdd plugin: Don't try to remove the same path twice! 2011-05-29 19:06:54 +01:00
feed806983 Wait at least 0.7 secs before triggering an update because of the search box, ie, allows typing the full search string before making the request. 2011-05-29 19:02:33 +01:00
c66637116b Allow searching torrents by name on the GTK UI. 2011-05-29 17:10:57 +01:00
4d4c6404b1 Log exception occurring while sending RPC errors to clients. 2011-05-29 16:39:00 +01:00
042ddd2891 Checkbox had no signal. 2011-05-29 14:36:26 +01:00
af24542856 AutoAdd plugin fix for #1863
In some cases, using `os.rename` between different mount points can trigger an `OSError`. Try to address these issues properly.
2011-05-29 11:37:31 +01:00
6dc393ed23 Allow a smtp port higher than 100
Add a shadow to scrolled window
2011-05-28 18:26:53 -07:00
c13eade81c Merge branch 'master' into pieces-progress-bar 2011-05-28 21:59:20 +01:00
eb639c3722 AutoAdd plugin auto fill.
When adding new entries, the dialog is auto-filled with what's defined for the Downloads entry in the preferences.
When showing errors, use the dialogs module.
Added some tooltips to the dialog.
2011-05-28 19:43:25 +01:00
dc514d308c GTK ui dialogs now have deluge's icon set. 2011-05-28 18:59:04 +01:00
67b5cde128 Fix #1867.
Now, if any option is changed on a torrent's options tab on the GTK UI, the apply button is set to sensitive.
2011-05-28 11:42:09 +01:00
ef98d19ed4 Merge branch 'master' into pieces-progress-bar 2011-05-28 10:54:03 +01:00
94a7b2ebf1 Fix #1861 - AutoAdd Warning (column number is a boolean) 2011-05-28 00:14:01 +01:00
e0443943b5 Catch an IndexError occurring in Files Tab when scrolling through long list of torrents 2011-05-28 00:05:18 +01:00
dd78a75ca8 Fix #1860 - Files Tab TypeError (could not parse subscript as a tree path) 2011-05-28 00:05:12 +01:00
82712c80e1 Fix #1195 - Right-click selecting issue when switching between files and folders 2011-05-28 00:05:00 +01:00
a710bcaed4 Add F2 key shortcut to rename files in Files Tab 2011-05-28 00:04:55 +01:00
3a7c182f83 Add XDG_DOWNLOAD_DIR for default download folder #1788 2011-05-28 00:01:58 +01:00
d42778afa3 Show the checking icon for the Checking Resume Data state 2011-05-25 13:21:16 -07:00
724025092a Set the WM_CLASS name to Deluge 2011-05-25 13:18:12 -07:00
bd43f3c464 Small text updates 2011-05-24 02:00:05 +01:00
8464a938b2 Fix up displaying versions in the about dialog 2011-05-23 17:09:16 -07:00
b8fad45eaa Change Connection Manager Key Shortcut to Ctrl-M 2011-05-23 22:23:35 +01:00
b08e90ac2a GTK UI edit trackers dialog. Remove un-used attribute. 2011-05-23 09:37:02 +01:00
13a379ef6c Update certain torrentview columns to default to not visible 2011-05-23 01:06:37 +01:00
09e24df4bb Merge branch 'masterchanges' 2011-05-22 23:36:49 +01:00
019f2a0619 Fix Up/Down buttons in Edit Trackers Dialog 2011-05-22 23:22:37 +01:00
2fb874d486 Add ability to set columns as not visible by default by setting the kwarg default to False when adding the column 2011-05-22 15:12:11 -07:00
85b4ceec30 Feature #1308: Add Seeds/Peers ratio to torrent list view 2011-05-22 22:48:03 +01:00
b0599313bc Feature #1646: Add columns for per torrent upload and download speed limits 2011-05-22 22:35:20 +01:00
974f48380f Change default value of close_to_tray to False
Prevents default install of Deluge disappearing if tray icon is missing.
2011-05-22 22:19:36 +01:00
b3865d0a7f Fix GTK UI edit trackers dialog.
Fix an issue with the edit trackers dialog where editing, adding or removing trackers was not "saved" in client/daemon mode.
2011-05-22 21:56:16 +01:00
79c9dd3076 Add libtorrent version to user_agent string
Example: Deluge/1.3.900-dev Libtorrent/0.15.5
2011-05-22 19:20:40 +01:00
edb0c2e71d Modify setup scripts to be executable 2011-05-21 18:54:15 +01:00
1c58dce3c1 Supress gobject warning in filtertreeview and torrentview
In console the warning "g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed" will appear. Quick investigation could find no solution with suggestions a python issue.
2011-05-21 17:32:20 +01:00
445f3c0123 Fix menu bug caused by Change Owner menuitem code
The right-click torrent menu would move/jump around when the option submenu was opened with the mouse, possibly due to a conflict between glade file and append menuitem code in menubar.py. Solution was to create a menuitem entry for Change Owner in glade file and update code to add submenu to this new entry.
2011-05-21 16:23:36 +01:00
eb15c96403 Add key shortcuts for menu items 2011-05-21 16:22:42 +01:00
71f411e458 Some comment explanation. 2011-05-19 00:29:30 +01:00
856a6cd1ab Pieces bar "calculation" bug fix. 2011-05-18 05:01:30 +01:00
99f2dbd178 Minor __doc__ naming change. 2011-05-18 05:01:30 +01:00
0e4747bf22 Lower debug messages level. 2011-05-18 05:01:30 +01:00
81637f4572 Add a nice border to the pieces bar. 2011-05-18 05:01:30 +01:00
25f086fa85 PiecesBar caching.
The drawings made on the pieces bar are now cached in "sub-drawings" kept in memory. If no data has changed, those "sub-drawings" are used. If data changed, redraw whatever is necessary.
2011-05-18 05:01:30 +01:00
6d57a29f1d PiecesBar rounded corners. 2011-05-18 05:01:30 +01:00
9b3f5783d5 Update ChangeLog. 2011-05-18 05:01:30 +01:00
b3492b07a1 PiecesBar
Now, either show the pieces bar or the progress bar, not both.
2011-05-18 05:01:30 +01:00
28def22625 PiecesBar enhancement.
The pieces bar will now draw status text, like a regular progress bar does, and for the overall progress, a semi-transparent overlay is also drawn.
2011-05-18 05:01:30 +01:00
427fe23bdc Minor comment cleanup. 2011-05-18 05:01:30 +01:00
da5c5d4b84 PiecesBar implementation.
We now provide an option to the user to see the states of a torrent's pieces, ie, completed, downloading, waiting, missing.
If the user has this option enabled, another 3 will be shown to him(on the GTK UI), which will allow him to choose the colors for each piece state.
2011-05-18 05:01:29 +01:00
438cbd2238 Correct the pieces states "calculation". 2011-05-18 05:01:29 +01:00
2d59b62317 Provide the option to the user to use the pieces bar or not(Glade file only). 2011-05-18 05:01:29 +01:00
19f32b1446 Provided a method to get the pieces information.
Each piece will return 0 for a missing piece, 1 for a not downloaded piece, 2 for a downloading piece and 3 for a downloaded piece.
2011-05-18 05:01:29 +01:00
9b812a4eec Extend RPC Protocol.
While adding the multiuser auth related stuff, RPC_AUTH_EVENT was added, simply to not touch how RPC_ERROR was handled. It was created to allow recreating the exception on the client side. Instead of adding another rpc event type, extend RPC_ERROR to send the proper data to recreate the exception on the client side.
2011-05-18 03:56:17 +01:00
e383187796 Fix #1281. Show a nice dialog stating that the client is incompatible on the GTK UI. 2011-05-17 22:50:05 +01:00
6151050ad4 PEP-8 2011-05-17 22:16:12 +01:00
9a3bf35cdf Include our custom lower log levels into python's logging. 2011-05-17 02:47:16 +01:00
6391970fad Updates to desktop file
Add magnet mimetype and tryexec key
Fix exec key to handle files and urls to conform with new desktop-entry spec
Update name, comment and category keys
2011-05-16 22:11:20 +01:00
0ba0e013b5 Force backwards incompatibility.
Force clients prior to 1.4 to fail authentication, this was we might reduce tickets like #1852.
2011-05-15 22:18:38 +01:00
552c898998 Fix #1844.
The submenu got lost on one of the glade files merges. Re-added.
2011-05-15 11:26:41 +01:00
bc5b4d902f Fix LP Bug #779074 - TypeError in on_key_press_event(): cannot concatenate 'str' and 'NoneType' 2011-05-12 22:42:13 +01:00
6a8e3f1c49 Minor code cleanup. 2011-05-11 09:06:21 +01:00
81ca9952e9 Moved core.conf config upgrade to core instead of pref's manager. 2011-05-10 20:15:33 +01:00
74618d5a65 GTK UI move storage fixed. Was still not allowing moves. 2011-05-10 20:13:26 +01:00
0c110c2408 Fix broken tests. Twisted deferred loops, must be stopped! 2011-05-10 11:21:24 +01:00
1ac997e7d7 Upgrade core config and handle empty AutoAdd watchdirs.
We now upgrade the core's config to include 'sequential_download'.
On the auto add plugin, if there are no watchdir, provide a default that will allow the GTK UI not to thrown an exception.
2011-05-09 15:47:10 +01:00
d4692bef42 AutoAdd Plugin. Remove line feeds from log messages. 2011-05-08 23:48:42 +01:00
77fc53afc0 Sequential downloads. Was querying for the wrong key on the torrent status. Fixed. 2011-05-08 23:46:55 +01:00
3b676eca40 Sequential downloads: Implemented in GTK UI torrent options tab. 2011-05-08 23:33:21 +01:00
ce3ce2c035 Sequential downloads: Implemented in GTK UI preferences dialog. 2011-05-08 23:11:51 +01:00
c8735b5cab Sequential downloads: Implemented in add torrent GTK UI dialog. 2011-05-08 22:55:55 +01:00
cc5f2ffe18 Implemented sequential downloads on core. 2011-05-08 22:39:11 +01:00
89b79c76a3 Multiple files prioritize first last.
Now `set_prioritize_first_last()` sets the first 2% and the last 2% of the pieces of each file on a torrent at a high priority, ie, it no longer works on just single file torrents.
2011-05-08 22:11:20 +01:00
837c39fdda Last seen complete checks.
Remove some un-necessary `lt.version_minor` checks since these checks will remain for a while, at least until deluge depends on libtorrent >= 0.16 which should preferrably not happen.
2011-05-08 21:58:26 +01:00
110026edbe AutoAdd plugin #1842
Fix bug #1842 and also implement "delete torrent file on torrent removal from session".
2011-05-08 21:38:53 +01:00
3b8ebf68a6 GTK UI menubar accounts retrieval.
Only ask for known accounts if we have the required level for it.
2011-05-08 21:36:10 +01:00
ffd344d0b5 On some "race" conditions, the torrent is removed before it's status could be retrieved. Return an empty status. 2011-05-08 21:35:06 +01:00
9d29ca7b29 Check against libtorrent >15 not >16. 2011-05-08 02:39:35 +01:00
38906468c1 Last seen complete
Update last_seen_complete when a status is queried for and that key is on the keys to get or it's a full status query. Either way, only "calculate" last seen at a minimum of one time per 60 seconds(simple caching).
2011-05-08 01:36:40 +01:00
95d7caf3ac Implement Last Seen Complete, on core and on GTK UI. 2011-05-07 20:22:55 +01:00
4044f52f77 GTK UI Torrent's options tab.
Set the apply button to sensitive if the value of a spin button changed. This was missing.
2011-05-07 15:54:10 +01:00
a7bd953169 GTK UI Connection Manager (#1824)
Warn the user if he's trying to connect to a local daemon that's not running and "start local daemon if necessary" is not enabled.
Extend and reuse the connection callbacks on the connection manager to re-use code.
2011-05-07 15:14:32 +01:00
8922717ff2 fix unrequired requests 2011-05-07 13:20:09 +01:00
117d50b728 fix a bug when the host_id doesn't exist 2011-05-07 13:20:04 +01:00
04af8965bc apply patch from #1742 2011-05-07 00:02:49 +01:00
d6f5e5b4ec fix #1537 editing trackers list, trackers have to be reselected 2011-05-06 23:44:19 +01:00
1f3a7bf44c Fix #1333 Peer list doesn't update automatically 2011-05-06 23:25:07 +01:00
2e62ced811 fix #1323 filter panels not scrollable 2011-05-06 22:33:10 +01:00
95819c79e5 Fix #1268, Torrent errors not displayed in webui 2011-05-06 22:23:25 +01:00
5ad21303c6 fix issue #1567, js from plugins not working with different base setting 2011-05-06 22:09:17 +01:00
922e64a07e fix issue #1799 2011-05-06 22:01:06 +01:00
30d70d2b9b apply patch from #1562 2011-05-06 19:16:20 +01:00
a06b350858 Correct log message. 2011-05-06 19:10:26 +01:00
06f025f4bd fix the widths on the input boxes, whitespace changes too 2011-05-06 19:03:05 +01:00
d362a6ceba fix the path given by the set-cookie header 2011-05-03 19:05:15 +01:00
138b8ae314 Sorry for the noise. Email change. 2011-05-02 05:02:46 +01:00
6f3bc5620f Fix #1824.
When connected to a client, and then trying to connect to another, the connection manager component will be stopped(while the connect deferred is still running), so, the ConnectionManager.connection_manager reference will be deleted. If that's not the case, close the dialog.
2011-05-01 07:41:44 +01:00
f2249d5803 Remove un-used import. 2011-05-01 04:46:54 +01:00
f26de83509 Don't run into loops when the auth file does not exist and we're trying to create it. 2011-05-01 04:44:42 +01:00
f6826a4f48 Fix #1822
Only query the core for the known accounts if connected to it.
2011-04-30 20:45:15 +01:00
dd3f78bd36 More changes related to automatic connections.
Auto-connecting on start of the gtk ui is now fully working.
Auto-staring localhost if needed is now also working.
Authentication failures now get passed correctly to the client implementation which will allow the user to enter username and/or password to complete authentication.
It's now possible to shutdown the daemon from the connection manager even if not on localhost, it just needs all required information to be present on the liststore.
2011-04-30 07:42:06 +01:00
63d0d0c69b GTK UI connection manager fixes.
Auto-connecting to daemon now works. Fixes #1815.
Auto-starting a `localhost` daemon now also works, the reconnecting attempts were not being "triggered".
When not connected to a daemon, "Quit & Shutdown Daemon" is not present. So #1818 is also fixed.
Some more work regarding #1819 was done.
Client now disconnects before shutting down the GTK UI.
2011-04-28 16:59:01 +01:00
1be59bb116 don't show decimals for 100% progress (100% instead of 100.00%) 2011-04-28 14:28:53 +02:00
751345fc28 minor function renaming to make things properly private 2011-04-28 14:28:53 +02:00
12ea65d188 show priorities for directories. fulfills feature request 1688 2011-04-28 14:28:53 +02:00
e950cca059 reset selection to top on alltorrent resume in case another mode has effected torrent list 2011-04-28 14:28:53 +02:00
a063095dad Make sure we have a config loaded in the connection manager. Fixes #1819. 2011-04-28 11:06:26 +01:00
39978d5ade Fix #1278 by keeping references. 2011-04-28 10:37:35 +01:00
9fa8748432 Fix some clean config dir issues.
Moved some auth stuff to `deluge.common` because they're also used on the GTK UI.
Now, if a user starts deluge in classic mode and tries to change it to client/daemon mode, he see's a dialog stating that the current session will be stopped. If he answers no, nothing is done, the classic mode pref is set back as it was. If he answers yes, all components are stopped and client is disconnected. At this stage the user can open the connection manager to start the daemon and connect.
If the user starts in client/daemon mode and switches to classic mode he see's a dialog stating that deluge must be restarted.
The GTK UI connection manager now loads it's default config with the localclient username and password. If not present in the auth file, the auth file will be recreated.
2011-04-27 22:06:13 +01:00
18b27d4b49 Remove a pref regarding auto adding in queue thinking it was from the core's previous AutoAdd. Re-Added. 2011-04-27 19:42:54 +01:00
f41f6ad46a Test fixes and #1814 fix.
All test were adapted, and some more were added to comply with the new multiuser support in deluge.
Regarding #1814, host entries in the Connection Manager UI are now migrated from the old format were automatic localhost logins were possible, which no longer is.
2011-04-27 19:32:13 +01:00
bb9a8509c8 Fix trying to load the AutoAdd component as it no longer exists in core 2011-04-27 11:03:43 -07:00
6694ac7a58 Merge branch 'master' of deluge-torrent.org:deluge 2011-04-27 17:30:09 +01:00
81d22eb730 When parsing the auth file, if an old format is found(with no auth levels), make sure that the localclient always has the ADMIN permission as he should. 2011-04-27 17:30:00 +01:00
47a9b18b89 enforce min/max values for float/int spin inputs 2011-04-27 18:09:20 +02:00
292929ba59 better handling of keyboard input in int/float spin inputs 2011-04-27 18:09:20 +02:00
cbcf413ffd use callbacks for mode switching when stopping alltorrents component. should fix bug 1686 2011-04-27 18:09:20 +02:00
4d8b34209b fix column trimming a bit 2011-04-27 18:09:20 +02:00
98a8be7131 avoid having the tests hang due to a failing to connect client! 2011-04-27 13:38:23 +01:00
2e68e0181c Remove some leftovers from the old multiuser branch. 2011-04-27 13:18:59 +01:00
e6773dfce1 Merge branch 'master' into multiuser-oldprefs 2011-04-27 13:15:05 +01:00
f56be66556 Update ChangeLog. 2011-04-27 13:06:26 +01:00
67a4fd49e9 fix problem when not showing name,state or queue columns. fix problem if there were no var length cols. 2011-04-26 22:59:48 +02:00
e992ac3eab ignore key presses that only makes sense when we have a state when we don't have a state 2011-04-26 14:18:25 +02:00
d05352db65 fix bug for selecting multiple torrents with cursor above last mark (bug 1689) 2011-04-26 14:03:49 +02:00
b1e0dd66eb handle files with [ and ] characters in them 2011-04-26 13:53:11 +02:00
897c2f981f Add help to torrent details mode.
fixes bug: 1687
2011-04-26 12:41:19 +02:00
91801e1632 Cleanup debug message new lines. 2011-04-26 10:14:39 +01:00
fa20e49a93 Fixed 2 bugs regarding torrent ownership change.
On the core, the code was not adapted to the new AuthManager. On The GtkUI, nothing was being shown to the user when errors occurred while changing ownership.
2011-04-26 08:37:20 +01:00
4432e6e6e3 Also handle moving the torrent files after adding them besides renaming or deleting(per whatchdir) 2011-04-25 16:38:49 +01:00
c225c045cb Better file modification detection. 2011-04-25 15:07:07 +01:00
e552c21f66 Automatically detect auth file changes and reloading those changes. 2011-04-25 14:43:44 +01:00
89d04a393b Upgrade old auth file, save it and reload it. 2011-04-25 14:35:01 +01:00
f1730dc4d4 Removed leftovers from the core autoadd. All autoadd features are now addressed by the AutoAdd plugin. NOTE: Console UI and Web UI should also remove the core auto add stuff. 2011-04-25 13:44:27 +01:00
fb5005e3f6 The AutoAdd plugin now supports multiusers. 2011-04-25 13:16:11 +01:00
51b5b23f76 Remove debug printing. 2011-04-25 13:12:18 +01:00
78e966946f Allow setting torrent ownership when adding new torrents. 2011-04-25 09:13:45 +01:00
936bd925d9 Some changes were left behind on last commit. 2011-04-25 08:12:46 +01:00
43e3fe2a1a Account Management Implemented.
Account management is now implemented for the GTK UI. Some changes to the core were required since the clients need to know which authentication levels exist, and, to expose account creation, update, and removal to the clients. The best effort is done to try not to compromise the auth file.
2011-04-24 17:38:35 +01:00
6ed3136c8e No server side logouts.
Do not try to for a disconnect on this branch, maybe on another one.
2011-04-24 17:19:57 +01:00
8195421c99 Some account management work. Not yet complete. 2011-04-22 18:51:51 +01:00
342da12d0c Merge branch 'master' into multiuser 2011-04-22 11:44:10 +01:00
5296fc7d4c Fix one more possible issue with regard to #1786 2011-04-22 11:42:48 +01:00
233e814547 Late import twisted's reactor, it allows the gtk reactor to be proper installed. 2011-04-22 11:33:45 +01:00
03325c5f48 Add some missing code 2011-04-22 11:27:14 +01:00
1a6742b1e2 Keep consistency on deluge.config. 2011-04-22 11:26:09 +01:00
154688a3e2 Implement __delitem__ on deluge.config.Config. 2011-04-22 11:26:09 +01:00
fe12552590 Now, when the authentication dialog appears, user fills in the password and hits ENTER, the authentication call will be made.
Implement tooltips on treeview's headers when adding columns.
Renamed the "public" state of a torrent to "shared", ie, shared among other deluge users. Allow changing shared state from clients and currently from the    GtkUi.
2011-04-22 11:26:08 +01:00
e63c33c496 Allow changing ownership of torrents. In order to achieve this, added deluge.core.set_torrents_owner(), deluge.core.get_known_accounts(), deluge.core.authmanager.get_known_accounts() and deluge.core.torrent.set_owner()`. So far only the GtkUi has this fully implemented. 2011-04-22 11:25:33 +01:00
105cb52cb0 Add method to return the current authenticated username. 2011-04-22 11:24:42 +01:00
3e0ea26e5f Remove unused method, duplicate log messages and add a method to get the logged in user's authentication level. 2011-04-22 11:24:42 +01:00
e44cac0eaa Since there's no default username for authentication, update hostlist to include the username.
Remove debug prints or extreme debugging. Minor code cleanup. Remove un-used imports.
2011-04-22 11:24:42 +01:00
86a1b801f5 Now it is possible to not even store the username on the hosts entry in the connection manager, both username and password will be asked to the user. WARNING: No more "localclient" automatic login, ie, username, is mandatory else, it will be asked to the user. 2011-04-22 11:24:42 +01:00
b3870ad6dd Use a specific response code for authentication requests. Recreate authentication request exceptions on the client end. 2011-04-22 11:24:42 +01:00
67ff83360f Use the exceptions from deluge.errors. 2011-04-22 11:22:19 +01:00
b2a16a0240 Move deluge errors to the errors module, they will be reused later on other parts of code.
Now, calling connect on client has two behaviours, if username/password is passed the client connects, authenticates and returns daemon info, if username/password is not passed, only the daemon info is returned. This might change a bit later on though.
2011-04-22 11:22:19 +01:00
e17c035521 Cleaned up previous commit regarding threads and the GTK2Reactor.
Now a dialog apears if the daemon complains about a missing password in order to authenticate. Asks the password from the user and retries to connect.
2011-04-22 11:22:19 +01:00
249398489e Removed problematic code. GTK2Reactor takes care of all that. 2011-04-22 11:22:19 +01:00
d44f59a0e7 Add some docstrings. 2011-04-22 11:22:19 +01:00
6c99204828 The GtkUi's connection manager now has the ability to edit existing host entries besides adding and deleting them.
It also asks for a password prior to attemting to connect in case the password is null, this alows host entries not to store the passwords on file like it has done so far.
NOTE: This is not yet the desired behaviour, ie, the daemon should simply complain if the authentication details are incomplete and the client should act accordingly. I had an issue with this though, I catched the errback the daemon was sending, asked the user for the password and re-tried to authenticate again. However, twisted always locked when I tried this. I'm investigating it.
2011-04-22 11:22:19 +01:00
1794f09b21 Make branch runnable. 2011-04-22 11:22:19 +01:00
b08a4679de Respect the torrents ownership and unless the logged in user is an admin, only return his own torrents plus all other public torrents. 2011-04-22 11:21:12 +01:00
bfc221fc18 Begin work on fixing up the Preference dialog
Split up the glade file for each individual page
2011-04-22 11:21:12 +01:00
5ad3a1666c Update the ErrorDialog 2011-04-22 11:20:15 +01:00
49d5ed6bde Hopefully, final fix for #1786:
We now make sure that a state file exists when trying to restore an order from it.
The best effort to restore the previous order is made, though, in some cases, since we're matching against names which are translatable, a match might not be found, in that case, continue the effort though skip the non matching column name. On next load, everything should be fine since the state file will include the, now in use, translation.
2011-04-21 20:55:01 +01:00
4b9209674e Merge branch 'master' of deluge-torrent.org:deluge 2011-04-21 18:31:03 +01:00
b9a688013f Fix #1786:
Don't reorder duplicate columns, need to dig how duplicate columns got into the state file, and also skip non visible columns.
2011-04-21 18:29:59 +01:00
387b746fae Merge branch 'master' of deluge-torrent.org:deluge 2011-04-21 10:41:15 +01:00
796109649d fix the client tests albeit in an ultra hacky way 2011-04-21 10:41:00 +01:00
d258794517 Merge branch 'master' of deluge-torrent.org:deluge 2011-04-21 00:47:01 +01:00
98f80c0eb6 Restore column order from state.
On the TorrentView's treeview, the column's position stored on state was being ignored, but event if that info was not being ignored and passed to the several `add_column`'s the order could not be added because the order the columns are added does not(might not) match what was stored in state. So, we now restore the ordering once all columns are added.
2011-04-21 00:42:36 +01:00
d18becc861 fix bug due to vim stripping regex being wrong 2011-04-20 18:37:39 +01:00
0503db85ea Merge branch 'master' of deluge-torrent.org:deluge 2011-04-20 18:34:02 +01:00
bcb636dda4 improve the core tests to use a built in webserver 2011-04-20 18:32:55 +01:00
5bc304470c Let's use what the stdlib provides us. Use email.utils.formatdate instead of strftime() a datetime object. 2011-04-14 09:29:40 +01:00
42e1e2fd20 Include proper Date header in email notifications. 2011-04-13 12:54:59 +01:00
bb0746c3e8 use os.sep and strip what's already in input box from complete options 2011-04-13 11:52:03 +02:00
19799d74b4 Include gif pixmaps in the package data 2011-04-09 22:36:12 +10:00
0d560bcd6f Add update-tracker/reannounce command for command-line/legacy interface 2011-03-29 14:56:39 +02:00
69b79756f2 Merge branch 'master' of git.deluge-torrent.org:deluge 2011-03-28 16:51:23 +01:00
fd248eb1fd split the auto_add_trackers textfield otherwise it breaks the label plugin 2011-03-28 16:51:03 +01:00
45ccd3b84a Fix libtorrent not compiling with boost libs 1.46 2011-03-26 17:33:26 +11:00
298b85c368 Improve autoadd filename matching (fixes #1614) 2011-03-26 17:33:26 +11:00
67add964de Apply patch from #1581 to add an option to enable the app indicator
interface
2011-03-22 17:16:50 -07:00
e81a279dc2 fix naming issue with close callback in popups 2011-03-19 12:38:55 +01:00
280781ded9 queue management in torrent actions 2011-03-13 12:53:13 +01:00
f30a2858ce convert the tests to use a local webserver instead of pinging deluge-torrent.org and damoxc.net 2011-03-08 14:43:09 +00:00
32b41fabd6 Handle redirection when adding a torrent by url 2011-03-09 00:44:42 +11:00
a0f9689664 Handle partial downloads due to incorrect headers (fixes #1517) 2011-03-09 00:40:15 +11:00
08843ccad5 support prefs that don't fit on the screen
remove superfluous log message
2011-03-08 12:21:45 +01:00
e0bb8869aa add some more columns 2011-03-08 11:34:59 +01:00
255af3c485 set status fields to get from visible columns 2011-03-08 00:35:55 +01:00
f35145b0a6 make alltorrents a component like it should be and get rid of alltorrentsstateupdater 2011-03-08 00:16:42 +01:00
f2d560351e add preferences for display and size of columns in alltorrent mode 2011-03-07 13:40:25 +01:00
62da60a0e4 break out each column preference 2011-03-07 12:37:23 +01:00
d9c1a56d44 use config params for columns to show 2011-03-07 12:14:36 +01:00
84f278dbcc show a 'scrollbar' of sorts for popups with a lot of text 2011-03-01 19:39:55 +01:00
356f298e9c update help display using new wrap_string.
re-wrap on resize

add some highlighting
2011-03-01 19:13:17 +01:00
5fb01dacc0 add a {!normal!} color scheme 2011-03-01 19:12:48 +01:00
8d541ad419 use new wrap_string function in MessagePopup 2011-03-01 19:12:05 +01:00
426eea154e add a wrap_string function that works properly 2011-03-01 19:11:53 +01:00
ccc047848a split long lines in torrent info popup 2011-02-28 20:22:49 +01:00
11d8332e43 make sure we're in interactive mode before checking for screen 2011-02-28 14:03:39 +01:00
2193240c66 update help for legacy mode 2011-02-27 17:15:12 +01:00
e43c532e63 remove screen.py
all functionality has been moved elsewhere at this point
2011-02-27 17:14:50 +01:00
930addb389 add 'legacy' mode, which is basically just the old deluge-console, for curmudgeons 2011-02-27 17:12:57 +01:00
fab1e8412d encode string before write to specified encoding 2011-02-24 14:18:49 +01:00
1cce30393b add interactive field to commander so command -h will work 2011-02-24 13:24:45 +01:00
510c81776f add status command 2011-02-24 13:24:35 +01:00
e7096d9509 Merge branch 'master' of deluge-torrent.org:/deluge 2011-02-24 12:13:06 +01:00
5ae242472f don't search for empty string in alltorrents.py 2011-02-24 12:11:14 +01:00
ee75786e40 handle the case where libtorrent doesn't know what files are in a torrent.
this is the case when torrent was added by a magnet link
2011-02-24 12:08:42 +01:00
87473f2cde support magnet uris in add command/dialog 2011-02-24 12:08:22 +01:00
b2f349c05d Remove unnecessary import 2011-02-23 11:22:29 -08:00
9ac0d62149 Remove some imports that are no longer neccessary 2011-02-23 11:21:47 -08:00
db46a97263 Merge branch 'master' of deluge-torrent.org:/deluge 2011-02-23 20:05:48 +01:00
4c2f9a1a0a un-revert main.py as it doesn't actually handle events. 2011-02-23 20:04:15 +01:00
64e38eac20 Revert "Fixed "PluginEnabledEvent" and "PluginDisabledEvent" the argument cannot be called name since that should contain the event name."
This reverts commit 078ed6ba71.
2011-02-23 11:01:59 -08:00
499a58f50d Revert 67ea05921c which changed how event handlers worked.
Unfortunately this will not work with plugins as they do not have the events defined and
the creation of the event object fails.
2011-02-23 10:47:16 -08:00
5f0f7204a8 don't use libtorrent anymore in add 2011-02-23 16:15:34 +01:00
62f6683730 support urls from add dialog 2011-02-23 16:12:49 +01:00
60d96c6f20 add add_spaces to InputPopup 2011-02-23 16:07:47 +01:00
d1efe5f1b4 support adding url arguments in add.py command 2011-02-23 15:53:14 +01:00
62421080ef better unicode handling and trimming 2011-02-22 18:04:16 +01:00
9e4ea0a671 don't trim rows
(basemode seems to get confused on strings with double-wide chars)
2011-02-22 17:18:12 +01:00
b11468c19b handle double-wide unicode characters 2011-02-22 17:17:55 +01:00
40a5722987 support -s STATE in info 2011-02-22 16:57:56 +01:00
956ea10a00 convert torrent_ids back into a list in case we need to remove from it 2011-02-22 16:56:54 +01:00
543fcf722c use os.path.commonprefix to do better completion 2011-02-22 14:58:40 +01:00
7f52472e9e add move command for console ui 2011-02-22 13:56:22 +01:00
5619991f2a add move storage option to torrent actions 2011-02-22 13:39:38 +01:00
2b04955128 don't double add current torrent on action popup 2011-02-22 13:39:20 +01:00
e83d540fe4 update help 2011-02-22 13:38:54 +01:00
4e5d88da82 import deluge.ui.common for TorrentInfo command 2011-02-22 12:34:29 +01:00
10816cb8f4 always have a torrents list, in case get_torrent_name gets called from a mode without get_torrent_name in new ui 2011-02-22 12:32:32 +01:00
5f8eda9204 Merge branch 'newconsole'
Conflicts:
	deluge/ui/console/main.py
2011-02-22 00:26:57 +01:00
b0c561dbbc add add_checked_input 2011-02-22 00:09:27 +01:00
1173f1c714 support globbing for multi-file add and have better fail reports 2011-02-21 17:44:12 +01:00
3da5cd9816 add get_torrent_name 2011-02-21 17:43:35 +01:00
d9d8762c8e add get_torrent_name 2011-02-21 17:41:28 +01:00
e16ee523a5 fix messagepopup height_req 2011-02-21 17:41:10 +01:00
3db7bcbfc7 make message popups a bit more sane 2011-02-21 16:30:49 +01:00
e1a3a431f0 support command line options again 2011-02-17 16:26:58 +01:00
9a3316f950 use '-' instead of '~' in progress bar 2011-02-17 16:04:54 +01:00
1789e8d03c Minor changes for command line usage 2011-02-17 16:03:11 +01:00
837322478b return deferred for proper command line behavior 2011-02-17 15:30:14 +01:00
ce2516ab2c search field is black,white 2011-02-15 20:15:43 +01:00
962bfc3d2c support searching torrent names in alltorrent view 2011-02-15 20:14:14 +01:00
4ff0fb19ee left arrow goes back to overview from torrent details 2011-02-15 19:36:17 +01:00
7a4006439b add preferences help 2011-02-15 19:10:57 +01:00
c015c3a57d update help a bit 2011-02-15 19:10:51 +01:00
8a9e732f95 lots of new preference work 2011-02-15 18:55:27 +01:00
0b3c408e64 make the edit trackers window resizable 2011-02-15 12:54:35 +00:00
d3a61bbda4 fix scrolling on the edit trackers window 2011-02-15 12:49:04 +00:00
c523958bf6 Fixes for gtk-ui translations 2011-02-14 23:50:08 +00:00
06003b3650 Fix translate issue for Trackers tree in sidebar 2011-02-14 23:49:45 +00:00
1e0005f572 Fix: os.join created root path in Remove_Empty_Folder if variable 'folder' had a leading slash 2011-02-14 23:49:04 +00:00
4a071ecba1 add an eventview 2011-02-14 12:38:18 +01:00
77eb1a5f82 prefs actually work. some tweaks to inputs to better support prefs
not all prefs available yet
2011-02-14 12:26:24 +01:00
ac8c928a5b don't always refresh on __init__ 2011-02-14 12:25:43 +01:00
23f64a5440 add back write method and keep track of events for (future) event view 2011-02-14 12:25:04 +01:00
98ca371b15 Sidebar: Enabled strings for translation and added icons to Trackers filter 2011-02-11 22:10:00 +00:00
f8737777b1 Fix #1527 - Converting unicode to unicode error in move_storage 2011-02-11 21:58:40 +00:00
e198ea14e4 Fix Create Torrent Dialog Box - Some buttons raise Type Error if no row selected 2011-02-11 21:58:32 +00:00
553f35eae5 Fix #1510 - Cannot create a torrent with only non-zero tier trackers 2011-02-11 21:58:24 +00:00
376a23e6fd Fix #1513: Unhandled Twisted Error in test_listen_port 2011-02-11 21:58:18 +00:00
077f35ec5c use selection input for cancel/apply/ok 2011-02-10 17:54:59 +01:00
00ab9ff499 support offset text/select inputs and select inputs with no message 2011-02-10 17:47:50 +01:00
20302021c4 initial prefs mode. doesn't do anything yet 2011-02-10 17:39:42 +01:00
9e5455793b remove unused code
this breaks command line right now, will put it back in later
2011-02-10 16:45:10 +01:00
7f6a1db89a start with a connection manager instead of just alltorrents 2011-02-10 01:12:14 +01:00
cdcab320fb add current_selection to SelectablePopup 2011-02-10 01:11:31 +01:00
ea22bb0b10 #1514 - Indicator Applet Patch 2011-02-09 19:15:34 +00:00
554f34a261 Fix #690 - Renaming folders does not remove old empty folders 2011-02-08 19:34:54 +00:00
ad2b13eb2c Fix #1248 - Deluge-console unicode support on redirected stdout 2011-02-08 17:36:09 +00:00
ce636ccd57 Catch a possible DivByZero error when moving folders around in fileview tab 2011-02-08 17:36:09 +00:00
053700342a Fix #1336 - Uneeded Horizontal Scrollbar shows in Files&Peers Tab 2011-02-08 17:35:57 +00:00
cd7805bfda make text input not go over width 2011-02-08 14:43:41 +01:00
79869faa53 #1494 - Add Downloaded and Uploaded columns to torrent view 2011-02-08 06:49:30 +00:00
b77f8929d6 oops, add torrent actions new file 2011-02-07 15:10:41 +01:00
e1d8025309 show torrent actions form details when 'a' pressed 2011-02-07 15:10:18 +01:00
183a97785b split off torrent actions popup 2011-02-07 15:01:07 +01:00
f748660cac show status message 2011-02-07 15:00:51 +01:00
cea6c817df Fix #1506 - max speed not restored on a yellow->green transition 2011-02-05 01:12:52 +00:00
b9ff47e10f Fix #755 - Can't set listen_ports through console UI 2011-02-05 01:12:48 +00:00
14746bf94d Fix #1450 Trailing white space in paths 2011-02-05 01:12:47 +00:00
87f871f40a Fix #1500 - Console crashes on command longer than terminal width. This error is raised if the cursor is off screen and is supressed with try-except 2011-02-05 01:12:45 +00:00
9f3ac37f25 Fix #1282 - Text for AutoManaged changed to 'On/Off' and localized 2011-02-05 01:12:44 +00:00
417a9f6e63 Fix #1373, #1386 - Creating and moving non-ascii folder names in MS Windows 2011-02-05 01:12:43 +00:00
ba6389bcac Updated help text for deluge-console on Windows 2011-02-05 01:12:42 +00:00
9bca1a72b1 Fix for deluge-console adding torrent files in Windows 2011-02-05 01:12:41 +00:00
e688b45448 Fix #1508 - TypeError in cell_data_queue() could not convert argument to correct param type 2011-02-05 01:12:36 +00:00
4c54cfedb9 Fix #1507 - Temporary file race condition in core/core.py:add_torrent_url 2011-02-05 01:12:35 +00:00
88039a0eda fix patch, missing deluge.common when adding the options 2011-02-04 19:57:31 +00:00
9a54beef78 add patch from #1473 2011-02-04 19:43:27 +00:00
b0a0574ae0 Apply patch from #1194 2011-02-04 19:37:42 +00:00
db64745862 fix priority popup title 2011-02-02 20:50:48 +01:00
0353a388b3 add option to action popup for torrent details 2011-02-02 20:49:27 +01:00
b41ebe1b89 don't show action popup if there are no torrents in the view 2011-02-02 20:49:11 +01:00
1952357f35 update help 2011-02-02 20:40:15 +01:00
6c8529b3ba updated file seperator 2011-02-02 20:39:57 +01:00
78ea5c9bd3 don't enter torrentdetails if nothing is selected 2011-02-02 18:32:03 +01:00
b35875e300 attempted fix of color/underline issue.
this is a bit of a hack, and seems to work some places, but not everywhere
2011-02-02 16:21:52 +01:00
ad498c6e42 Revert "remove special case white/black pair. doesn't seem needed and breaks white,black,attrs"
This does actually seem to break some terminals

This reverts commit ba3a093746.
2011-02-02 13:57:31 +01:00
d1b3aa54ad support setting file priorities in torrentdetails 2011-02-02 13:46:05 +01:00
d0346a104f don't need len() 2011-02-02 13:45:17 +01:00
5f888faceb handle resize in torrentdetail 2011-02-02 13:11:15 +01:00
f6f3a8e084 allow marking in file view (no actions just yet) 2011-02-02 13:09:41 +01:00
5dcc935852 fix for only drawing one effected line and only draw effected lines on marking 2011-02-02 12:50:40 +01:00
eba7c2bf17 add index value to directories in file_list 2011-02-02 12:44:07 +01:00
00fa074452 small fix for scrolling 2011-02-01 18:00:25 +01:00
5d46d2aee5 add torrentdetails state, allow state switching, move some formating to format_utils 2011-02-01 17:23:15 +01:00
007dd67ea1 only redraw effected lines on scroll. seems to get rid of the flickering problem :) 2011-01-29 14:04:32 +01:00
ff3c3f7148 add torrent can add paused. remove torrent works 2011-01-29 12:29:18 +01:00
68c04acf50 refactor + support selectinput 2011-01-28 22:42:04 +01:00
44676f282a use some caching to speed up row drawing.
still some flicker unfortunatly.  seems to be related to the length of the row line, not sure if there's much i can do there
2011-01-28 17:54:36 +01:00
182ec0cd97 specify hotkeys for filter/action popups 2011-01-28 17:34:03 +01:00
6f0b1fd7f2 support hotkeys in selectable popup 2011-01-28 17:33:51 +01:00
ba3a093746 remove special case white/black pair. doesn't seem needed and breaks white,black,attrs 2011-01-28 17:04:28 +01:00
b7e7a4bc49 Fix typo 2011-01-27 11:18:59 -08:00
ac18ecd1f0 Fix #1498: Use os.path.normpath on new_folder to remove any double slashes or other problems that could be in the string 2011-01-27 11:12:40 -08:00
2f6283ea39 initial checkin of new console ui
pretty alpha code, but it works and gives an idea of the direction the ui might go
2011-01-26 22:18:18 +01:00
b30499c6ac Fix #1484: trying to access the screen object when not using interactive mode 2011-01-16 15:59:29 -08:00
8c12c47d3e Add "none" as a log level to support quieting the logging messages. Refs #1470. 2011-01-02 16:06:28 +00:00
356808b02c Fix #1470. 2011-01-02 16:03:00 +00:00
1f800bf49a On a 64bit platform with old plugins, the deprecation code was unable to find out which was the caller module. This might also happen on other platforms although I was unable to reproduce it on x86. Anyway, handle it cleanly. 2011-01-01 18:33:41 +00:00
d1b4523733 Let log files be a little bigger, 50Mb is not that much anyway. 2010-12-28 01:58:38 +00:00
c00391a852 Merge branch 'master' into plugins-namespace 2010-12-28 01:45:50 +00:00
5841521133 Added missing import.
Removed log message that caused error. It was never logged anyway, even on the master branch before the improved logging branch was created.
2010-12-28 01:26:25 +00:00
7e2411289d Merge branch 'master' into plugins-namespace
Conflicts:
	ChangeLog
2010-12-13 12:37:37 +00:00
2fa8ca6753 Merge branch 'master' into improved-logging
Conflicts:
	ChangeLog
	deluge/plugins/autoadd/autoadd/gtkui.py
	deluge/ui/client.py
2010-12-13 12:31:20 +00:00
1c15df8e00 Merge branch 'master' of deluge-torrent.org:deluge 2010-12-12 17:50:13 +00:00
f282487806 fix bug #1355 2010-12-12 00:03:04 +00:00
078ed6ba71 Fixed "PluginEnabledEvent" and "PluginDisabledEvent" the argument cannot be called name since that should contain the event name.
Ported plugins on deluge's git to this new event dispatching style.
2010-12-11 05:42:45 +00:00
67ea05921c Implemented passing a copy of an event instead of it's arguments to event handlers. Necessary changes to the event handlers on deluge's code were made, plugins still need to be ported to this new style. 2010-12-11 05:11:18 +00:00
0f36a65aaf Update ChangeLog with the backwards incompatible notice. 2010-12-10 05:49:38 +00:00
90d23ce582 Revert e52018bfcd. Simply fail. It's documented on the ChangeLog. 2010-12-10 05:42:19 +00:00
860457ff48 Update ChangeLog. 2010-12-10 05:01:31 +00:00
e52018bfcd Dont make code that still uses the old "TorrentAddedEvent" fail, instead log a warning and make it work. 2010-12-10 04:59:05 +00:00
9bd11ab204 Update ChangeLog. 2010-12-10 04:35:41 +00:00
c164013725 Ported Feeder(although not working on current deluge), Notifications, Scheduler, Stats, Toggle and WebUi to the deluge.plugins namespace. 2010-12-10 04:31:51 +00:00
b9a8bf2409 Ported the Label plugin to the deluge.plugins namespace. 2010-12-10 04:09:25 +00:00
4c3d068f0c Improve create_plugin.py and the generated create_dev_link.sh. 2010-12-10 04:08:36 +00:00
c9e4d286c3 Ported the FreeSpace plugin to the deluge.plugins namespace. 2010-12-10 03:29:36 +00:00
e43146a4ac Simplify some code on create_plugin.py. 2010-12-10 03:25:39 +00:00
1c2eb0c737 Improve and port the create_plugin.py script to generate the plugins under the "deluge.plugins" namespace.
Any plugin not using the "deluge.plugins" namespace will get a `DeprecationWarning` printed on the console.
2010-12-10 03:15:36 +00:00
b0d77a4f20 Ported the Extractor plugin to the plugins namespace. 2010-12-10 00:35:51 +00:00
20635773b3 Ported "Blocklist", "Example" and "Execute" to the "deluge.plugins" namespace. 2010-12-09 23:11:20 +00:00
f17634ea63 Drop pkutil and simply use pkg_resources. 2010-12-09 22:18:59 +00:00
16f617d240 Initial commit to implement the "deluge.plugins" namespace package support. 2010-12-09 22:05:34 +00:00
1c7676bfe5 Finish last commit, forgot to revert some of the files. 2010-12-09 18:12:25 +00:00
63fa5bf85b Reuse existing "TorrentAddedEvent" instead of creating a new one. 2010-12-09 18:08:34 +00:00
6cefb49f28 Diferentiate adding an already managed torrent from adding a new, unmanaged torrent, to the session. 2010-12-08 19:33:05 +00:00
eeed72a977 Fix tweak_logging_levels(). 2010-12-07 14:21:45 +00:00
49e10ea0cf Fix bug introduced in previous commit. 2010-12-06 12:34:57 +00:00
26e45dcbc8 Make use of logging.handlers.WatchedFileHandler if deluge is running on python 2.6. 2010-12-06 11:59:37 +00:00
b7bc1fdb1d configmanager needs to be imported at a latter stage at least for the gtk frontend. 2010-12-06 11:45:17 +00:00
3b00a7de59 Swhiched the old style logging, ie, a single logger for all logging output to several loggers. This brings the ability to tweak the logging levels for each of the loggers, ie, we can have the "deluge" logger be at the ERROR level while having "deluge.core" at the DEBUG level, meaning we will only see log message for all of the deluge loggers above the ERROR level while still having all messages above the DEBUG level for the "deluge.core" logger and it's children. This kind of tweak can be achieved by adding a file named "logging.conf" to deluge's config dir and this is explained on the deluge.log.tweak_logging_levels function.
Passing `-r` to the cli's while also passing `-l` will make the logfile rotate when reaching 5Mb in size. Three backups will be kept at all times.
All deluge's code is now using this new style logging along with the git hosted plugins. For other plugins not hosted by deluge, which still imports `LOG` as the logger, a deprecation warning will be shown explaining the required changes needed to use the new style logging. New plugins created by the `create_plugin` script will use the new logging facilities.
2010-12-06 11:20:22 +00:00
14ec9464aa fix a bug that can occur when upgrading 1.1 config files 2010-12-01 10:21:26 +00:00
3d64f0d8da Fix #1283 Consistent icons and add localization to file priorities
Signed-off-by: John Garland <johnnybg+deluge@gmail.com>
2010-11-11 14:01:58 +11:00
87e3a5f515 rebuild the compressed javascript 2010-11-01 09:14:23 +00:00
75e9ff57de update the build file to include the spinnerfieldfix file 2010-11-01 09:13:37 +00:00
b180d2a900 remove the convert conf script that won't actually work anymore 2010-10-31 14:35:00 +00:00
1822c2bde9 fix a silly bug 2010-10-31 10:13:33 +00:00
40e6777c48 Merge branch 'master' of deluge-torrent.org:deluge 2010-10-31 09:18:26 +00:00
f88b24d507 apply 41ffee5 to master 2010-10-31 09:18:09 +00:00
593452ed63 update bbfreeze script 2010-10-29 10:11:40 +01:00
4197e129fe change location of httpdownloader as a temporary fix 2010-10-25 00:13:09 +01:00
0360cbe0b8 fix a bug in the MultiOptionsManager that didn't fire the right arguments in the initial event fire 2010-10-24 23:42:29 +01:00
d2f41fe7e5 apply patch from #1377 2010-10-24 13:30:40 +01:00
0a2e9a5324 include a file that fixes the SpinnerField onBlur method (no idea why it is set to emptyFn) 2010-10-23 22:22:00 +01:00
3c302088f6 fix the path to the loading gif (not that its actually used) 2010-10-23 21:24:06 +01:00
3b6bad2f13 fix up the tracker icon tests 2010-10-22 19:47:38 +01:00
6fd4b298f3 fix up the core tests that were erroring 2010-10-22 19:42:07 +01:00
2beec764c9 move tests back inside the deluge package, makes it easier to run trial on them 2010-10-22 16:36:52 +01:00
e5760ee341 Fix uncaught exception when quitting 2010-10-23 01:12:21 +11:00
45940b9064 Fix #1373 use of cyrllic paths 2010-10-16 12:56:00 -07:00
c97f809bdc Fix #1349 force a theme style with expander-size = 15 to show entries in the sidebar properly. 2010-10-15 19:32:13 -07:00
ae6837c88c create the toolbar with the rest of the UI 2010-10-14 13:54:26 +01:00
a827cf6c7a Keep a torrent paused after a forced recheck if it was paused to start. 2010-10-10 12:36:25 -07:00
f52e3c4aa0 add a check to ensure that the events loop doesn't continue indefinitely 2010-10-10 19:52:08 +01:00
8f7e307f33 wrap client.disconnect() call with a check to see if its classic mode 2010-10-07 00:14:21 +01:00
f1f6f137c3 Make sure config value strings are utf8 encoded (fixes #1369) 2010-10-03 18:33:57 +11:00
ff7ff8eac7 Move decode_string/utf8_encoded to common 2010-10-03 18:12:42 +11:00
4cb2bcae25 Fix sidebar not updating (#1365) 2010-10-02 23:55:51 +10:00
df95222849 Use better attribute / method names in blocklist 2010-09-26 11:39:17 +10:00
463fd3ac04 Fix attribute error in blocklist plugin 2010-09-26 11:30:58 +10:00
eb37c91866 Set locale to the user's default settings in the gtkui 2010-09-20 02:43:07 +10:00
7cd210a59b include missing theme images 2010-09-18 00:47:47 +10:00
eee27868a8 include the .order files 2010-09-16 12:16:07 +01:00
e5dec3f020 add all the other scripts to package_data 2010-09-16 08:52:16 +01:00
def1127c78 More clean-up of setup.py 2010-09-14 11:29:47 -07:00
847f2c2ebd Remove the custom 'install' class and include_package_data 2010-09-14 10:56:42 -07:00
fb49aa02a8 Fix preference page index when removing a preference page 2010-09-13 18:21:31 -07:00
f8dc66b773 Fix bugs with unicode torrents in AutoAdd plugin. 2010-09-13 02:23:10 -04:00
c17b466bae Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled. 2010-09-13 02:23:09 -04:00
d9cdff9525 Increase max piece size to 8 MiB in create torrent dialog (closes #1358) 2010-09-13 08:50:29 +10:00
915db80a55 Fix VersionSplit behavior when comparing to a dev version. 2010-09-11 16:27:29 -04:00
350d4d7260 Add rpc to check if authorized to call a rpc: daemon.authorized_call() 2010-09-04 12:31:27 -07:00
4b92912577 AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception. 2010-09-03 22:34:28 -04:00
a794223d96 Fix "adjustment with non-zero page size" deprication warning in autoadd plugin. 2010-09-03 22:34:27 -04:00
5811d372f9 Fix up some docstrings 2010-09-03 17:21:56 -07:00
0b2f2f2c8a Add TorrentFileCompleted event. 2010-09-03 17:11:37 -07:00
64022d7bc7 Add 'Owner' filter 2010-09-03 15:15:29 -07:00
bf715d90fd Save 'owner' and 'public' to the torrent state 2010-09-03 15:15:02 -07:00
fdbd9e6687 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.
2010-09-03 14:28:16 -07:00
0a0383d075 Use a temp filename with add_torrent_url 2010-08-31 00:04:56 +10:00
5b1bed5a48 Update get_free_space test 2010-08-30 23:49:49 +10:00
7ed33192ec Ensure preferencesmanager only changes intended libtorrent session settings. 2010-08-26 01:31:53 -04:00
c82ba44be8 Fix scheduler so that it keeps current state, even after global settings change. 2010-08-26 01:22:14 -04:00
729daf331c Ignore global stop ratio related settings in logic, so per torrent ones are used. 2010-08-24 23:16:33 -04:00
db1835d942 Merge branch 'newscheduler' 2010-08-24 00:57:15 -04:00
7d4a316733 Add max active downloading and seeding options to scheduler. 2010-08-24 00:30:54 -04:00
da8629db97 Implemented search as you type capabilities to the treeview, ie, when the
treeview has focus and user starts typing, select the first matching
torrent name.
2010-08-24 01:38:05 +01:00
df573c66c6 Merge branch 'master' of deluge-torrent.org:deluge 2010-08-23 17:35:42 -07:00
29f61b58fb Fix key error after enabling a plugin that introduces a new status key 2010-08-23 17:34:30 -07:00
15ce2b71f9 Moved xdg import so it is not called on Windows, where it is unused. fixes #1343 2010-08-22 15:39:12 -04:00
116ccc21fd AutoAdd plugin changes
adds queue to top option
adds ability to append extension instead of deleting torrent once added
2010-08-22 00:31:41 -04:00
dee33745c8 Merge branch 'master' into multiuser 2010-08-21 13:00:47 -07:00
8586cda4e0 Fix unhandled exception when adding a torrent to the session 2010-08-21 12:53:44 -07:00
db9b5580d7 Fix issue where the save_timer is cancelled when it's not active 2010-08-21 12:53:21 -07:00
10aebd600a Add 'Owner' and 'Public' fields to the details tab 2010-08-21 12:42:01 -07:00
f0fe3c7879 Add 'Owner' and 'Public' columns to the TorrentView 2010-08-21 12:28:26 -07:00
33fd852bda Add 'public' torrent option to allow making a torrent publically viewable by other users 2010-08-21 12:25:05 -07:00
65c9dc5fa8 Add new torrent status key 'owner' for keeping track of who added the torrent to the session 2010-08-21 12:15:41 -07:00
7e2eea46d3 Fix man deluged not showing '-u' on its own line 2010-08-20 01:15:27 +10:00
01773e433f Fix #1341 issue where Config would try to cancel the save_timer when it is None. 2010-08-18 12:33:09 -07:00
ca5eaf4270 Add cache expiry check by key update times to fix issue where some status updates would not return
correctly if they were done < cache_time from the previous status request
2010-08-18 12:15:53 -07:00
7d64f057c7 Add additional test for get_torrents_status and fix the other one to properly invalidate the cache
time from startup before proceeding
2010-08-18 12:15:16 -07:00
48d016e97d Add test to demonstrate flaw in SessionProxy design. Need to keep track of update times for each
status key individually to fix this.
2010-08-18 11:06:10 -07:00
e9ce506d1c fix the script resource on windows 2010-08-14 17:38:30 +01:00
e0eb0bd06a add the apple iOS bookmark icons from #1339 2010-08-14 16:16:10 +01:00
9f992ec40d fix the system.listMethods json call when running in classic mode 2010-08-14 16:05:40 +01:00
ce8ef4f95b Add test suite for SessionProxy 2010-08-10 09:57:00 -07:00
4d0560eff2 Fix getting a torrent's status with an empty key list to return all the
torrent's status keys instead of an empty dict
2010-08-06 17:28:16 -07:00
d49cde1994 use the get_libtorrent.sh script to get libtorrent if it is missing 2010-07-22 21:13:26 +01:00
16a1173f1d change default version to 0.15 2010-07-22 18:18:15 +01:00
333d2f5562 add libtorrent fetch script 2010-07-22 18:17:51 +01:00
c7fe1bdef5 remove the libtorrent submodule 2010-07-22 18:05:46 +01:00
46a967fb8c a couple of fixes to stop the webui crashing when running within the gtkui 2010-07-18 23:11:02 +01:00
ca22e84858 Alternate tooltip when toggling session 2010-07-18 01:17:33 +10:00
20bd962e6a Add Toggle plugin 2010-07-18 00:56:56 +10:00
22a1448372 Only use an icon if it passes some sanity checks 2010-07-17 17:11:19 +10:00
722a5cd9e1 Use a blank icon when the tracker icon downloaded isn't a proper image 2010-07-15 19:17:17 -07:00
efecf38bcd Attempt to create a move_storage destination path if it doesn't exist 2010-07-15 10:51:06 -07:00
dfb75d67b9 Do not attempt to move a torrents storage if the destination path does
not exist
2010-07-12 14:45:13 -07:00
961d405921 Try to import system rencode before deluge.rencode to allow the use of the new rencode library at: http://code.google.com/p/rencode/ 2010-07-08 16:39:02 -07:00
e025b6b9db Fix hang when quitting in classic mode 2010-07-08 16:23:12 -07:00
bc5aa1bf71 Add logging the user when a torrent is added or removed 2010-07-05 21:08:15 -07:00
3cd30ea96a Use torrent state name instead of number if available 2010-07-02 18:09:21 -07:00
504751424f Use basestring instead of str and unicode 2010-07-02 15:19:42 +10:00
37a00a48a7 Fix uncaught exception when closing deluge in classic mode 2010-07-02 02:45:47 +10:00
2a2f5d90ae Fix typo 2010-07-02 02:43:32 +10:00
f0920f5638 more improvements to the shift select 2010-07-01 14:21:37 +01:00
43fb998651 fix select 'upwards' 2010-07-01 14:08:21 +01:00
148fcdbe37 allow for shift selecting in tree grids 2010-07-01 13:45:23 +01:00
de79bba540 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.
2010-06-22 18:24:27 -07:00
d5881142aa Always look for -mt boost libraries first 2010-06-18 09:50:58 -07:00
494c468da8 fix typo 2010-06-12 22:47:59 +01:00
672668ccdb change bits to bytes, thanks to charles 2010-06-11 15:59:33 +01:00
538aed9147 Fix typo in label plugin - thanks konti 2010-06-11 00:49:42 +10:00
d800273891 Handle os.remove failing on windows 2010-06-08 03:19:30 +10:00
94f96c5165 Python independent version of previous commit 2010-06-08 01:55:48 +10:00
4b1d60c727 Fix console ui not liking paths with backslashes on windows (#1293) 2010-06-08 01:25:21 +10:00
cfe547b31a Print a more informative error message if the torrent file doesn't exist 2010-06-08 01:22:19 +10:00
f6195f775f Fix execute plugin only executing last event (#1306) 2010-06-08 00:17:22 +10:00
87879ab3b8 Only encode if necessary 2010-06-07 20:12:31 +10:00
81a837faed Fix unicode support in console ui (#1307) 2010-06-07 20:11:11 +10:00
c06f905702 Add some debug logging statements 2010-06-04 18:05:21 +01:00
f6f9e0234a Fix an error in the key 2010-06-04 17:37:53 +01:00
e1e1472a8f Fix saving the correct event name 2010-06-04 16:37:37 +01:00
b7e1fe1696 Save the execute config after adding/removing/saving commands 2010-06-04 16:31:51 +01:00
0314d0440f Fix typo in execute plugin 2010-05-20 00:04:43 +10:00
3226b1819d Fix man deluged not showing '-d' on its own line 2010-05-16 22:32:12 +10:00
4b8a85763c Fix remote save path dialog not disappearing after creating a torrent 2010-05-16 18:10:13 +10:00
ae4f2c3bb0 Fix only being able to click "remote path" once when creating a torrent 2010-05-16 18:08:33 +10:00
bc28b83062 Fix deluged crashing on windows when logfile's directory doesn't exist 2010-05-16 13:11:18 +10:00
2603c36e7d Revert "Fix trac wiki turning CamelCase words into broken links"
This reverts commit 81b56cce62.
2010-05-11 23:47:06 +10:00
81b56cce62 Fix trac wiki turning CamelCase words into broken links 2010-05-11 23:23:39 +10:00
649a2b6f8e Update email address and copyright 2010-05-11 03:51:45 +10:00
4caf81ef89 Fix new release check pref 2010-05-09 22:45:28 -07:00
65c33a37a1 Fix the naming of the some of the config set methods 2010-05-09 22:36:31 -07:00
eff17931eb Fix preferences manager not setting initial settings on start-up 2010-05-09 22:32:14 -07:00
b33c2abf82 Fix label plugin not remembering newly created labels 2010-05-09 17:50:39 +10:00
ba514f0b0e Remove unused code from label plugin 2010-05-09 17:39:45 +10:00
7f60867ae9 Update docstrings to use names from previous commit 2010-05-09 17:05:22 +10:00
71d8836118 Use better names for TrackerIcons' args 2010-05-09 17:01:15 +10:00
97d6f8ce80 Return the noIcon for empty strings as well 2010-05-09 16:43:22 +10:00
ca7f009e74 Raise IconsError instead of IndexError (fixes infinite looping) 2010-05-09 16:31:32 +10:00
f08e5176c3 Do not request a tracker icon if the host is "" 2010-05-08 20:06:15 -07:00
70161a54fa Simplify PreferencesManager by simply looking for the _on_get_<config key> method instead of registering a set function for each 2010-05-08 20:04:55 -07:00
a945d0a78d Use previously defined host variable instead of getting the tracker host from the TreeModel 2010-05-08 20:02:58 -07:00
245b799ccf Add test for tracker_icons for when requesting an icon for host that is "". This test results in an infinite loop. 2010-05-08 20:01:46 -07:00
0dc6c3ecfd Return 0 in get_free_space if the download_location is invalid 2010-05-08 20:00:41 -07:00
98f000cc70 rebuild deluge-all and ext-extensions 2010-05-08 16:18:48 +01:00
8d4daff068 update the build files for deluge-all and ext-extensions 2010-05-08 16:18:48 +01:00
79d68a5b9b fix the null comparison 2010-05-08 16:18:48 +01:00
412d0ee4f9 set the baseCls for the add label form panel to x-plain 2010-05-08 16:18:47 +01:00
e8788bde08 Make host_to_url support redirection and add another test 2010-05-08 16:25:16 +10:00
815a71fe8b Try favicon.ico if there's a HTMLParseError 2010-05-08 15:50:04 +10:00
fce16ba51f Fix relative redirecting in blocklist plugin 2010-05-06 23:27:04 +10:00
50cfd9c9b1 Update tests 2010-05-06 23:06:53 +10:00
369b03bffb Update version 2010-05-05 14:46:27 -07:00
2233 changed files with 533379 additions and 253212 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ dist
*.pyc
*.tar.*
_trial_temp
deluge/i18n/*/
*.desktop

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "libtorrent"]
path = libtorrent
url = git://deluge-torrent.org/libtorrent

View File

@ -1,4 +1,13 @@
=== Deluge 1.3.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.
==== Core ====
* Implement #1063 option to delete torrent file copy on torrent removal - patch from Ghent
* Implement #457 progress bars for folders
@ -7,16 +16,39 @@
* #1112: Fix renaming files in add torrent dialog
* #1247: Fix deluge-gtk from hanging on shutdown
* #995: Rewrote tracker_icons
* 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
==== 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.
==== WebUI ====
* Migrate to ExtJS 3.1
* Add gzip compression of HTTP data to the server
* Improve the efficiency of the TorrentGrid
==== Blocklist ====
* Implement local blocklist support
* #861: Pause transfers until blocklist is imported
==== Web ====
* Migrate to ExtJS 3.1
* Add gzip compression of HTTP data to the server
* Improve the efficiency of the TorrentGrid
=== Deluge 1.2.0 - "Bursting like an infected kidney" (10 January 2010) ===
==== Core ====
* Implement new RPC protocol DelugeRPC replacing XMLRPC

View File

@ -6,7 +6,9 @@
* simplejson (if python < 2.6)
* setuptools
* gettext
* intltool
* pyxdg
* chardet
* geoip-database (optional)
* libtorrent >= 0.14, or build the included version
@ -16,9 +18,6 @@
* openssl
* zlib
=== UIs ===
* chardet
=== Gtk ===
* python-notify (libnotify python wrapper)
* pygame

4
README
View File

@ -15,9 +15,9 @@ For past developers and contributers see: http://dev.deluge-torrent.org/wiki/Abo
License
==========================
Deluge is under the GNU GPLv3 license.
Icon data/pixmaps/deluge.svg and derivatives in data/icons are copyright
Icon ui/data/pixmaps/deluge.svg and derivatives in ui/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
All other icons in ui/data/pixmaps are copyright Andrew Resch and are under
the GNU GPLv3.
==========================

21
check_glade.sh Executable file
View File

@ -0,0 +1,21 @@
#!/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 Normal file → Executable file
View File

@ -1,17 +1,26 @@
#!/usr/bin/env python
import os
import re
import sys
# Paths to exclude
EXCLUSIONS = [
"deluge/scripts"
"deluge/scripts",
"deluge/i18n",
]
POTFILE_IN = "deluge/i18n/POTFILES.in"
print "Creating " + POTFILE_IN + " .."
pattern = "deluge\/plugins\/.*\/build"
compiled = re.compile(pattern)
sys.stdout.write("Creating " + POTFILE_IN + " ... ")
sys.stdout.flush()
to_translate = []
for (dirpath, dirnames, filenames) in os.walk("deluge"):
for filename in filenames:
if os.path.splitext(filename)[1] in (".py", ".glade") and dirpath not in EXCLUSIONS:
if os.path.splitext(filename)[1] in (".py", ".glade", ".in") \
and dirpath not in EXCLUSIONS \
and not compiled.match(dirpath):
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/data/\
for size in 16 22 24 32 36 48 64 72 96 128 192 256; do mkdir -p deluge/ui/data/\
icons/hicolor/${size}x${size}/apps; rsvg-convert -w ${size} -h ${size} \
-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
-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

View File

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

View File

@ -40,13 +40,16 @@ import os
import time
import subprocess
import platform
import sys
import chardet
import logging
try:
import json
except ImportError:
import simplejson as json
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"):
@ -63,26 +66,9 @@ if not hasattr(json, "dumps"):
json.load = load
import pkg_resources
import xdg, xdg.BaseDirectory
import gettext
import locale
# Initialize gettext
try:
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:
from deluge.log import LOG as log
log.error("Unable to initialize gettext/locale!")
log.exception(e)
import __builtin__
__builtin__.__dict__["_"] = lambda x: x
from deluge.error import *
LT_TORRENT_STATE = {
@ -119,11 +105,12 @@ FILE_PRIORITY = {
0: "Do Not Download",
1: "Normal Priority",
2: "High Priority",
5: "Highest Priority",
5: "High Priority",
7: "Highest Priority",
"Do Not Download": 0,
"Normal Priority": 1,
"High Priority": 2,
"Highest Priority": 5
"High Priority": 5,
"Highest Priority": 7
}
def get_version():
@ -150,6 +137,7 @@ def get_default_config_dir(filename=None):
else:
return os.path.join(os.environ.get("APPDATA"), "deluge")
else:
import xdg.BaseDirectory
if filename:
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
else:
@ -164,6 +152,18 @@ def get_default_download_dir():
if windows_check():
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():
@ -198,7 +198,7 @@ def osx_check():
def get_pixmap(fname):
"""
Provides easy access to files in the deluge/data/pixmaps folder within the Deluge egg
Provides easy access to files in the deluge/ui/data/pixmaps folder within the Deluge egg
:param fname: the filename to look for
:type fname: string
@ -206,8 +206,18 @@ def get_pixmap(fname):
:rtype: string
"""
return pkg_resources.resource_filename("deluge", os.path.join("data", \
"pixmaps", fname))
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]))
)
def open_file(path):
"""
@ -291,7 +301,14 @@ def fspeed(bps):
'42.1 KiB/s'
"""
return '%s/s' % (fsize(bps))
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"))
def fpeer(num_peers, total_peers):
"""
@ -474,7 +491,7 @@ def free_space(path):
sectors, bytes, free, total = map(long, win32file.GetDiskFreeSpace(path))
return (free * sectors * bytes)
else:
disk_data = os.statvfs(path)
disk_data = os.statvfs(path.encode("utf8"))
block_size = disk_data.f_bsize
return disk_data.f_bavail * block_size
@ -526,7 +543,7 @@ def path_join(*parts):
path += '/' + part
return path
XML_ESCAPES = (
XML_ESCAPES = (
('&', '&amp;'),
('<', '&lt;'),
('>', '&gt;'),
@ -535,9 +552,9 @@ XML_ESCAPES = (
)
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.
@ -548,9 +565,9 @@ def xml_decode(string):
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.
@ -560,6 +577,41 @@ def xml_encode(string):
string = string.replace(char, escape)
return string
def decode_string(s, encoding="utf8"):
"""
Decodes a string and re-encodes it in utf8. If it cannot decode using
`:param:encoding` then it will try to detect the string encoding and
decode it.
:param s: string to decode
:type s: string
:keyword encoding: the encoding to use in the decoding
:type encoding: string
"""
try:
s = s.decode(encoding).encode("utf8", "ignore")
except UnicodeDecodeError:
s = s.decode(chardet.detect(s)["encoding"], "ignore").encode("utf8", "ignore")
return s
def utf8_encoded(s):
"""
Returns a utf8 encoded string of s
:param s: (unicode) string to (re-)encode
:type s: basestring
:returns: a utf8 encoded string of s
:rtype: str
"""
if isinstance(s, str):
s = decode_string(s)
elif isinstance(s, unicode):
s = s.encode("utf8", "ignore")
return s
class VersionSplit(object):
"""
Used for comparing version numbers.
@ -570,13 +622,15 @@ class VersionSplit(object):
"""
def __init__(self, ver):
ver = ver.lower()
vs = ver.split("_") if "_" in ver else ver.split("-")
vs = ver.replace("_", "-").split("-")
self.version = [int(x) for x in vs[0].split(".")]
self.suffix = None
self.dev = False
if len(vs) > 1:
for s in ("rc", "alpha", "beta", "dev"):
if s in vs[1][:len(s)]:
self.suffix = vs[1]
if vs[1].startswith(("rc", "alpha", "beta")):
self.suffix = vs[1]
if vs[-1] == 'dev':
self.dev = True
def __cmp__(self, ver):
"""
@ -587,19 +641,74 @@ class VersionSplit(object):
"""
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)
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
# 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

View File

@ -33,9 +33,11 @@
#
#
import logging
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
@ -96,6 +98,10 @@ class Component(object):
self._component_stopping_deferred = None
_ComponentRegistry.register(self)
def __del__(self):
if _ComponentRegistry:
_ComponentRegistry.deregister(self)
def _component_start_timer(self):
if hasattr(self, "update"):
self._component_timer = LoopingCall(self.update)
@ -139,11 +145,18 @@ class Component(object):
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)
@ -219,22 +232,22 @@ class ComponentRegistry(object):
self.components[obj._component_name] = obj
def deregister(self, name):
def deregister(self, obj):
"""
Deregisters a component from the registry. A stop will be
issued to the component prior to deregistering it.
:param name: the name of the component
:type name: string
:param obj: the Component object
:type obj: object
"""
if name in self.components:
log.debug("Deregistering Component: %s", name)
d = self.stop([name])
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, name)
return d.addCallback(on_stop, obj._component_name)
else:
return succeed(None)

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,14 +68,16 @@ 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
@ -93,13 +95,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
@ -119,8 +121,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
@ -146,7 +148,8 @@ class Config(object):
self._save_timer = None
if defaults:
self.__config = dict(defaults)
for key, value in defaults.iteritems():
self.set_item(key, value)
# Load the config from file in the config_dir
if config_dir:
@ -187,6 +190,10 @@ 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))
@ -200,7 +207,10 @@ 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:
value = oldtype(value)
if oldtype == unicode:
value = oldtype(value, "utf8")
else:
value = oldtype(value)
except ValueError:
log.warning("Type '%s' invalid for '%s'", newtype, key)
raise
@ -250,7 +260,38 @@ what is currently in the config and it could not convert the value
5
"""
return self.__config[key]
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)
def register_change_callback(self, callback):
"""
@ -348,21 +389,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]
@ -371,8 +412,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)
@ -396,26 +437,24 @@ 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
self._save_timer.cancel()
return
except Exception, e:
log.warning("Unable to open config file: %s", filename)
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return True
except IOError, e:
log.warning("Unable to open config file: %s because: %s", filename, e)
# 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 Exception, e:
except IOError, e:
log.error("Error writing new config file: %s", e)
return False

View File

@ -34,11 +34,14 @@
#
import os
import logging
import deluge.common
from deluge.log import LOG as log
import deluge.log
from deluge.config import Config
log = logging.getLogger(__name__)
class _ConfigManager:
def __init__(self):
log.debug("ConfigManager started..")
@ -52,7 +55,6 @@ class _ConfigManager:
return self.__config_directory
def __del__(self):
log.debug("ConfigManager stopping..")
del self.config_files
def set_config_dir(self, directory):
@ -86,6 +88,7 @@ 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,12 +41,13 @@ 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.log import LOG as log
log = logging.getLogger(__name__)
class AlertManager(component.Component):
def __init__(self):

View File

@ -2,6 +2,7 @@
# authmanager.py
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -36,27 +37,56 @@
import os
import random
import stat
import shutil
import logging
import deluge.component as component
import deluge.configmanager as configmanager
import deluge.error
from deluge.common import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE, AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY, AUTH_LEVEL_DEFAULT,
create_localclient_account)
from deluge.log import LOG as log
from deluge.error import AuthManagerError, AuthenticationRequired, BadLoginError
AUTH_LEVEL_NONE = 0
AUTH_LEVEL_READONLY = 1
AUTH_LEVEL_NORMAL = 5
AUTH_LEVEL_ADMIN = 10
log = logging.getLogger(__name__)
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
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__)
class BadLoginError(deluge.error.DelugeError):
pass
class AuthManager(component.Component):
def __init__(self):
component.Component.__init__(self, "AuthManager")
component.Component.__init__(self, "AuthManager", interval=10)
self.__auth = {}
self.__auth_modification_time = None
def start(self):
self.__load_auth_file()
@ -67,6 +97,19 @@ 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
@ -76,49 +119,121 @@ class AuthManager(component.Component):
:returns: int, the auth level for this user
:rtype: int
:raises BadLoginError: if the username does not exist or password does not match
:raises AuthenticationRequired: if aditional details are required to
authenticate.
: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")
raise BadLoginError("Username does not exist", username)
if self.__auth[username][0] == password:
if self.__auth[username].password == password:
# Return the users auth level
return int(self.__auth[username][1])
return self.__auth[username].authlevel
elif not password and self.__auth[username].password:
raise AuthenticationRequired("Password is required", username)
else:
raise BadLoginError("Password does not match")
raise BadLoginError("Password does not match", username)
def __create_localclient_account(self):
def has_account(self, username):
return username in self.__auth
def get_known_accounts(self):
"""
Returns the string.
Returns a list of known deluge usernames.
"""
# We create a 'localclient' account with a random password
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)
try:
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"
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 __load_auth_file(self):
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
localclient = self.__create_localclient_account()
fd = open(auth_file, "w")
fd.write(localclient)
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()
# 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()
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()
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()
for line in f:
if line.startswith("#"):
@ -132,15 +247,43 @@ 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)
level = AUTH_LEVEL_DEFAULT
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
elif len(lsplit) == 3:
username, password, level = lsplit
username, password, authlevel = 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
self.__auth[username.strip()] = (password.strip(), level)
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)
if "localclient" not in self.__auth:
open(auth_file, "a").write(self.__create_localclient_account())
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

View File

@ -1,135 +0,0 @@
#
# 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":
try:
filepath = os.path.join(self.config["autoadd_location"], filename)
except UnicodeDecodeError, e:
log.error("Unable to auto add torrent due to inproper filename encoding: %s", e)
continue
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,6 +2,7 @@
# core.py
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -38,36 +39,34 @@ from deluge._libtorrent import lt
import os
import glob
import base64
import shutil
import logging
import threading
import pkg_resources
import warnings
import tempfile
from urlparse import urljoin
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..")
@ -77,7 +76,8 @@ class Core(component.Component):
log.info("Starting libtorrent %s session..", lt.version)
# Create the client fingerprint
version = [int(value.split("-")[0]) for value in deluge.common.get_version().split(".")]
version = [int(value.split("-")[0]) for value in
deluge.common.get_version().split(".")]
while len(version) < 4:
version.append(0)
@ -88,10 +88,17 @@ class Core(component.Component):
# Set the user agent
self.settings = lt.session_settings()
self.settings.user_agent = "Deluge %s" % deluge.common.get_version()
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] }
# 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
@ -106,7 +113,6 @@ 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
@ -114,6 +120,8 @@ class Core(component.Component):
# Get the core config
self.config = deluge.configmanager.ConfigManager("core.conf")
self.config.run_converter((0, 1), 2, self.__migrate_config_1_to_2)
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
@ -147,19 +155,25 @@ class Core(component.Component):
def __save_session_state(self):
"""Saves the libtorrent session state"""
try:
open(deluge.configmanager.get_config_dir("session.state"), "wb").write(
lt.bencode(self.session.state()))
session_state = deluge.configmanager.get_config_dir("session.state")
open(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:
self.session.load_state(lt.bdecode(
open(deluge.configmanager.get_config_dir("session.state"), "rb").read()))
session_state = deluge.configmanager.get_config_dir("session.state")
self.session.load_state(lt.bdecode(open(session_state, "rb").read()))
except Exception, e:
log.warning("Failed to load lt state: %s", e)
def __migrate_config_1_to_2(self, config):
if 'sequential_download' not in config:
config['sequential_download'] = False
return config
def save_dht_state(self):
"""Saves the dht state to a file"""
try:
@ -212,7 +226,9 @@ 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)
@ -236,20 +252,44 @@ 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_get_file(filename):
def on_download_success(filename):
# We got the file, so add it to the session
data = open(filename, "rb").read()
return self.add_torrent_file(filename, base64.encodestring(data), options)
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
)
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
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
d = download_file(url, url.split("/")[-1], headers=headers)
d.addCallback(on_get_file)
d.addErrback(on_get_file_error)
d = download_file(
url, tempfile.mkstemp()[1], headers=headers, force_filename=True
)
d.addCallbacks(on_download_success, on_download_fail)
return d
@export
@ -387,7 +427,11 @@ class Core(component.Component):
@export
def get_torrent_status(self, torrent_id, keys, diff=False):
# Build the status dictionary
status = self.torrentmanager[torrent_id].get_status(keys, diff)
try:
status = self.torrentmanager[torrent_id].get_status(keys, diff)
except KeyError:
# Torrent was probaly removed meanwhile
return {}
# Get the leftover fields and ask the plugin manager to fill them
leftover_fields = list(set(keys) - set(status.keys()))
@ -454,7 +498,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], unicode) or isinstance(config[key], str):
if isinstance(config[key], basestring):
config[key] = config[key].encode("utf8")
self.config[key] = config[key]
@ -535,6 +579,11 @@ 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"""
@ -565,6 +614,32 @@ 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})
@export
def get_path_size(self, path):
"""Returns the size of the file or folder 'path' and -1 if the path is
@ -747,7 +822,11 @@ class Core(component.Component):
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
@ -768,7 +847,10 @@ class Core(component.Component):
"""
if not path:
path = self.config["download_location"]
return deluge.common.free_space(path)
try:
return deluge.common.free_space(path)
except InvalidPathError:
return 0
@export
def get_libtorrent_version(self):
@ -780,3 +862,23 @@ 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,9 +33,7 @@
#
import os
import gettext
import locale
import pkg_resources
import logging
from twisted.internet import reactor
import twisted.internet.error
@ -43,16 +41,19 @@ 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:
@ -91,22 +92,10 @@ class Daemon(object):
else:
# This is a deluged!
s.close()
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
raise deluge.error.DaemonRunningError(
"There is a deluge daemon running with this config "
"directory!"
)
# Twisted catches signals to terminate, so just have it call the shutdown
# method.
@ -120,7 +109,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)
@ -189,18 +178,24 @@ class Daemon(object):
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):
"""
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,8 +33,10 @@
#
#
import logging
import deluge.component as component
from deluge.log import LOG as log
log = logging.getLogger(__name__)
class EventManager(component.Component):
def __init__(self):
@ -53,7 +55,10 @@ 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)
handler(*event.args)
try:
handler(*event.args)
except:
log.error("Event handler %s failed in %s", event.name, handler)
def register_event_handler(self, event, handler):
"""

View File

@ -33,12 +33,13 @@
#
#
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.
@ -77,6 +78,27 @@ 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")
@ -91,7 +113,7 @@ def tracker_error_filter(torrent_ids, values):
# Check all the torrent's tracker_status for 'Error:' and only return torrent_ids
# that have this substring in their tracker_status
for torrent_id in torrent_ids:
if "Error:" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
if _("Error") + ":" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
@ -107,6 +129,7 @@ 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)
@ -116,6 +139,10 @@ 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.
@ -131,7 +158,7 @@ class FilterManager(component.Component):
if "id"in filter_dict: #optimized filter for id:
torrent_ids = filter_dict["id"]
torrent_ids = list(filter_dict["id"])
del filter_dict["id"]
else:
torrent_ids = self.torrents.get_torrent_list()
@ -196,9 +223,8 @@ class FilterManager(component.Component):
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
items["tracker_host"]["All"] = len(torrent_ids)
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:

View File

@ -39,12 +39,14 @@ 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
from deluge.log import LOG as log
log = logging.getLogger(__name__)
#start : http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
def makeFakeClass(module, name):

View File

@ -36,13 +36,13 @@
"""PluginManager for Core"""
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
import logging
from deluge.event import PluginEnabledEvent, PluginDisabledEvent
import deluge.pluginmanagerbase
import deluge.component as component
from deluge.log import LOG as log
log = logging.getLogger(__name__)
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
component.Component):

View File

@ -1,7 +1,7 @@
#
# preferencesmanager.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2008-2010 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -34,10 +34,9 @@
#
import os.path
import os
import logging
import threading
import pkg_resources
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from deluge._libtorrent import lt
@ -46,7 +45,8 @@ from deluge.event import *
import deluge.configmanager
import deluge.common
import deluge.component as component
from deluge.log import LOG as log
log = logging.getLogger(__name__)
DEFAULT_PREFS = {
"send_info": False,
@ -62,6 +62,7 @@ DEFAULT_PREFS = {
"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,
@ -85,8 +86,6 @@ 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,
@ -140,7 +139,9 @@ DEFAULT_PREFS = {
"rate_limit_ip_overhead": True,
"geoip_db_location": "/usr/share/GeoIP/GeoIP.dat",
"cache_size": 512,
"cache_expiry": 60
"cache_expiry": 60,
"auto_manage_prefer_seeds": False,
"shared": False
}
class PreferencesManager(component.Component):
@ -148,92 +149,40 @@ 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.settings = component.get("Core").settings
# 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)
# Set the initial preferences on start-up
for key in DEFAULT_PREFS:
self.do_config_set_func(key, self.config[key])
self.config.register_change_callback(self._on_config_value_change)
def stop(self):
if self.new_release_timer:
if self.new_release_timer and self.new_release_timer.running:
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):
@ -247,7 +196,9 @@ 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
@ -269,13 +220,15 @@ 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.settings.outgoing_ports = value[0], value[1]
self.session.set_settings(self.settings)
self.session_set_setting("outgoing_ports", (value[0], value[1]))
def _on_set_random_outgoing_ports(self, key, value):
if value:
@ -284,13 +237,11 @@ class PreferencesManager(component.Component):
def _on_set_peer_tos(self, key, value):
log.debug("setting peer_tos to: %s", value)
try:
self.settings.peer_tos = chr(int(value, 16))
self.session_set_setting("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")
@ -339,6 +290,18 @@ class PreferencesManager(component.Component):
if value:
self.session.add_extension(lt.create_ut_pex_plugin)
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)
def _on_set_encryption(self, key, value):
log.debug("encryption value %s set to %s..", key, value)
pe_settings = lt.pe_settings()
@ -387,53 +350,41 @@ class PreferencesManager(component.Component):
self.session.set_max_half_open_connections(value)
def _on_set_max_connections_per_second(self, key, value):
self.settings.connection_speed = value
self.session.set_settings(self.settings)
self.session_set_setting("connection_speed", 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_ignore_limits_on_local_network(self, key, value):
self.session_set_setting("ignore_limits_on_local_network", value)
def _on_set_share_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.share_ratio_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("share_ratio_limit", value)
def _on_set_seed_time_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.seed_time_ratio_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("seed_time_ratio_limit", value)
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.settings.seed_time_limit = int(value * 60)
self.session.set_settings(self.settings)
self.session_set_setting("seed_time_limit", int(value * 60))
def _on_set_max_active_downloading(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_downloads: %s", self.settings.active_downloads)
self.settings.active_downloads = value
self.session.set_settings(self.settings)
self.session_set_setting("active_downloads", value)
def _on_set_max_active_seeding(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_seeds: %s", self.settings.active_seeds)
self.settings.active_seeds = value
self.session.set_settings(self.settings)
self.session_set_setting("active_seeds", value)
def _on_set_max_active_limit(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_limit: %s", self.settings.active_limit)
self.settings.active_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("active_limit", value)
def _on_set_dont_count_slow_torrents(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.dont_count_slow_torrents = value
self.session.set_settings(self.settings)
self.session_set_setting("dont_count_slow_torrents", value)
def _on_send_info(self, key, value):
def _on_set_send_info(self, key, value):
log.debug("Sending anonymous stats..")
"""sends anonymous stats home"""
class Send_Info_Thread(threading.Thread):
@ -463,18 +414,18 @@ class PreferencesManager(component.Component):
if value:
Send_Info_Thread(self.config).start()
def _on_new_release_check(self, key, value):
def _on_set_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:
if self.new_release_timer and self.new_release_timer.running:
self.new_release_timer.stop()
# Set a timer to check for a new release every 3 days
self.new_release_timer = LoopingCall(
self._on_new_release_check, "new_release_check", True)
self._on_set_new_release_check, "new_release_check", True)
self.new_release_timer.start(72 * 60 * 60, False)
else:
if self.new_release_timer:
if self.new_release_timer and self.new_release_timer.running:
self.new_release_timer.stop()
def _on_set_proxies(self, key, value):
@ -489,19 +440,20 @@ class PreferencesManager(component.Component):
log.debug("setting %s proxy settings", k)
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
def _on_rate_limit_ip_overhead(self, key, value):
def _on_set_rate_limit_ip_overhead(self, key, value):
log.debug("%s: %s", key, value)
self.settings.rate_limit_ip_overhead = value
self.session.set_settings(self.settings)
self.session_set_setting("rate_limit_ip_overhead", value)
def _on_geoip_db_location(self, key, value):
def _on_set_geoip_db_location(self, key, value):
log.debug("%s: %s", key, value)
# Load the GeoIP DB for country look-ups if available
geoip_db = ""
if os.path.exists(value):
geoip_db = value
elif os.path.exists(pkg_resources.resource_filename("deluge", os.path.join("data", "GeoIP.dat"))):
geoip_db = pkg_resources.resource_filename("deluge", os.path.join("data", "GeoIP.dat"))
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")
)
else:
log.warning("Unable to find GeoIP database file!")
@ -512,12 +464,14 @@ class PreferencesManager(component.Component):
log.error("Unable to load geoip database!")
log.exception(e)
def _on_cache_size(self, key, value):
def _on_set_cache_size(self, key, value):
log.debug("%s: %s", key, value)
self.settings.cache_size = value
self.session.set_settings(self.settings)
self.session_set_setting("cache_size", value)
def _on_cache_expiry(self, key, value):
def _on_set_cache_expiry(self, key, value):
log.debug("%s: %s", key, value)
self.settings.cache_expiry = value
self.session.set_settings(self.settings)
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)

View File

@ -39,25 +39,33 @@ import sys
import zlib
import os
import stat
import logging
import traceback
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import ssl, reactor, defer
from twisted.internet import reactor, defer
from OpenSSL import crypto, SSL
from types import FunctionType
import deluge.rencode as rencode
from deluge.log import LOG as log
try:
import rencode
except ImportError:
import deluge.rencode as rencode
import deluge.component as component
import deluge.configmanager
from deluge.core.authmanager import AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT
from deluge.core.authmanager import (AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_ADMIN)
from deluge.error import (DelugeError, NotAuthorizedError, WrappedException,
_ClientSideRecreateError, IncompatibleClient)
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
@ -90,13 +98,13 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
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] + "("
@ -111,12 +119,6 @@ def format_request(call):
return "UnicodeEncodeError, call: %s" % call
else:
return s
class DelugeError(Exception):
pass
class NotAuthorizedError(DelugeError):
pass
class ServerContextFactory(object):
def getContext(self):
@ -139,7 +141,7 @@ class DelugeRPCProtocol(Protocol):
"""
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
If the RPC Request message is valid, then the method is called in
:meth:`dispatch`.
:param data: the data from the client. It should be a zlib compressed
@ -175,7 +177,8 @@ class DelugeRPCProtocol(Protocol):
for call in request:
if len(call) != 4:
log.debug("Received invalid rpc request: number of items in request is %s", len(call))
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)
@ -187,7 +190,7 @@ class DelugeRPCProtocol(Protocol):
: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.transport.write(zlib.compress(rencode.dumps(data)))
@ -196,7 +199,8 @@ class DelugeRPCProtocol(Protocol):
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
@ -218,6 +222,9 @@ class DelugeRPCProtocol(Protocol):
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
@ -239,33 +246,55 @@ class DelugeRPCProtocol(Protocol):
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()
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":
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":
# 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
self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0])
self.factory.session_protocols[self.transport.sessionno] = self
except Exception, e:
sendError()
log.exception(e)
if not isinstance(e, _ClientSideRecreateError):
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.transport.sessionno in self.factory.authorized_sessions:
elif method == "daemon.set_event_interest" and self.valid_session():
log.debug("RPC dispatch daemon.set_event_interest")
# This special case is to allow clients to set which events they are
# interested in receiving.
# We are expecting a sequence from the client.
@ -280,21 +309,24 @@ class DelugeRPCProtocol(Protocol):
finally:
return
if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions:
if method in self.factory.methods and self.valid_session():
log.debug("RPC dispatch %s", method)
try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[self.transport.sessionno]
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]
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 too low: %s < %s" % (auth_level, method_auth_requirement))
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
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:
@ -338,7 +370,7 @@ 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
@ -348,6 +380,7 @@ class RPCServer(component.Component):
# Holds the interested event list for the sessions
self.factory.interested_events = {}
self.listen = listen
if not listen:
return
@ -391,6 +424,17 @@ 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.
@ -417,26 +461,63 @@ class RPCServer(component.Component):
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:
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.
@ -454,6 +535,29 @@ 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
@ -497,8 +601,12 @@ 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)

View File

@ -36,6 +36,7 @@
import os
import time
import logging
from urllib import unquote
from urlparse import urlparse
@ -44,30 +45,58 @@ from deluge._libtorrent import lt
import deluge.common
import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.log import LOG as log
from deluge.event import *
TORRENT_STATE = deluge.common.TORRENT_STATE
log = logging.getLogger(__name__)
def sanitize_filepath(filepath, folder=False):
"""
Returns a sanitized filepath to pass to libotorrent rename_file().
The filepath will have backslashes substituted along with whitespace
padding and duplicate slashes stripped. If `folder` is True a trailing
slash is appended to the returned filepath.
"""
def clean_filename(filename):
filename = filename.strip()
if filename.replace('.', '') == '':
return ''
return filename
if '\\' in filepath or '/' in filepath:
folderpath = filepath.replace('\\', '/').split('/')
folderpath = [clean_filename(x) for x in folderpath]
newfilepath = '/'.join(filter(None, folderpath))
else:
newfilepath = clean_filename(filepath)
if folder is True:
return newfilepath + '/'
else:
return newfilepath
class TorrentOptions(dict):
def __init__(self):
config = ConfigManager("core.conf").config
options_conf_map = {
"max_connections": "max_connections_per_torrent",
"max_upload_slots": "max_upload_slots_per_torrent",
"max_upload_speed": "max_upload_speed_per_torrent",
"max_download_speed": "max_download_speed_per_torrent",
"prioritize_first_last_pieces": "prioritize_first_last_pieces",
"compact_allocation": "compact_allocation",
"download_location": "download_location",
"auto_managed": "auto_managed",
"stop_at_ratio": "stop_seed_at_ratio",
"stop_ratio": "stop_seed_ratio",
"remove_at_ratio": "remove_seed_at_ratio",
"move_completed": "move_completed",
"move_completed_path": "move_completed_path",
"add_paused": "add_paused",
}
"max_connections": "max_connections_per_torrent",
"max_upload_slots": "max_upload_slots_per_torrent",
"max_upload_speed": "max_upload_speed_per_torrent",
"max_download_speed": "max_download_speed_per_torrent",
"prioritize_first_last_pieces": "prioritize_first_last_pieces",
"sequential_download": "sequential_download",
"compact_allocation": "compact_allocation",
"download_location": "download_location",
"auto_managed": "auto_managed",
"stop_at_ratio": "stop_seed_at_ratio",
"stop_ratio": "stop_seed_ratio",
"remove_at_ratio": "remove_seed_at_ratio",
"move_completed": "move_completed",
"move_completed_path": "move_completed_path",
"add_paused": "add_paused",
"shared": "shared"
}
for opt_k, conf_k in options_conf_map.iteritems():
self[opt_k] = config[conf_k]
self["file_priorities"] = []
@ -76,13 +105,13 @@ class TorrentOptions(dict):
class Torrent(object):
"""Torrent holds information about torrents added to the libtorrent session.
"""
def __init__(self, handle, options, state=None, filename=None, magnet=None):
def __init__(self, handle, options, state=None, filename=None, magnet=None, owner=None):
log.debug("Creating torrent object %s", str(handle.info_hash()))
# Get the core config
self.config = ConfigManager("core.conf")
self.rpcserver = component.get("RPCServer")
# This dict holds previous status dicts returned for this torrent
# We use this to return dicts that only contain changes from the previous
# {session_id: status_dict, ...}
@ -90,7 +119,7 @@ class Torrent(object):
from twisted.internet.task import LoopingCall
self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status)
self.prev_status_cleanup_loop.start(10)
# Set the libtorrent handle
self.handle = handle
# Set the torrent_id for this torrent
@ -179,6 +208,23 @@ class Torrent(object):
else:
self.time_added = time.time()
# Keep track of the owner
if state:
self.owner = state.owner
else:
self.owner = owner
# Keep track of last seen complete
if state:
self._last_seen_complete = state.last_seen_complete or 0.0
else:
self._last_seen_complete = 0.0
# Keep track if we're forcing a recheck of the torrent so that we can
# re-pause it after its done if necessary
self.forcing_recheck = False
self.forcing_recheck_paused = False
log.debug("Torrent object created.")
## Options methods ##
@ -192,17 +238,44 @@ class Torrent(object):
"max_download_speed": self.set_max_download_speed,
"max_upload_slots": self.handle.set_max_uploads,
"max_upload_speed": self.set_max_upload_speed,
"prioritize_first_last_pieces": self.set_prioritize_first_last
"prioritize_first_last_pieces": self.set_prioritize_first_last,
"sequential_download": self.set_sequential_download
}
for (key, value) in options.items():
if OPTIONS_FUNCS.has_key(key):
OPTIONS_FUNCS[key](value)
self.options.update(options)
def get_options(self):
return self.options
def get_name(self):
if self.handle.has_metadata():
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
if not name:
name = self.torrent_info.name()
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
elif self.magnet:
try:
keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
name = keys.get('dn')
if not name:
return self.torrent_id
name = unquote(name).replace('+', ' ')
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
except:
pass
return self.torrent_id
def set_owner(self, account):
self.owner = account
def set_max_connections(self, max_connections):
self.options["max_connections"] = int(max_connections)
@ -231,14 +304,30 @@ class Torrent(object):
def set_prioritize_first_last(self, prioritize):
self.options["prioritize_first_last_pieces"] = prioritize
if prioritize:
if self.handle.has_metadata():
if self.handle.get_torrent_info().num_files() == 1:
# We only do this if one file is in the torrent
priorities = [1] * self.handle.get_torrent_info().num_pieces()
priorities[0] = 7
priorities[-1] = 7
self.handle.prioritize_pieces(priorities)
if self.handle.has_metadata():
if self.options["compact_allocation"]:
log.debug("Setting first/last priority with compact "
"allocation does not work!")
return
paths = {}
ti = self.handle.get_torrent_info()
for n in range(ti.num_pieces()):
slices = ti.map_block(n, 0, ti.piece_size(n))
for slice in slices:
fe = ti.file_at(slice.file_index)
paths.setdefault(fe.path, []).append(n)
priorities = self.handle.piece_priorities()
for pieces in paths.itervalues():
two_percent = 2*100/len(pieces)
for piece in pieces[:two_percent]+pieces[-two_percent:]:
priorities[piece] = prioritize and 7 or 1
self.handle.prioritize_pieces(priorities)
def set_sequential_download(self, set_sequencial):
self.options["sequential_download"] = set_sequencial
self.handle.set_sequential_download(set_sequencial)
def set_auto_managed(self, auto_managed):
self.options["auto_managed"] = auto_managed
@ -308,7 +397,7 @@ class Torrent(object):
tracker_list = []
for tracker in trackers:
new_entry = lt.announce_entry(tracker["url"])
new_entry = lt.announce_entry(str(tracker["url"]))
new_entry.tier = tracker["tier"]
tracker_list.append(new_entry)
self.handle.replace_trackers(tracker_list)
@ -319,7 +408,7 @@ class Torrent(object):
# Set the tracker list in the torrent object
self.trackers = trackers
if len(trackers) > 0:
# Force a reannounce if there is at least 1 tracker
# Force a re-announce if there is at least 1 tracker
self.force_reannounce()
self.tracker_host = None
@ -341,7 +430,10 @@ class Torrent(object):
# Set self.state to the ltstate right away just incase we don't hit some
# of the logic below
self.state = str(ltstate)
if ltstate in LTSTATE:
self.state = LTSTATE[ltstate]
else:
self.state = str(ltstate)
log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
log.debug("session.is_paused: %s", component.get("Core").session.is_paused())
@ -393,15 +485,14 @@ class Torrent(object):
else:
status = self.status
if self.is_finished and (self.options["stop_at_ratio"] or self.config["stop_seed_at_ratio"]):
if self.is_finished and self.options["stop_at_ratio"]:
# We're a seed, so calculate the time to the 'stop_share_ratio'
if not status.upload_payload_rate:
return 0
stop_ratio = self.config["stop_seed_ratio"] if self.config["stop_seed_at_ratio"] else self.options["stop_ratio"]
stop_ratio = self.options["stop_ratio"]
return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
left = status.total_wanted - status.total_done
left = status.total_wanted - status.total_wanted_done
if left <= 0 or status.download_payload_rate == 0:
return 0
@ -475,11 +566,11 @@ class Torrent(object):
ret.append({
"client": client,
"country": country,
"down_speed": peer.down_speed,
"down_speed": peer.payload_down_speed,
"ip": "%s:%s" % (peer.ip[0], peer.ip[1]),
"progress": peer.progress,
"seed": peer.flags & peer.seed,
"up_speed": peer.up_speed,
"up_speed": peer.payload_up_speed,
})
return ret
@ -540,21 +631,31 @@ class Torrent(object):
return host
return ""
def get_last_seen_complete(self):
"""
Returns the time a torrent was last seen complete, ie, with all pieces
available.
"""
if lt.version_minor > 15:
return self.status.last_seen_complete
self.calculate_last_seen_complete()
return self._last_seen_complete
def get_status(self, keys, diff=False):
"""
Returns the status of the torrent based on the keys provided
:param keys: the keys to get the status on
:type keys: list of str
:param diff: if True, will return a diff of the changes since the last
call to get_status based on the session_id
:type diff: bool
:returns: a dictionary of the status keys and their values
:rtype: dict
"""
# Create the full dictionary
self.status = self.handle.status()
if self.handle.has_metadata():
@ -568,7 +669,13 @@ class Torrent(object):
if distributed_copies < 0:
distributed_copies = 0.0
#if you add a key here->add it to core.py STATUS_KEYS too.
# Calculate the seeds:peers ratio
if self.status.num_incomplete == 0:
# Use -1.0 to signify infinity
seeds_peers_ratio = -1.0
else:
seeds_peers_ratio = self.status.num_complete / float(self.status.num_incomplete)
full_status = {
"active_time": self.status.active_time,
"all_time_download": self.status.all_time_download,
@ -586,15 +693,21 @@ class Torrent(object):
"message": self.statusmsg,
"move_on_completed_path": self.options["move_completed_path"],
"move_on_completed": self.options["move_completed"],
"move_completed_path": self.options["move_completed_path"],
"move_completed": self.options["move_completed"],
"next_announce": self.status.next_announce.seconds,
"num_peers": self.status.num_peers - self.status.num_seeds,
"num_seeds": self.status.num_seeds,
"owner": self.owner,
"paused": self.status.paused,
"prioritize_first_last": self.options["prioritize_first_last_pieces"],
"sequential_download": self.options["sequential_download"],
"progress": progress,
"shared": self.options["shared"],
"remove_at_ratio": self.options["remove_at_ratio"],
"save_path": self.options["download_location"],
"seeding_time": self.status.seeding_time,
"seeds_peers_ratio": seeds_peers_ratio,
"seed_rank": self.status.seed_rank,
"state": self.state,
"stop_at_ratio": self.options["stop_at_ratio"],
@ -603,8 +716,8 @@ class Torrent(object):
"total_done": self.status.total_done,
"total_payload_download": self.status.total_payload_download,
"total_payload_upload": self.status.total_payload_upload,
"total_peers": self.status.num_incomplete,
"total_seeds": self.status.num_complete,
"total_peers": self.status.list_peers - self.status.list_seeds,
"total_seeds": self.status.list_seeds,
"total_uploaded": self.status.all_time_upload,
"total_wanted": self.status.total_wanted,
"tracker": self.status.current_tracker,
@ -621,32 +734,6 @@ class Torrent(object):
return self.torrent_info.comment()
return ""
def ti_name():
if self.handle.has_metadata():
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
if not name:
name = self.torrent_info.name()
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
elif self.magnet:
try:
keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
name = keys.get('dn')
if not name:
return self.torrent_id
name = unquote(name).replace('+', ' ')
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
except:
pass
return self.torrent_id
def ti_priv():
if self.handle.has_metadata():
return self.torrent_info.priv()
@ -667,6 +754,10 @@ class Torrent(object):
if self.handle.has_metadata():
return self.torrent_info.piece_length()
return 0
def ti_pieces_info():
if self.handle.has_metadata():
return self.get_pieces_info()
return None
fns = {
"comment": ti_comment,
@ -674,9 +765,10 @@ class Torrent(object):
"file_progress": self.get_file_progress,
"files": self.get_files,
"is_seed": self.handle.is_seed,
"name": ti_name,
"name": self.get_name,
"num_files": ti_num_files,
"num_pieces": ti_num_pieces,
"pieces": ti_pieces_info,
"peers": self.get_peers,
"piece_length": ti_piece_length,
"private": ti_priv,
@ -684,6 +776,7 @@ class Torrent(object):
"ratio": self.get_ratio,
"total_size": ti_total_size,
"tracker_host": self.get_tracker_host,
"last_seen_complete": self.get_last_seen_complete
}
# Create the desired status dictionary and return it
@ -699,7 +792,7 @@ class Torrent(object):
status_dict[key] = full_status[key]
elif key in fns:
status_dict[key] = fns[key]()
session_id = self.rpcserver.get_session_id()
if diff:
if session_id in self.prev_status:
@ -711,7 +804,7 @@ class Torrent(object):
status_diff[key] = value
else:
status_diff[key] = value
self.prev_status[session_id] = status_dict
return status_diff
@ -727,6 +820,7 @@ class Torrent(object):
self.handle.set_upload_limit(int(self.max_upload_speed * 1024))
self.handle.set_download_limit(int(self.max_download_speed * 1024))
self.handle.prioritize_files(self.file_priorities)
self.handle.set_sequential_download(self.options["sequential_download"])
self.handle.resolve_countries(True)
def pause(self):
@ -761,13 +855,8 @@ class Torrent(object):
if self.handle.is_finished():
# If the torrent has already reached it's 'stop_seed_ratio' then do not do anything
if self.config["stop_seed_at_ratio"] or self.options["stop_at_ratio"]:
if self.options["stop_at_ratio"]:
ratio = self.options["stop_ratio"]
else:
ratio = self.config["stop_seed_ratio"]
if self.get_ratio() >= ratio:
if self.options["stop_at_ratio"]:
if self.get_ratio() >= self.options["stop_ratio"]:
#XXX: This should just be returned in the RPC Response, no event
#self.signals.emit_event("torrent_resume_at_stop_ratio")
return
@ -794,8 +883,27 @@ class Torrent(object):
def move_storage(self, dest):
"""Move a torrent's storage location"""
# Attempt to convert utf8 path to unicode
# Note: Inconsistent encoding for 'dest', needs future investigation
try:
self.handle.move_storage(dest.encode("utf8"))
dest_u = unicode(dest, "utf-8")
except TypeError:
# String is already unicode
dest_u = dest
if not os.path.exists(dest_u):
try:
# Try to make the destination path if it doesn't exist
os.makedirs(dest_u)
except IOError, e:
log.exception(e)
log.error("Could not move storage for torrent %s since %s does "
"not exist and could not create the directory.",
self.torrent_id, dest_u)
return False
try:
self.handle.move_storage(dest_u)
except:
return False
@ -857,18 +965,22 @@ class Torrent(object):
def force_recheck(self):
"""Forces a recheck of the torrents pieces"""
paused = self.handle.is_paused()
try:
self.handle.force_recheck()
self.handle.resume()
except Exception, e:
log.debug("Unable to force recheck: %s", e)
return False
self.forcing_recheck = True
self.forcing_recheck_paused = paused
return True
def rename_files(self, filenames):
"""Renames files in the torrent. 'filenames' should be a list of
(index, filename) pairs."""
for index, filename in filenames:
filename = sanitize_filepath(filename)
self.handle.rename_file(index, filename.encode("utf-8"))
def rename_folder(self, folder, new_folder):
@ -879,8 +991,7 @@ class Torrent(object):
log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder)
return
if new_folder[-1:] != "/":
new_folder += "/"
new_folder = sanitize_filepath(new_folder, folder=True)
wait_on_folder = (folder, new_folder, [])
for f in self.get_files():
@ -889,14 +1000,62 @@ class Torrent(object):
wait_on_folder[2].append(f["index"])
self.handle.rename_file(f["index"], f["path"].replace(folder, new_folder, 1).encode("utf-8"))
self.waiting_on_folder_rename.append(wait_on_folder)
def cleanup_prev_status(self):
"""
This method gets called to check the validity of the keys in the prev_status
dict. If the key is no longer valid, the dict will be deleted.
"""
for key in self.prev_status.keys():
if not self.rpcserver.is_session_valid(key):
del self.prev_status[key]
def calculate_last_seen_complete(self):
if self._last_seen_complete+60 > time.time():
# Simple caching. Only calculate every 1 min at minimum
return self._last_seen_complete
availability = self.handle.piece_availability()
if filter(lambda x: x<1, availability):
# Torrent does not have all the pieces
return
log.trace("Torrent %s has all the pieces. Setting last seen complete.",
self.torrent_id)
self._last_seen_complete = time.time()
def get_pieces_info(self):
pieces = {}
# First get the pieces availability.
availability = self.handle.piece_availability()
# Pieces from connected peers
for peer_info in self.handle.get_peer_info():
if peer_info.downloading_piece_index < 0:
# No piece index, then we're not downloading anything from
# this peer
continue
pieces[peer_info.downloading_piece_index] = 2
# Now, the rest of the pieces
for idx, piece in enumerate(self.handle.status().pieces):
if idx in pieces:
# Piece beeing downloaded, handled above
continue
elif piece:
# Completed Piece
pieces[idx] = 3
continue
elif availability[idx] > 0:
# Piece not downloaded nor beeing downloaded but available
pieces[idx] = 1
continue
# If we reached here, it means the piece is missing, ie, there's
# no known peer with this piece, or this piece has not been asked
# for so far.
pieces[idx] = 0
sorted_indexes = pieces.keys()
sorted_indexes.sort()
# Return only the piece states, no need for the piece index
# Keep the order
return [pieces[idx] for idx in sorted_indexes]

View File

@ -41,24 +41,24 @@ import os
import time
import shutil
import operator
import logging
import re
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from deluge._libtorrent import lt
from deluge.event import *
from deluge.error import *
import deluge.common
import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.core.torrent import Torrent
from deluge.core.torrent import TorrentOptions
import deluge.core.oldstateupgrader
from deluge.ui.common import utf8_encoded
from deluge.common import utf8_encoded
from deluge.log import LOG as log
log = logging.getLogger(__name__)
class TorrentState:
def __init__(self,
@ -74,6 +74,7 @@ class TorrentState:
max_upload_speed=-1.0,
max_download_speed=-1.0,
prioritize_first_last=False,
sequential_download=False,
file_priorities=None,
queue=None,
auto_managed=True,
@ -84,7 +85,10 @@ class TorrentState:
move_completed=False,
move_completed_path=None,
magnet=None,
time_added=-1
time_added=-1,
last_seen_complete=0.0, # 0 is the default returned when the info
owner="", # does not exist on lt >= .16
shared=False
):
self.torrent_id = torrent_id
self.filename = filename
@ -94,6 +98,8 @@ class TorrentState:
self.is_finished = is_finished
self.magnet = magnet
self.time_added = time_added
self.last_seen_complete = last_seen_complete
self.owner = owner
# Options
self.compact = compact
@ -104,6 +110,7 @@ class TorrentState:
self.max_upload_speed = max_upload_speed
self.max_download_speed = max_download_speed
self.prioritize_first_last = prioritize_first_last
self.sequential_download = sequential_download
self.file_priorities = file_priorities
self.auto_managed = auto_managed
self.stop_ratio = stop_ratio
@ -111,6 +118,7 @@ class TorrentState:
self.remove_at_ratio = remove_at_ratio
self.move_completed = move_completed
self.move_completed_path = move_completed_path
self.shared = shared
class TorrentManagerState:
def __init__(self):
@ -124,7 +132,8 @@ class TorrentManager(component.Component):
"""
def __init__(self):
component.Component.__init__(self, "TorrentManager", interval=5, depend=["CorePluginManager"])
component.Component.__init__(self, "TorrentManager", interval=5,
depend=["CorePluginManager"])
log.debug("TorrentManager init..")
# Set the libtorrent session
self.session = component.get("Core").session
@ -139,6 +148,7 @@ class TorrentManager(component.Component):
# Create the torrents dict { torrent_id: Torrent }
self.torrents = {}
self.last_seen_complete_loop = None
# This is a list of torrent_id when we shutdown the torrentmanager.
# We use this list to determine if all active torrents have been paused
@ -192,6 +202,8 @@ class TorrentManager(component.Component):
self.on_alert_metadata_received)
self.alerts.register_handler("file_error_alert",
self.on_alert_file_error)
self.alerts.register_handler("file_completed_alert",
self.on_alert_file_completed)
def start(self):
# Get the pluginmanager reference
@ -209,6 +221,9 @@ class TorrentManager(component.Component):
self.save_resume_data_timer = LoopingCall(self.save_resume_data)
self.save_resume_data_timer.start(190)
if self.last_seen_complete_loop:
self.last_seen_complete_loop.start(60)
def stop(self):
# Stop timers
if self.save_state_timer.running:
@ -217,6 +232,9 @@ class TorrentManager(component.Component):
if self.save_resume_data_timer.running:
self.save_resume_data_timer.stop()
if self.last_seen_complete_loop:
self.last_seen_complete_loop.stop()
# Save state on shutdown
self.save_state()
@ -256,16 +274,16 @@ class TorrentManager(component.Component):
def update(self):
for torrent_id, torrent in self.torrents.items():
if self.config["stop_seed_at_ratio"] or torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
# If the global setting is set, but the per-torrent isn't.. Just skip to the next torrent
# This is so that a user can turn-off the stop at ratio option on a per-torrent basis
if self.config["stop_seed_at_ratio"] and not torrent.options["stop_at_ratio"]:
if torrent.options["stop_at_ratio"] and torrent.state not in (
"Checking", "Allocating", "Paused", "Queued"):
# If the global setting is set, but the per-torrent isn't..
# Just skip to the next torrent.
# This is so that a user can turn-off the stop at ratio option
# on a per-torrent basis
if not torrent.options["stop_at_ratio"]:
continue
stop_ratio = self.config["stop_seed_ratio"]
if torrent.options["stop_at_ratio"]:
stop_ratio = torrent.options["stop_ratio"]
if torrent.get_ratio() >= stop_ratio and torrent.is_finished:
if self.config["remove_seed_at_ratio"] or torrent.options["remove_at_ratio"]:
if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
if torrent.options["remove_at_ratio"]:
self.remove(torrent_id)
break
if not torrent.handle.is_paused():
@ -277,7 +295,16 @@ class TorrentManager(component.Component):
def get_torrent_list(self):
"""Returns a list of torrent_ids"""
return self.torrents.keys()
torrent_ids = self.torrents.keys()
if component.get("RPCServer").get_session_auth_level() == AUTH_LEVEL_ADMIN:
return torrent_ids
current_user = component.get("RPCServer").get_session_user()
for torrent_id in torrent_ids[:]:
torrent_status = self[torrent_id].get_status(["owner", "shared"])
if torrent_status["owner"] != current_user and torrent_status["shared"] == False:
torrent_ids.pop(torrent_ids.index(torrent_id))
return torrent_ids
def get_torrent_info_from_file(self, filepath):
"""Returns a torrent_info for the file specified or None"""
@ -317,9 +344,14 @@ class TorrentManager(component.Component):
log.warning("Unable to delete the fastresume file: %s", e)
def add(self, torrent_info=None, state=None, options=None, save_state=True,
filedump=None, filename=None, magnet=None, resume_data=None):
filedump=None, filename=None, magnet=None, resume_data=None, owner=None):
"""Add a torrent to the manager and returns it's torrent_id"""
if owner is None:
owner = component.get("RPCServer").get_session_user()
if not owner:
owner = "localclient"
if torrent_info is None and state is None and filedump is None and magnet is None:
log.debug("You must specify a valid torrent_info, torrent state or magnet.")
return
@ -346,6 +378,7 @@ class TorrentManager(component.Component):
options["max_upload_speed"] = state.max_upload_speed
options["max_download_speed"] = state.max_download_speed
options["prioritize_first_last_pieces"] = state.prioritize_first_last
options["sequential_download"] = state.sequential_download
options["file_priorities"] = state.file_priorities
options["compact_allocation"] = state.compact
options["download_location"] = state.save_path
@ -356,6 +389,7 @@ class TorrentManager(component.Component):
options["move_completed"] = state.move_completed
options["move_completed_path"] = state.move_completed_path
options["add_paused"] = state.paused
options["shared"] = state.shared
ti = self.get_torrent_info_from_file(
os.path.join(get_config_dir(),
@ -376,7 +410,35 @@ class TorrentManager(component.Component):
add_torrent_params["resume_data"] = resume_data
else:
# We have a torrent_info object so we're not loading from state.
# We have a torrent_info object or magnet uri so we're not loading from state.
if torrent_info:
add_torrent_id = str(torrent_info.info_hash())
if add_torrent_id in self.get_torrent_list():
# Torrent already exists just append any extra trackers.
log.debug("Torrent (%s) exists, checking for trackers to add...", add_torrent_id)
add_torrent_trackers = []
for value in torrent_info.trackers():
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
add_torrent_trackers.append(tracker)
torrent_trackers = {}
tracker_list = []
for tracker in self[add_torrent_id].get_status(["trackers"])["trackers"]:
torrent_trackers[(tracker["url"])] = tracker
tracker_list.append(tracker)
added_tracker = False
for tracker in add_torrent_trackers:
if tracker['url'] not in torrent_trackers:
tracker_list.append(tracker)
added_tracker = True
if added_tracker:
self[add_torrent_id].set_trackers(tracker_list)
return
# Check if options is None and load defaults
if options == None:
options = TorrentOptions()
@ -393,7 +455,6 @@ class TorrentManager(component.Component):
torrent_info.rename_file(index, utf8_encoded(name))
add_torrent_params["ti"] = torrent_info
add_torrent_params["resume_data"] = ""
#log.info("Adding torrent: %s", filename)
log.debug("options: %s", options)
@ -434,7 +495,13 @@ class TorrentManager(component.Component):
# Set auto_managed to False because the torrent is paused
handle.auto_managed(False)
# Create a Torrent object
torrent = Torrent(handle, options, state, filename, magnet)
owner = state.owner if state else (
owner if owner else component.get("RPCServer").get_session_user()
)
account_exists = component.get("AuthManager").has_account(owner)
if not account_exists:
owner = 'localclient'
torrent = Torrent(handle, options, state, filename, magnet, owner)
# Add the torrent object to the dictionary
self.torrents[torrent.torrent_id] = torrent
if self.config["queue_new_to_top"]:
@ -473,9 +540,15 @@ class TorrentManager(component.Component):
# Save the session state
self.save_state()
# Emit the torrent_added signal
component.get("EventManager").emit(TorrentAddedEvent(torrent.torrent_id))
# Emit torrent_added signal
from_state = state is not None
component.get("EventManager").emit(
TorrentAddedEvent(torrent.torrent_id, from_state)
)
log.info("Torrent %s from user \"%s\" %s",
torrent.get_status(["name"])["name"],
torrent.get_status(["owner"])["owner"],
(from_state and "loaded" or "added"))
return torrent.torrent_id
def load_torrent(self, torrent_id):
@ -515,6 +588,8 @@ class TorrentManager(component.Component):
if torrent_id not in self.torrents:
raise InvalidTorrentError("torrent_id not in session")
torrent_name = self.torrents[torrent_id].get_status(["name"])["name"]
# Emit the signal to the clients
component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id))
@ -554,7 +629,7 @@ class TorrentManager(component.Component):
# Remove the torrent from deluge's session
try:
del self.torrents[torrent_id]
except KeyError, ValueError:
except (KeyError, ValueError):
return False
# Save the session state
@ -562,7 +637,8 @@ class TorrentManager(component.Component):
# Emit the signal to the clients
component.get("EventManager").emit(TorrentRemovedEvent(torrent_id))
log.info("Torrent %s removed by user: %s", torrent_name,
component.get("RPCServer").get_session_user())
return True
def load_state(self):
@ -602,6 +678,17 @@ class TorrentManager(component.Component):
log.error("Torrent state file is either corrupt or incompatible! %s", e)
break
if lt.version_minor < 16:
log.debug("libtorrent version is lower than 0.16. Start looping "
"callback to calculate last_seen_complete info.")
def calculate_last_seen_complete():
for torrent in self.torrents.values():
torrent.calculate_last_seen_complete()
self.last_seen_complete_loop = LoopingCall(
calculate_last_seen_complete
)
component.get("EventManager").emit(SessionStartedEvent())
def save_state(self):
@ -626,6 +713,7 @@ class TorrentManager(component.Component):
torrent.options["max_upload_speed"],
torrent.options["max_download_speed"],
torrent.options["prioritize_first_last_pieces"],
torrent.options["sequential_download"],
torrent.options["file_priorities"],
torrent.get_queue_position(),
torrent.options["auto_managed"],
@ -636,7 +724,10 @@ class TorrentManager(component.Component):
torrent.options["move_completed"],
torrent.options["move_completed_path"],
torrent.magnet,
torrent.time_added
torrent.time_added,
torrent.get_last_seen_complete(),
torrent.owner,
torrent.options["shared"]
)
state.torrents.append(torrent_state)
@ -731,6 +822,38 @@ class TorrentManager(component.Component):
except IOError:
log.warning("Error trying to save fastresume file")
def remove_empty_folders(self, torrent_id, folder):
"""
Recursively removes folders but only if they are empty.
Cleans up after libtorrent folder renames.
"""
if torrent_id not in self.torrents:
raise InvalidTorrentError("torrent_id is not in session")
info = self.torrents[torrent_id].get_status(['save_path'])
# Regex removes leading slashes that causes join function to ignore save_path
folder_full_path = os.path.join(info['save_path'], re.sub("^/*", "", folder))
folder_full_path = os.path.normpath(folder_full_path)
try:
if not os.listdir(folder_full_path):
os.removedirs(folder_full_path)
log.debug("Removed Empty Folder %s", folder_full_path)
else:
for root, dirs, files in os.walk(folder_full_path, topdown=False):
for name in dirs:
try:
os.removedirs(os.path.join(root, name))
log.debug("Removed Empty Folder %s", os.path.join(root, name))
except OSError as (errno, strerror):
if errno == 39:
# Error raised if folder is not empty
log.debug("%s", strerror)
except OSError as (errno, strerror):
log.debug("Cannot Remove Folder: %s (ErrNo %s)", strerror, errno)
def queue_top(self, torrent_id):
"""Queue torrent to top"""
if self.torrents[torrent_id].get_queue_position() == 0:
@ -850,6 +973,13 @@ class TorrentManager(component.Component):
except:
return
# Check to see if we're forcing a recheck and set it back to paused
# if necessary
if torrent.forcing_recheck:
torrent.forcing_recheck = False
if torrent.forcing_recheck_paused:
torrent.handle.pause()
# Set the torrent state
torrent.update_state()
@ -905,7 +1035,7 @@ class TorrentManager(component.Component):
torrent = self.torrents[str(alert.handle.info_hash())]
except:
return
torrent.set_save_path(alert.handle.save_path())
torrent.set_save_path(os.path.normpath(alert.handle.save_path()))
torrent.set_move_completed(False)
def on_alert_torrent_resumed(self, alert):
@ -985,6 +1115,8 @@ class TorrentManager(component.Component):
if len(wait_on_folder[2]) == 1:
# This is the last alert we were waiting for, time to send signal
component.get("EventManager").emit(TorrentFolderRenamedEvent(torrent_id, wait_on_folder[0], wait_on_folder[1]))
# Empty folders are removed after libtorrent folder renames
self.remove_empty_folders(torrent_id, wait_on_folder[0])
del torrent.waiting_on_folder_rename[i]
self.save_resume_data((torrent_id,))
break
@ -1012,3 +1144,9 @@ class TorrentManager(component.Component):
except:
return
torrent.update_state()
def on_alert_file_completed(self, alert):
log.debug("file_completed_alert: %s", alert.message())
torrent_id = str(alert.handle.info_hash())
component.get("EventManager").emit(
TorrentFileCompletedEvent(torrent_id, alert.index))

View File

@ -1,12 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Deluge BitTorrent Client
GenericName=Bittorrent Client
Comment=Transfer files using the Bittorrent protocol
Exec=deluge-gtk
Icon=deluge
Terminal=false
Type=Application
Categories=Network;
StartupNotify=true
MimeType=application/x-bittorrent;

View File

@ -2,6 +2,7 @@
# error.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -35,7 +36,21 @@
class DelugeError(Exception):
pass
def _get_message(self):
return self._message
def _set_message(self, message):
self._message = message
message = property(_get_message, _set_message)
del _get_message, _set_message
def __str__(self):
return self.message
def __new__(cls, *args, **kwargs):
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
inst._args = args
inst._kwargs = kwargs
return inst
class NoCoreError(DelugeError):
pass
@ -48,3 +63,69 @@ class InvalidTorrentError(DelugeError):
class InvalidPathError(DelugeError):
pass
class WrappedException(DelugeError):
def _get_traceback(self):
return self._traceback
def _set_traceback(self, traceback):
self._traceback = traceback
traceback = property(_get_traceback, _set_traceback)
del _get_traceback, _set_traceback
def _get_type(self):
return self._type
def _set_type(self, type):
self._type = type
type = property(_get_type, _set_type)
del _get_type, _set_type
def __init__(self, message, exception_type, traceback):
self.message = message
self.type = exception_type
self.traceback = traceback
class _ClientSideRecreateError(DelugeError):
pass
class IncompatibleClient(_ClientSideRecreateError):
def __init__(self, daemon_version):
self.daemon_version = daemon_version
self.message = _(
"Your deluge client is not compatible with the daemon. "
"Please upgrade your client to %(daemon_version)s"
) % dict(daemon_version=self.daemon_version)
class NotAuthorizedError(_ClientSideRecreateError):
def __init__(self, current_level, required_level):
self.message = _(
"Auth level too low: %(current_level)s < %(required_level)s" %
dict(current_level=current_level, required_level=required_level)
)
self.current_level = current_level
self.required_level = required_level
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
def _get_username(self):
return self._username
def _set_username(self, username):
self._username = username
username = property(_get_username, _set_username)
del _get_username, _set_username
def __init__(self, message, username):
super(_UsernameBasedPasstroughError, self).__init__(message)
self.message = message
self.username = username
class BadLoginError(_UsernameBasedPasstroughError):
pass
class AuthenticationRequired(_UsernameBasedPasstroughError):
pass
class AuthManagerError(_UsernameBasedPasstroughError):
pass

View File

@ -57,7 +57,9 @@ class DelugeEvent(object):
The base class for all events.
:prop name: this is the name of the class which is in-turn the event name
:type name: string
:prop args: a list of the attribute values
:type args: list
"""
__metaclass__ = DelugeEventMetaClass
@ -77,11 +79,14 @@ class TorrentAddedEvent(DelugeEvent):
"""
Emitted when a new torrent is successfully added to the session.
"""
def __init__(self, torrent_id):
def __init__(self, torrent_id, from_state):
"""
:param torrent_id: str, the torrent_id of the torrent that was added
:param torrent_id: the torrent_id of the torrent that was added
:type torrent_id: string
:param from_state: was the torrent loaded from state? Or is it a new torrent.
:type from_state: bool
"""
self._args = [torrent_id]
self._args = [torrent_id, from_state]
class TorrentRemovedEvent(DelugeEvent):
"""
@ -89,7 +94,8 @@ class TorrentRemovedEvent(DelugeEvent):
"""
def __init__(self, torrent_id):
"""
:param torrent_id: str, the torrent_id
:param torrent_id: the torrent_id
:type torrent_id: string
"""
self._args = [torrent_id]
@ -99,7 +105,8 @@ class PreTorrentRemovedEvent(DelugeEvent):
"""
def __init__(self, torrent_id):
"""
:param torrent_id: str, the torrent_id
:param torrent_id: the torrent_id
:type torrent_id: string
"""
self._args = [torrent_id]
@ -109,8 +116,10 @@ class TorrentStateChangedEvent(DelugeEvent):
"""
def __init__(self, torrent_id, state):
"""
:param torrent_id: str, the torrent_id
:param state: str, the new state
:param torrent_id: the torrent_id
:type torrent_id: string
:param state: the new state
:type state: string
"""
self._args = [torrent_id, state]
@ -126,9 +135,12 @@ class TorrentFolderRenamedEvent(DelugeEvent):
"""
def __init__(self, torrent_id, old, new):
"""
:param torrent_id: str, the torrent_id
:param old: str, the old folder name
:param new: str, the new folder name
:param torrent_id: the torrent_id
:type torrent_id: string
:param old: the old folder name
:type old: string
:param new: the new folder name
:type new: string
"""
self._args = [torrent_id, old, new]
@ -138,9 +150,12 @@ class TorrentFileRenamedEvent(DelugeEvent):
"""
def __init__(self, torrent_id, index, name):
"""
:param torrent_id: str, the torrent_id
:param index: int, the index of the file
:param name: str, the new filename
:param torrent_id: the torrent_id
:type torrent_id: string
:param index: the index of the file
:type index: int
:param name: the new filename
:type name: string
"""
self._args = [torrent_id, index, name]
@ -150,7 +165,8 @@ class TorrentFinishedEvent(DelugeEvent):
"""
def __init__(self, torrent_id):
"""
:param torrent_id: str, the torrent_id
:param torrent_id: the torrent_id
:type torrent_id: string
"""
self._args = [torrent_id]
@ -160,17 +176,42 @@ class TorrentResumedEvent(DelugeEvent):
"""
def __init__(self, torrent_id):
"""
:param torrent_id: str, the torrent_id
:param torrent_id: the torrent_id
:type torrent_id: string
"""
self._args = [torrent_id]
class TorrentFileCompletedEvent(DelugeEvent):
"""
Emitted when a file completes.
This will only work with libtorrent 0.15 or greater.
"""
def __init__(self, torrent_id, index):
"""
:param torrent_id: the torrent_id
:type torrent_id: string
:param index: the file index
:type index: int
"""
self._args = [torrent_id, index]
class CreateTorrentProgressEvent(DelugeEvent):
"""
Emitted when creating a torrent file remotely.
"""
def __init__(self, piece_count, num_pieces):
self._args = [piece_count, num_pieces]
class NewVersionAvailableEvent(DelugeEvent):
"""
Emitted when a more recent version of Deluge is available.
"""
def __init__(self, new_release):
"""
:param new_release: str, the new version that is available
:param new_release: the new version that is available
:type new_release: string
"""
self._args = [new_release]
@ -199,7 +240,8 @@ class ConfigValueChangedEvent(DelugeEvent):
"""
def __init__(self, key, value):
"""
:param key: str, the key that changed
:param key: the key that changed
:type key: string
:param value: the new value of the `:param:key`
"""
self._args = [key, value]
@ -208,20 +250,13 @@ class PluginEnabledEvent(DelugeEvent):
"""
Emitted when a plugin is enabled in the Core.
"""
def __init__(self, name):
"""
:param name: the plugin name
:type name: string
"""
self._args = [name]
def __init__(self, plugin_name):
self._args = [plugin_name]
class PluginDisabledEvent(DelugeEvent):
"""
Emitted when a plugin is disabled in the Core.
"""
def __init__(self, name):
"""
:param name: the plugin name
:type name: string
"""
self._args = [name]
def __init__(self, plugin_name):
self._args = [plugin_name]

View File

@ -36,21 +36,26 @@ from twisted.web import client, http
from twisted.web.error import PageRedirect
from twisted.python.failure import Failure
from twisted.internet import reactor
from deluge.log import setupLogger, LOG as log
from common import get_version
import logging
import os.path
import zlib
log = logging.getLogger(__name__)
class HTTPDownloader(client.HTTPDownloader):
"""
Factory class for downloading files and keeping track of progress.
"""
def __init__(self, url, filename, part_callback=None, headers=None, force_filename=False, allow_compression=True):
def __init__(self, url, filename, part_callback=None, headers=None,
force_filename=False, allow_compression=True):
"""
:param url: the url to download from
:type url: string
:param filename: the filename to save the file as
:type filename: string
:param force_filename: forces use of the supplied filename, regardless of header content
:type force_filename: bool
:param part_callback: a function to be called when a part of data
is received, it's signature should be: func(data, current_length, total_length)
:type part_callback: function
@ -84,15 +89,20 @@ class HTTPDownloader(client.HTTPDownloader):
self.decoder = zlib.decompressobj(zlib.MAX_WBITS + 32)
if "content-disposition" in headers and not self.force_filename:
try:
new_file_name = str(headers["content-disposition"][0]).split(";")[1].split("=")[1]
new_file_name = sanitise_filename(new_file_name)
new_file_name = os.path.join(os.path.split(self.fileName)[0], new_file_name)
except Exception, e:
log.exception(e)
else:
self.fileName = new_file_name
self.value = new_file_name
new_file_name = str(headers["content-disposition"][0]).split(";")[1].split("=")[1]
new_file_name = sanitise_filename(new_file_name)
new_file_name = os.path.join(os.path.split(self.fileName)[0], new_file_name)
count = 1
fileroot = os.path.splitext(new_file_name)[0]
fileext = os.path.splitext(new_file_name)[1]
while os.path.isfile(new_file_name):
# Increment filename if already exists
new_file_name = "%s-%s%s" % (fileroot, count, fileext)
count += 1
self.fileName = new_file_name
self.value = new_file_name
elif self.code in (http.MOVED_PERMANENTLY, http.FOUND, http.SEE_OTHER, http.TEMPORARY_REDIRECT):
location = headers["location"][0]
@ -129,8 +139,6 @@ def sanitise_filename(filename):
:type filename: string
:returns: the sanitised filename
:rtype: string
:raises IOError: when the filename exists
"""
# Remove any quotes
@ -141,18 +149,16 @@ def sanitise_filename(filename):
log.warning("Potentially malicious server: trying to write to file '%s'" % filename)
# Only use the basename
filename = os.path.basename(filename)
filename = filename.strip()
if filename.startswith(".") or ";" in filename or "|" in filename:
# Dodgy server, log it
log.warning("Potentially malicious server: trying to write to file '%s'" % filename)
if os.path.exists(filename):
raise IOError, "File '%s' already exists!" % filename
return filename
def download_file(url, filename, callback=None, headers=None, force_filename=False, allow_compression=True):
def download_file(url, filename, callback=None, headers=None,
force_filename=False, allow_compression=True):
"""
Downloads a file from a specific URL and returns a Deferred. You can also
specify a callback function to be called as parts are received.

View File

@ -1,290 +0,0 @@
deluge/configmanager.py
deluge/httpdownloader.py
deluge/error.py
deluge/component.py
deluge/log.py
deluge/metafile.py
deluge/config.py
deluge/main.py
deluge/__init__.py
deluge/common.py
deluge/bencode.py
deluge/pluginmanagerbase.py
deluge/event.py
deluge/rencode.py
deluge/decorators.py
deluge/_libtorrent.py
deluge/__rpcapi.py
deluge/maketorrent.py
deluge/plugins/__init__.py
deluge/plugins/pluginbase.py
deluge/plugins/init.py
deluge/plugins/feeder/setup.py
deluge/plugins/feeder/feeder/__init__.py
deluge/plugins/feeder/feeder/core.py
deluge/plugins/feeder/feeder/webui.py
deluge/plugins/feeder/build/lib/feeder/__init__.py
deluge/plugins/feeder/build/lib/feeder/core.py
deluge/plugins/feeder/build/lib/feeder/webui.py
deluge/plugins/label/setup.py
deluge/plugins/label/label/test.py
deluge/plugins/label/label/__init__.py
deluge/plugins/label/label/core.py
deluge/plugins/label/label/webui.py
deluge/plugins/label/label/gtkui/__init__.py
deluge/plugins/label/label/gtkui/label_config.py
deluge/plugins/label/label/gtkui/submenu.py
deluge/plugins/label/label/gtkui/sidebar_menu.py
deluge/plugins/label/label/data/label_pref.glade
deluge/plugins/label/label/data/label_options.glade
deluge/plugins/label/build/lib/label/test.py
deluge/plugins/label/build/lib/label/__init__.py
deluge/plugins/label/build/lib/label/core.py
deluge/plugins/label/build/lib/label/webui.py
deluge/plugins/label/build/lib/label/gtkui/__init__.py
deluge/plugins/label/build/lib/label/gtkui/label_config.py
deluge/plugins/label/build/lib/label/gtkui/submenu.py
deluge/plugins/label/build/lib/label/gtkui/sidebar_menu.py
deluge/plugins/label/build/lib/label/data/label_pref.glade
deluge/plugins/label/build/lib/label/data/label_options.glade
deluge/plugins/autoadd/setup.py
deluge/plugins/autoadd/autoadd/__init__.py
deluge/plugins/autoadd/autoadd/common.py
deluge/plugins/autoadd/autoadd/core.py
deluge/plugins/autoadd/autoadd/webui.py
deluge/plugins/autoadd/autoadd/gtkui.py
deluge/plugins/autoadd/autoadd/data/config.glade
deluge/plugins/autoadd/autoadd/data/autoadd_options.glade
deluge/plugins/autoadd/build/lib/autoadd/__init__.py
deluge/plugins/autoadd/build/lib/autoadd/common.py
deluge/plugins/autoadd/build/lib/autoadd/core.py
deluge/plugins/autoadd/build/lib/autoadd/webui.py
deluge/plugins/autoadd/build/lib/autoadd/gtkui.py
deluge/plugins/autoadd/build/lib/autoadd/data/config.glade
deluge/plugins/autoadd/build/lib/autoadd/data/autoadd_options.glade
deluge/plugins/scheduler/setup.py
deluge/plugins/scheduler/scheduler/__init__.py
deluge/plugins/scheduler/scheduler/common.py
deluge/plugins/scheduler/scheduler/core.py
deluge/plugins/scheduler/scheduler/webui.py
deluge/plugins/scheduler/scheduler/gtkui.py
deluge/plugins/scheduler/build/lib/scheduler/__init__.py
deluge/plugins/scheduler/build/lib/scheduler/common.py
deluge/plugins/scheduler/build/lib/scheduler/core.py
deluge/plugins/scheduler/build/lib/scheduler/webui.py
deluge/plugins/scheduler/build/lib/scheduler/gtkui.py
deluge/plugins/notifications/setup.py
deluge/plugins/notifications/notifications/test.py
deluge/plugins/notifications/notifications/__init__.py
deluge/plugins/notifications/notifications/common.py
deluge/plugins/notifications/notifications/core.py
deluge/plugins/notifications/notifications/webui.py
deluge/plugins/notifications/notifications/gtkui.py
deluge/plugins/notifications/notifications/data/config.glade
deluge/plugins/notifications/build/lib/notifications/test.py
deluge/plugins/notifications/build/lib/notifications/__init__.py
deluge/plugins/notifications/build/lib/notifications/common.py
deluge/plugins/notifications/build/lib/notifications/core.py
deluge/plugins/notifications/build/lib/notifications/webui.py
deluge/plugins/notifications/build/lib/notifications/gtkui.py
deluge/plugins/notifications/build/lib/notifications/data/config.glade
deluge/plugins/stats/setup.py
deluge/plugins/stats/stats/test_total.py
deluge/plugins/stats/stats/test.py
deluge/plugins/stats/stats/__init__.py
deluge/plugins/stats/stats/graph.py
deluge/plugins/stats/stats/common.py
deluge/plugins/stats/stats/core.py
deluge/plugins/stats/stats/webui.py
deluge/plugins/stats/stats/gtkui.py
deluge/plugins/stats/stats/data/tabs.glade
deluge/plugins/stats/stats/data/config.glade
deluge/plugins/stats/build/lib/stats/test_total.py
deluge/plugins/stats/build/lib/stats/test.py
deluge/plugins/stats/build/lib/stats/__init__.py
deluge/plugins/stats/build/lib/stats/graph.py
deluge/plugins/stats/build/lib/stats/common.py
deluge/plugins/stats/build/lib/stats/core.py
deluge/plugins/stats/build/lib/stats/webui.py
deluge/plugins/stats/build/lib/stats/gtkui.py
deluge/plugins/stats/build/lib/stats/data/tabs.glade
deluge/plugins/stats/build/lib/stats/data/config.glade
deluge/plugins/webui/setup.py
deluge/plugins/webui/webui/__init__.py
deluge/plugins/webui/webui/common.py
deluge/plugins/webui/webui/core.py
deluge/plugins/webui/webui/gtkui.py
deluge/plugins/webui/webui/data/config.glade
deluge/plugins/webui/build/lib/webui/__init__.py
deluge/plugins/webui/build/lib/webui/common.py
deluge/plugins/webui/build/lib/webui/core.py
deluge/plugins/webui/build/lib/webui/gtkui.py
deluge/plugins/webui/build/lib/webui/data/config.glade
deluge/plugins/extractor/setup.py
deluge/plugins/extractor/build/lib/extractor/__init__.py
deluge/plugins/extractor/build/lib/extractor/common.py
deluge/plugins/extractor/build/lib/extractor/core.py
deluge/plugins/extractor/build/lib/extractor/webui.py
deluge/plugins/extractor/build/lib/extractor/gtkui.py
deluge/plugins/extractor/build/lib/extractor/data/extractor_prefs.glade
deluge/plugins/extractor/extractor/__init__.py
deluge/plugins/extractor/extractor/common.py
deluge/plugins/extractor/extractor/core.py
deluge/plugins/extractor/extractor/webui.py
deluge/plugins/extractor/extractor/gtkui.py
deluge/plugins/extractor/extractor/data/extractor_prefs.glade
deluge/plugins/execute/setup.py
deluge/plugins/execute/build/lib/execute/__init__.py
deluge/plugins/execute/build/lib/execute/common.py
deluge/plugins/execute/build/lib/execute/core.py
deluge/plugins/execute/build/lib/execute/webui.py
deluge/plugins/execute/build/lib/execute/gtkui.py
deluge/plugins/execute/build/lib/execute/data/execute_prefs.glade
deluge/plugins/execute/execute/__init__.py
deluge/plugins/execute/execute/common.py
deluge/plugins/execute/execute/core.py
deluge/plugins/execute/execute/webui.py
deluge/plugins/execute/execute/gtkui.py
deluge/plugins/execute/execute/data/execute_prefs.glade
deluge/plugins/example/setup.py
deluge/plugins/example/build/lib/example/__init__.py
deluge/plugins/example/build/lib/example/common.py
deluge/plugins/example/build/lib/example/core.py
deluge/plugins/example/build/lib/example/webui.py
deluge/plugins/example/build/lib/example/gtkui.py
deluge/plugins/example/example/__init__.py
deluge/plugins/example/example/common.py
deluge/plugins/example/example/core.py
deluge/plugins/example/example/webui.py
deluge/plugins/example/example/gtkui.py
deluge/plugins/freespace/setup.py
deluge/plugins/freespace/build/lib/freespace/__init__.py
deluge/plugins/freespace/build/lib/freespace/common.py
deluge/plugins/freespace/build/lib/freespace/core.py
deluge/plugins/freespace/build/lib/freespace/webui.py
deluge/plugins/freespace/build/lib/freespace/gtkui.py
deluge/plugins/freespace/build/lib/freespace/data/config.glade
deluge/plugins/freespace/freespace/__init__.py
deluge/plugins/freespace/freespace/common.py
deluge/plugins/freespace/freespace/core.py
deluge/plugins/freespace/freespace/webui.py
deluge/plugins/freespace/freespace/gtkui.py
deluge/plugins/freespace/freespace/data/config.glade
deluge/plugins/blocklist/setup.py
deluge/plugins/blocklist/build/lib/blocklist/peerguardian.py
deluge/plugins/blocklist/build/lib/blocklist/decompressers.py
deluge/plugins/blocklist/build/lib/blocklist/detect.py
deluge/plugins/blocklist/build/lib/blocklist/readers.py
deluge/plugins/blocklist/build/lib/blocklist/__init__.py
deluge/plugins/blocklist/build/lib/blocklist/common.py
deluge/plugins/blocklist/build/lib/blocklist/core.py
deluge/plugins/blocklist/build/lib/blocklist/webui.py
deluge/plugins/blocklist/build/lib/blocklist/gtkui.py
deluge/plugins/blocklist/build/lib/blocklist/data/blocklist_pref.glade
deluge/plugins/blocklist/blocklist/peerguardian.py
deluge/plugins/blocklist/blocklist/decompressers.py
deluge/plugins/blocklist/blocklist/detect.py
deluge/plugins/blocklist/blocklist/readers.py
deluge/plugins/blocklist/blocklist/__init__.py
deluge/plugins/blocklist/blocklist/common.py
deluge/plugins/blocklist/blocklist/core.py
deluge/plugins/blocklist/blocklist/webui.py
deluge/plugins/blocklist/blocklist/gtkui.py
deluge/plugins/blocklist/blocklist/data/blocklist_pref.glade
deluge/core/eventmanager.py
deluge/core/autoadd.py
deluge/core/authmanager.py
deluge/core/rpcserver.py
deluge/core/torrentmanager.py
deluge/core/oldstateupgrader.py
deluge/core/__init__.py
deluge/core/torrent.py
deluge/core/pluginmanager.py
deluge/core/core.py
deluge/core/daemon.py
deluge/core/alertmanager.py
deluge/core/preferencesmanager.py
deluge/core/filtermanager.py
deluge/ui/sessionproxy.py
deluge/ui/ui.py
deluge/ui/session.py
deluge/ui/tracker_icons.py
deluge/ui/__init__.py
deluge/ui/common.py
deluge/ui/Win32IconImagePlugin.py
deluge/ui/client.py
deluge/ui/countries.py
deluge/ui/coreconfig.py
deluge/ui/web/server.py
deluge/ui/web/web.py
deluge/ui/web/__init__.py
deluge/ui/web/common.py
deluge/ui/web/pluginmanager.py
deluge/ui/web/gen_gettext.py
deluge/ui/web/auth.py
deluge/ui/web/json_api.py
deluge/ui/gtkui/connectionmanager.py
deluge/ui/gtkui/torrentdetails.py
deluge/ui/gtkui/queuedtorrents.py
deluge/ui/gtkui/addtorrentdialog.py
deluge/ui/gtkui/__init__.py
deluge/ui/gtkui/status_tab.py
deluge/ui/gtkui/preferences.py
deluge/ui/gtkui/mainwindow.py
deluge/ui/gtkui/notification.py
deluge/ui/gtkui/ipcinterface.py
deluge/ui/gtkui/createtorrentdialog.py
deluge/ui/gtkui/torrentview.py
deluge/ui/gtkui/listview.py
deluge/ui/gtkui/systemtray.py
deluge/ui/gtkui/common.py
deluge/ui/gtkui/pluginmanager.py
deluge/ui/gtkui/menubar.py
deluge/ui/gtkui/sidebar.py
deluge/ui/gtkui/statusbar.py
deluge/ui/gtkui/filtertreeview.py
deluge/ui/gtkui/new_release_dialog.py
deluge/ui/gtkui/options_tab.py
deluge/ui/gtkui/peers_tab.py
deluge/ui/gtkui/details_tab.py
deluge/ui/gtkui/files_tab.py
deluge/ui/gtkui/gtkui.py
deluge/ui/gtkui/edittrackersdialog.py
deluge/ui/gtkui/removetorrentdialog.py
deluge/ui/gtkui/toolbar.py
deluge/ui/gtkui/dialogs.py
deluge/ui/gtkui/aboutdialog.py
deluge/ui/gtkui/glade/filtertree_menu.glade
deluge/ui/gtkui/glade/main_window.glade
deluge/ui/gtkui/glade/remove_torrent_dialog.glade
deluge/ui/gtkui/glade/create_torrent_dialog.glade
deluge/ui/gtkui/glade/connection_manager.glade
deluge/ui/gtkui/glade/preferences_dialog.glade
deluge/ui/gtkui/glade/torrent_menu.glade
deluge/ui/gtkui/glade/queuedtorrents.glade
deluge/ui/gtkui/glade/move_storage_dialog.glade
deluge/ui/gtkui/glade/add_torrent_dialog.glade
deluge/ui/gtkui/glade/dgtkpopups.glade
deluge/ui/gtkui/glade/tray_menu.glade
deluge/ui/gtkui/glade/edit_trackers.glade
deluge/ui/console/colors.py
deluge/ui/console/eventlog.py
deluge/ui/console/main.py
deluge/ui/console/__init__.py
deluge/ui/console/statusbars.py
deluge/ui/console/screen.py
deluge/ui/console/commands/plugin.py
deluge/ui/console/commands/info.py
deluge/ui/console/commands/recheck.py
deluge/ui/console/commands/quit.py
deluge/ui/console/commands/connect.py
deluge/ui/console/commands/help.py
deluge/ui/console/commands/add.py
deluge/ui/console/commands/config.py
deluge/ui/console/commands/__init__.py
deluge/ui/console/commands/cache.py
deluge/ui/console/commands/debug.py
deluge/ui/console/commands/pause.py
deluge/ui/console/commands/rm.py
deluge/ui/console/commands/halt.py
deluge/ui/console/commands/resume.py

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
# log.py
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@ -33,18 +34,101 @@
#
#
"""Logging functions"""
import os
import inspect
import logging
from deluge import common
from twisted.internet import defer
from twisted.python.log import PythonLoggingObserver
__all__ = ["setupLogger", "setLoggerLevel", "getPluginLogger", "LOG"]
LoggingLoggerClass = logging.getLoggerClass()
if 'dev' in common.get_version():
DEFAULT_LOGGING_FORMAT = "%%(asctime)s.%%(msecs)03.0f [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s"
else:
DEFAULT_LOGGING_FORMAT = "%%(asctime)s [%%(levelname)-8s][%%(name)-%ds] %%(message)s"
MAX_LOGGER_NAME_LENGTH = 10
class Logging(LoggingLoggerClass):
def __init__(self, logger_name):
LoggingLoggerClass.__init__(self, logger_name)
# This makes module name padding increase to the biggest module name
# so that logs keep readability.
global MAX_LOGGER_NAME_LENGTH
if len(logger_name) > MAX_LOGGER_NAME_LENGTH:
MAX_LOGGER_NAME_LENGTH = len(logger_name)
for handler in logging.getLogger().handlers:
handler.setFormatter(logging.Formatter(
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
datefmt="%H:%M:%S"
))
@defer.inlineCallbacks
def garbage(self, msg, *args, **kwargs):
yield LoggingLoggerClass.log(self, 1, msg, *args, **kwargs)
@defer.inlineCallbacks
def trace(self, msg, *args, **kwargs):
yield LoggingLoggerClass.log(self, 5, msg, *args, **kwargs)
@defer.inlineCallbacks
def debug(self, msg, *args, **kwargs):
yield LoggingLoggerClass.debug(self, msg, *args, **kwargs)
@defer.inlineCallbacks
def info(self, msg, *args, **kwargs):
yield LoggingLoggerClass.info(self, msg, *args, **kwargs)
@defer.inlineCallbacks
def warning(self, msg, *args, **kwargs):
yield LoggingLoggerClass.warning(self, msg, *args, **kwargs)
warn = warning
@defer.inlineCallbacks
def error(self, msg, *args, **kwargs):
yield LoggingLoggerClass.error(self, msg, *args, **kwargs)
@defer.inlineCallbacks
def critical(self, msg, *args, **kwargs):
yield LoggingLoggerClass.critical(self, msg, *args, **kwargs)
@defer.inlineCallbacks
def exception(self, msg, *args, **kwargs):
yield LoggingLoggerClass.exception(self, msg, *args, **kwargs)
def findCaller(self):
f = logging.currentframe().f_back
rv = "(unknown file)", 0, "(unknown function)"
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename in (__file__.replace('.pyc', '.py'),
defer.__file__.replace('.pyc', '.py')):
f = f.f_back
continue
rv = (filename, f.f_lineno, co.co_name)
break
return rv
levels = {
"none": logging.NOTSET,
"info": logging.INFO,
"warn": logging.WARNING,
"warning": logging.WARNING,
"error": logging.ERROR,
"none": logging.CRITICAL,
"debug": logging.DEBUG
"debug": logging.DEBUG,
"trace": 5,
"garbage": 1
}
def setupLogger(level="error", filename=None, filemode="w"):
"""
Sets up the basic logger and if `:param:filename` is set, then it will log
@ -53,30 +137,168 @@ def setupLogger(level="error", filename=None, filemode="w"):
:param level: str, the level to log
:param filename: str, the file to log to
"""
import logging
if not level or level not in levels:
level = "error"
if logging.getLoggerClass() is not Logging:
logging.setLoggerClass(Logging)
logging.addLevelName(5, 'TRACE')
logging.addLevelName(1, 'GARBAGE')
logging.basicConfig(
level=levels[level],
format="[%(levelname)-8s] %(asctime)s %(module)s:%(lineno)d %(message)s",
datefmt="%H:%M:%S",
filename=filename,
filemode=filemode
level = levels.get(level, logging.ERROR)
rootLogger = logging.getLogger()
if filename and filemode=='a':
import logging.handlers
handler = logging.handlers.RotatingFileHandler(
filename, filemode,
maxBytes=50*1024*1024, # 50 Mb
backupCount=3,
encoding='utf-8',
delay=0
)
elif filename and filemode=='w':
import logging.handlers
handler = getattr(
logging.handlers, 'WatchedFileHandler', logging.FileHandler)(
filename, filemode, 'utf-8', delay=0
)
else:
handler = logging.StreamHandler()
handler.setLevel(level)
formatter = logging.Formatter(
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
datefmt="%H:%M:%S"
)
def setLoggerLevel(level):
handler.setFormatter(formatter)
rootLogger.addHandler(handler)
rootLogger.setLevel(level)
twisted_logging = PythonLoggingObserver('twisted')
twisted_logging.start()
logging.getLogger("twisted").setLevel(level)
def tweak_logging_levels():
"""This function allows tweaking the logging levels for all or some loggers.
This is mostly usefull for developing purposes hence the contents of the
file are NOT like regular deluge config file's.
To use is, create a file named "logging.conf" on your Deluge's config dir
with contents like for example:
deluge:warn
deluge.core:debug
deluge.plugin:error
What the above mean is the logger "deluge" will be set to the WARN level,
the "deluge.core" logger will be set to the DEBUG level and the
"deluge.plugin" will be set to the ERROR level.
Remember, one rule per line and this WILL override the setting passed from
the command line.
"""
from deluge import configmanager
logging_config_file = os.path.join(configmanager.get_config_dir(),
'logging.conf')
if not os.path.isfile(logging_config_file):
return
log = logging.getLogger(__name__)
log.warn("logging.conf found! tweaking logging levels from %s",
logging_config_file)
for line in open(logging_config_file, 'r').readlines():
if line.strip().startswith("#"):
continue
name, level = line.strip().split(':')
if level not in levels:
continue
log.warn("Setting logger \"%s\" to logging level \"%s\"", name, level)
setLoggerLevel(level, name)
def setLoggerLevel(level, logger_name=None):
"""
Sets the logger level.
:param level: str, a string representing the desired level
:param logger_name: str, a string representing desired logger name for which
the level should change. The default is "None" will will
tweak the root logger level.
"""
if level not in levels:
return
logging.getLogger(logger_name).setLevel(levels.get(level, "error"))
global LOG
LOG.setLevel(levels[level])
# Get the logger
LOG = logging.getLogger("deluge")
def getPluginLogger(logger_name):
import warnings
stack = inspect.stack()
stack.pop(0) # The logging call from this module
module_stack = stack.pop(0) # The module that called the log function
caller_module = inspect.getmodule(module_stack[0])
# In some weird cases caller_module might be None, try to continue
caller_module_name = getattr(caller_module, '__name__', '')
warnings.warn_explicit(DEPRECATION_WARNING, DeprecationWarning,
module_stack[1], module_stack[2],
caller_module_name)
if 'deluge.plugins.' in logger_name:
return logging.getLogger(logger_name)
return logging.getLogger("deluge.plugin.%s" % logger_name)
DEPRECATION_WARNING = """You seem to be using old style logging on your code, ie:
from deluge.log import LOG as log
or:
from deluge.log import getPluginLogger
This has been deprecated in favour of an enhanced logging system and both "LOG"
and "getPluginLogger" will be removed on the next major version release of Deluge,
meaning, code will break, specially plugins.
If you're seeing this message and you're not the developer of the plugin which
triggered this warning, please report to it's author.
If you're the developer, please stop using the above code and instead use:
import logging
log = logging.getLogger(__name__)
The above will result in, regarding the "Label" plugin for example a log message similar to:
15:33:54 [deluge.plugins.label.core:78 ][INFO ] *** Start Label plugin ***
Triggering code:"""
class __BackwardsCompatibleLOG(object):
def __getattribute__(self, name):
import warnings
logger_name = 'deluge'
stack = inspect.stack()
stack.pop(0) # The logging call from this module
module_stack = stack.pop(0) # The module that called the log function
caller_module = inspect.getmodule(module_stack[0])
# In some weird cases caller_module might be None, try to continue
caller_module_name = getattr(caller_module, '__name__', '')
warnings.warn_explicit(DEPRECATION_WARNING, DeprecationWarning,
module_stack[1], module_stack[2],
caller_module_name)
if caller_module:
for member in stack:
module = inspect.getmodule(member[0])
if not module:
continue
if module.__name__ in ('deluge.plugins.pluginbase',
'deluge.plugins.init'):
logger_name += '.plugin.%s' % caller_module_name
# Monkey Patch The Plugin Module
caller_module.log = logging.getLogger(logger_name)
break
else:
logging.getLogger(logger_name).warning(
"Unable to monkey-patch the calling module's `log` attribute! "
"You should really update and rebuild your plugins..."
)
return getattr(logging.getLogger(logger_name), name)
LOG = __BackwardsCompatibleLOG()

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