├── .gitignore
├── create_dev_link.sh
├── autoadd
├── data
│ ├── config.gladep
│ ├── autoadd_options.gladep
│ ├── autoadd.js
│ ├── config.glade
│ └── autoadd_options.glade
├── common.py
├── webui.py
├── __init__.py
├── core.py
└── gtkui.py
├── README.md
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | AutoAdd.egg-info/*
4 | *.pyc
5 | *.pyo
6 | *.bak
7 | *~
8 |
--------------------------------------------------------------------------------
/create_dev_link.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | mkdir temp
3 | export PYTHONPATH=./temp
4 | python setup.py build develop --install-dir ./temp
5 | cp ./temp/AutoAdd.egg-link ~/.config/deluge/plugins
6 | rm -fr ./temp
7 |
--------------------------------------------------------------------------------
/autoadd/data/config.gladep:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | FALSE
8 |
9 |
--------------------------------------------------------------------------------
/autoadd/data/autoadd_options.gladep:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | FALSE
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # deluge-autoadd-plugin
2 | Adds multiple watch folder support to Deluge.
3 |
4 | This plugin has been merged into deluge, find the latest source at the [deluge repo](https://github.com/deluge-torrent/deluge/tree/develop/deluge/plugins/AutoAdd).
5 |
6 | All bugs should be reported on the [official deluge bug tracker](https://dev.deluge-torrent.org/report/1).
7 |
--------------------------------------------------------------------------------
/autoadd/common.py:
--------------------------------------------------------------------------------
1 | #
2 | # common.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | def get_resource(filename):
41 | import pkg_resources, os
42 | return pkg_resources.resource_filename("autoadd", os.path.join("data", filename))
43 |
--------------------------------------------------------------------------------
/autoadd/data/autoadd.js:
--------------------------------------------------------------------------------
1 | /*
2 | Script: autoadd.js
3 | The client-side javascript code for the AutoAdd plugin.
4 |
5 | Copyright:
6 | (C) GazpachoKing 2009
7 | This program is free software; you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation; either version 3, or (at your option)
10 | any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, write to:
19 | The Free Software Foundation, Inc.,
20 | 51 Franklin Street, Fifth Floor
21 | Boston, MA 02110-1301, USA.
22 |
23 | In addition, as a special exception, the copyright holders give
24 | permission to link the code of portions of this program with the OpenSSL
25 | library.
26 | You must obey the GNU General Public License in all respects for all of
27 | the code used other than OpenSSL. If you modify file(s) with this
28 | exception, you may extend this exception to your version of the file(s),
29 | but you are not obligated to do so. If you do not wish to do so, delete
30 | this exception statement from your version. If you delete this exception
31 | statement from all source files in the program, then also delete it here.
32 | */
33 |
34 | AutoAddPlugin = Ext.extend(Deluge.Plugin, {
35 | constructor: function(config) {
36 | config = Ext.apply({
37 | name: "AutoAdd"
38 | }, config);
39 | AutoAddPlugin.superclass.constructor.call(this, config);
40 | },
41 |
42 | onDisable: function() {
43 |
44 | },
45 |
46 | onEnable: function() {
47 |
48 | }
49 | });
50 | new AutoAddPlugin();
51 |
--------------------------------------------------------------------------------
/autoadd/webui.py:
--------------------------------------------------------------------------------
1 | #
2 | # webui.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | from deluge.log import LOG as log
41 | from deluge.ui.client import client
42 | from deluge import component
43 | from deluge.plugins.pluginbase import WebPluginBase
44 |
45 | from common import get_resource
46 |
47 | class WebUI(WebPluginBase):
48 |
49 | scripts = [get_resource("autoadd.js")]
50 |
51 | def enable(self):
52 | pass
53 |
54 | def disable(self):
55 | pass
56 |
--------------------------------------------------------------------------------
/autoadd/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # __init__.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | from deluge.plugins.init import PluginInitBase
41 |
42 | class CorePlugin(PluginInitBase):
43 | def __init__(self, plugin_name):
44 | from core import Core as _plugin_cls
45 | self._plugin_cls = _plugin_cls
46 | super(CorePlugin, self).__init__(plugin_name)
47 |
48 | class GtkUIPlugin(PluginInitBase):
49 | def __init__(self, plugin_name):
50 | from gtkui import GtkUI as _plugin_cls
51 | self._plugin_cls = _plugin_cls
52 | super(GtkUIPlugin, self).__init__(plugin_name)
53 |
54 | class WebUIPlugin(PluginInitBase):
55 | def __init__(self, plugin_name):
56 | from webui import WebUI as _plugin_cls
57 | self._plugin_cls = _plugin_cls
58 | super(WebUIPlugin, self).__init__(plugin_name)
59 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #
2 | # setup.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | from setuptools import setup
41 |
42 | __plugin_name__ = "AutoAdd"
43 | __author__ = "Chase Sterling"
44 | __author_email__ = "chase.sterling@gmail.com"
45 | __version__ = "1.02"
46 | __url__ = "http://forum.deluge-torrent.org/viewtopic.php?f=9&t=26775"
47 | __license__ = "GPLv3"
48 | __description__ = "Monitors folders for .torrent files."
49 | __long_description__ = """"""
50 | __pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
51 |
52 | setup(
53 | name=__plugin_name__,
54 | version=__version__,
55 | description=__description__,
56 | author=__author__,
57 | author_email=__author_email__,
58 | url=__url__,
59 | license=__license__,
60 | long_description=__long_description__ if __long_description__ else __description__,
61 |
62 | packages=[__plugin_name__.lower()],
63 | package_data = __pkg_data__,
64 |
65 | entry_points="""
66 | [deluge.plugin.core]
67 | %s = %s:CorePlugin
68 | [deluge.plugin.gtkui]
69 | %s = %s:GtkUIPlugin
70 | [deluge.plugin.webui]
71 | %s = %s:WebUIPlugin
72 | """ % ((__plugin_name__, __plugin_name__.lower())*3)
73 | )
74 |
--------------------------------------------------------------------------------
/autoadd/data/config.glade:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | True
9 |
10 |
11 |
12 |
13 |
14 | True
15 |
16 |
17 | 340
18 | 390
19 | True
20 | 3
21 | vertical
22 |
23 |
24 |
25 | True
26 | 0
27 | none
28 |
29 |
30 | True
31 | vertical
32 | True
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | True
41 | <b>Watch Folders:</b>
42 | True
43 |
44 |
45 | label_item
46 |
47 |
48 |
49 |
50 | 0
51 |
52 |
53 |
54 |
55 | True
56 |
57 |
58 | gtk-add
59 | True
60 | True
61 | True
62 | True
63 | True
64 |
65 |
66 |
67 | 0
68 |
69 |
70 |
71 |
72 | gtk-remove
73 | True
74 | False
75 | True
76 | True
77 | True
78 | True
79 |
80 |
81 |
82 | 1
83 |
84 |
85 |
86 |
87 | gtk-edit
88 | True
89 | False
90 | True
91 | True
92 | True
93 | True
94 |
95 |
96 |
97 | 2
98 |
99 |
100 |
101 |
102 | False
103 | 1
104 |
105 |
106 |
107 |
108 |
109 |
110 | 1
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/autoadd/core.py:
--------------------------------------------------------------------------------
1 | #
2 | # core.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | from deluge._libtorrent import lt
41 | import os
42 | from deluge.log import LOG as log
43 | from deluge.plugins.pluginbase import CorePluginBase
44 | import deluge.component as component
45 | import deluge.configmanager
46 | from deluge.core.rpcserver import export
47 | from twisted.internet.task import LoopingCall, deferLater
48 | from twisted.internet import reactor
49 | from deluge.event import DelugeEvent
50 |
51 | DEFAULT_PREFS = {
52 | "watchdirs":{},
53 | "next_id":1
54 | }
55 |
56 | OPTIONS_AVAILABLE = { #option: builtin
57 | "enabled":False,
58 | "path":False,
59 | "append_extension":False,
60 | "abspath":False,
61 | "download_location":True,
62 | "max_download_speed":True,
63 | "max_upload_speed":True,
64 | "max_connections":True,
65 | "max_upload_slots":True,
66 | "prioritize_first_last":True,
67 | "auto_managed":True,
68 | "stop_at_ratio":True,
69 | "stop_ratio":True,
70 | "remove_at_ratio":True,
71 | "move_completed":True,
72 | "move_completed_path":True,
73 | "label":False,
74 | "add_paused":True,
75 | "queue_to_top":False
76 | }
77 |
78 | MAX_NUM_ATTEMPTS = 10
79 |
80 | class AutoaddOptionsChangedEvent(DelugeEvent):
81 | """Emitted when the options for the plugin are changed."""
82 | def __init__(self):
83 | pass
84 |
85 | def CheckInput(cond, message):
86 | if not cond:
87 | raise Exception(message)
88 |
89 | class Core(CorePluginBase):
90 | def enable(self):
91 |
92 | #reduce typing, assigning some values to self...
93 | self.config = deluge.configmanager.ConfigManager("autoadd.conf", DEFAULT_PREFS)
94 | self.watchdirs = self.config["watchdirs"]
95 | self.core_cfg = deluge.configmanager.ConfigManager("core.conf")
96 |
97 | # Dict of Filename:Attempts
98 | self.invalid_torrents = {}
99 | # Loopingcall timers for each enabled watchdir
100 | self.update_timers = {}
101 | # If core autoadd folder is enabled, move it to the plugin
102 | if self.core_cfg.config.get('autoadd_enable'):
103 | # Disable core autoadd
104 | self.core_cfg['autoadd_enable'] = False
105 | self.core_cfg.save()
106 | # Check if core autoadd folder is already added in plugin
107 | for watchdir in self.watchdirs:
108 | if os.path.abspath(self.core_cfg['autoadd_location']) == watchdir['abspath']:
109 | watchdir['enabled'] = True
110 | break
111 | else:
112 | # didn't find core watchdir, add it
113 | self.add({'path':self.core_cfg['autoadd_location'], 'enabled':True})
114 | deferLater(reactor, 5, self.enable_looping)
115 |
116 | def enable_looping(self):
117 | #Enable all looping calls for enabled watchdirs here
118 | for watchdir_id, watchdir in self.watchdirs.iteritems():
119 | if watchdir['enabled']:
120 | self.enable_watchdir(watchdir_id)
121 |
122 | def disable(self):
123 | #disable all running looping calls
124 | for loopingcall in self.update_timers.itervalues():
125 | loopingcall.stop()
126 | self.config.save()
127 |
128 | def update(self):
129 | pass
130 |
131 | @export()
132 | def set_options(self, watchdir_id, options):
133 | """Update the options for a watch folder."""
134 | watchdir_id = str(watchdir_id)
135 | options = self._make_unicode(options)
136 | CheckInput(watchdir_id in self.watchdirs , _("Watch folder does not exist."))
137 | if options.has_key('path'):
138 | options['abspath'] = os.path.abspath(options['path'])
139 | CheckInput(os.path.isdir(options['abspath']), _("Path does not exist."))
140 | for w_id, w in self.watchdirs.iteritems():
141 | if options['abspath'] == w['abspath'] and watchdir_id != w_id:
142 | raise Exception("Path is already being watched.")
143 | for key in options.keys():
144 | if not key in OPTIONS_AVAILABLE:
145 | if not key in [key2+'_toggle' for key2 in OPTIONS_AVAILABLE.iterkeys()]:
146 | raise Exception("autoadd: Invalid options key:%s" % key)
147 | #disable the watch loop if it was active
148 | if watchdir_id in self.update_timers:
149 | self.disable_watchdir(watchdir_id)
150 |
151 | self.watchdirs[watchdir_id].update(options)
152 | #re-enable watch loop if appropriate
153 | if self.watchdirs[watchdir_id]['enabled']:
154 | self.enable_watchdir(watchdir_id)
155 | self.config.save()
156 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
157 |
158 | def load_torrent(self, filename):
159 | try:
160 | log.debug("Attempting to open %s for add.", filename)
161 | _file = open(filename, "rb")
162 | filedump = _file.read()
163 | if not filedump:
164 | raise RuntimeError, "Torrent is 0 bytes!"
165 | _file.close()
166 | except IOError, e:
167 | log.warning("Unable to open %s: %s", filename, e)
168 | raise e
169 |
170 | # Get the info to see if any exceptions are raised
171 | info = lt.torrent_info(lt.bdecode(filedump))
172 |
173 | return filedump
174 |
175 | def update_watchdir(self, watchdir_id):
176 | """Check the watch folder for new torrents to add."""
177 | watchdir_id = str(watchdir_id)
178 | watchdir = self.watchdirs[watchdir_id]
179 | if not watchdir['enabled']:
180 | # We shouldn't be updating because this watchdir is not enabled
181 | self.disable_watchdir(watchdir_id)
182 | return
183 |
184 | if not os.path.isdir(watchdir["abspath"]):
185 | log.warning("Invalid AutoAdd folder: %s", watchdir["abspath"])
186 | self.disable_watchdir(watchdir_id)
187 | return
188 |
189 | # Generate options dict for watchdir
190 | opts = {}
191 | if 'stop_at_ratio_toggle' in watchdir:
192 | watchdir['stop_ratio_toggle'] = watchdir['stop_at_ratio_toggle']
193 | # We default to True wher reading _toggle values, so a config
194 | # without them is valid, and applies all its settings.
195 | for option, value in watchdir.iteritems():
196 | if OPTIONS_AVAILABLE.get(option):
197 | if watchdir.get(option+'_toggle', True):
198 | opts[option] = value
199 | for filename in os.listdir(watchdir["abspath"]):
200 | if filename.split(".")[-1] == "torrent":
201 | try:
202 | filepath = os.path.join(watchdir["abspath"], filename)
203 | except UnicodeDecodeError, e:
204 | log.error("Unable to auto add torrent due to inproper filename encoding: %s", e)
205 | continue
206 | try:
207 | filedump = self.load_torrent(filepath)
208 | except (RuntimeError, Exception), e:
209 | # If the torrent is invalid, we keep track of it so that we
210 | # can try again on the next pass. This is because some
211 | # torrents may not be fully saved during the pass.
212 | log.debug("Torrent is invalid: %s", e)
213 | if filename in self.invalid_torrents:
214 | self.invalid_torrents[filename] += 1
215 | if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
216 | os.rename(filepath, filepath + ".invalid")
217 | del self.invalid_torrents[filename]
218 | else:
219 | self.invalid_torrents[filename] = 1
220 | continue
221 |
222 | # The torrent looks good, so lets add it to the session.
223 | torrent_id = component.get("TorrentManager").add(filedump=filedump, filename=filename, options=opts)
224 | # If the torrent added successfully, set the extra options.
225 | if torrent_id:
226 | if 'Label' in component.get("CorePluginManager").get_enabled_plugins():
227 | if watchdir.get('label_toggle', True) and watchdir.get('label'):
228 | label = component.get("CorePlugin.Label")
229 | if not watchdir['label'] in label.get_labels():
230 | label.add(watchdir['label'])
231 | label.set_torrent(torrent_id, watchdir['label'])
232 | if watchdir.get('queue_to_top_toggle', True) and 'queue_to_top' in watchdir:
233 | if watchdir['queue_to_top']:
234 | component.get("TorrentManager").queue_top(torrent_id)
235 | else:
236 | component.get("TorrentManager").queue_bottom(torrent_id)
237 | # Rename or delete the torrent once added to deluge.
238 | if watchdir.get('append_extension_toggle'):
239 | if not watchdir.get('append_extension'):
240 | watchdir['append_extension'] = ".added"
241 | os.rename(filepath, filepath + watchdir['append_extension'])
242 | else:
243 | os.remove(filepath)
244 |
245 | def on_update_watchdir_error(self, failure, watchdir_id):
246 | """Disables any watch folders with unhandled exceptions."""
247 | self.disable_watchdir(watchdir_id)
248 | log.error("Disabling '%s', error during update: %s" % (self.watchdirs[watchdir_id]["path"], failure))
249 |
250 | @export
251 | def enable_watchdir(self, watchdir_id):
252 | watchdir_id = str(watchdir_id)
253 | # Enable the looping call
254 | if watchdir_id not in self.update_timers or not self.update_timers[watchdir_id].running:
255 | self.update_timers[watchdir_id] = LoopingCall(self.update_watchdir, watchdir_id)
256 | self.update_timers[watchdir_id].start(5).addErrback(self.on_update_watchdir_error, watchdir_id)
257 | # Update the config
258 | if not self.watchdirs[watchdir_id]['enabled']:
259 | self.watchdirs[watchdir_id]['enabled'] = True
260 | self.config.save()
261 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
262 |
263 | @export
264 | def disable_watchdir(self, watchdir_id):
265 | watchdir_id = str(watchdir_id)
266 | # Disable the looping call
267 | if watchdir_id in self.update_timers:
268 | if self.update_timers[watchdir_id].running:
269 | self.update_timers[watchdir_id].stop()
270 | del self.update_timers[watchdir_id]
271 | # Update the config
272 | if self.watchdirs[watchdir_id]['enabled']:
273 | self.watchdirs[watchdir_id]['enabled'] = False
274 | self.config.save()
275 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
276 |
277 | @export
278 | def set_config(self, config):
279 | """Sets the config dictionary."""
280 | config = self._make_unicode(config)
281 | for key in config.keys():
282 | self.config[key] = config[key]
283 | self.config.save()
284 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
285 |
286 | @export
287 | def get_config(self):
288 | """Returns the config dictionary."""
289 | return self.config.config
290 |
291 | @export()
292 | def get_watchdirs(self):
293 | return self.watchdirs.keys()
294 |
295 | def _make_unicode(self, options):
296 | opts = {}
297 | for key in options:
298 | if isinstance(options[key], str):
299 | options[key] = unicode(options[key], "utf8")
300 | opts[key] = options[key]
301 | return opts
302 |
303 | @export()
304 | def add(self, options={}):
305 | """Add a watch folder."""
306 | options = self._make_unicode(options)
307 | abswatchdir = os.path.abspath(options['path'])
308 | CheckInput(os.path.isdir(abswatchdir) , _("Path does not exist."))
309 | CheckInput(os.access(abswatchdir, os.R_OK|os.W_OK), "You must have read and write access to watch folder.")
310 | if abswatchdir in [wd['abspath'] for wd in self.watchdirs.itervalues()]:
311 | raise Exception("Path is already being watched.")
312 | options.setdefault('enabled', False)
313 | options['abspath'] = abswatchdir
314 | watchdir_id = self.config['next_id']
315 | self.watchdirs[str(watchdir_id)] = options
316 | if options.get('enabled'):
317 | self.enable_watchdir(watchdir_id)
318 | self.config['next_id'] = watchdir_id + 1
319 | self.config.save()
320 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
321 | return watchdir_id
322 |
323 | @export
324 | def remove(self, watchdir_id):
325 | """Remove a watch folder."""
326 | watchdir_id = str(watchdir_id)
327 | CheckInput(watchdir_id in self.watchdirs, "Unknown Watchdir: %s" % self.watchdirs)
328 | if self.watchdirs[watchdir_id]['enabled']:
329 | self.disable_watchdir(watchdir_id)
330 | del self.watchdirs[watchdir_id]
331 | self.config.save()
332 | component.get("EventManager").emit(AutoaddOptionsChangedEvent())
333 |
--------------------------------------------------------------------------------
/autoadd/gtkui.py:
--------------------------------------------------------------------------------
1 | #
2 | # gtkui.py
3 | #
4 | # Copyright (C) 2009 GazpachoKing
5 | #
6 | # Basic plugin template created by:
7 | # Copyright (C) 2008 Martijn Voncken
8 | # Copyright (C) 2007-2009 Andrew Resch
9 | # Copyright (C) 2009 Damien Churchill
10 | #
11 | # Deluge is free software.
12 | #
13 | # You may redistribute it and/or modify it under the terms of the
14 | # GNU General Public License, as published by the Free Software
15 | # Foundation; either version 3 of the License, or (at your option)
16 | # any later version.
17 | #
18 | # deluge is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 | # See the GNU General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU General Public License
24 | # along with deluge. If not, write to:
25 | # The Free Software Foundation, Inc.,
26 | # 51 Franklin Street, Fifth Floor
27 | # Boston, MA 02110-1301, USA.
28 | #
29 | # In addition, as a special exception, the copyright holders give
30 | # permission to link the code of portions of this program with the OpenSSL
31 | # library.
32 | # You must obey the GNU General Public License in all respects for all of
33 | # the code used other than OpenSSL. If you modify file(s) with this
34 | # exception, you may extend this exception to your version of the file(s),
35 | # but you are not obligated to do so. If you do not wish to do so, delete
36 | # this exception statement from your version. If you delete this exception
37 | # statement from all source files in the program, then also delete it here.
38 | #
39 |
40 | import gtk
41 |
42 | from deluge.log import LOG as log
43 | from deluge.ui.client import client
44 | from deluge.plugins.pluginbase import GtkPluginBase
45 | import deluge.component as component
46 | import deluge.common
47 | import os
48 |
49 | from common import get_resource
50 |
51 | class OptionsDialog():
52 | spin_ids = ["max_download_speed", "max_upload_speed", "stop_ratio"]
53 | spin_int_ids = ["max_upload_slots", "max_connections"]
54 | chk_ids = ["stop_at_ratio", "remove_at_ratio", "move_completed", "add_paused", "auto_managed", "queue_to_top"]
55 | def __init__(self):
56 | pass
57 |
58 | def show(self, options={}, watchdir_id=None):
59 | self.glade = gtk.glade.XML(get_resource("autoadd_options.glade"))
60 | self.glade.signal_autoconnect({
61 | "on_opts_add":self.on_add,
62 | "on_opts_apply":self.on_apply,
63 | "on_opts_cancel":self.on_cancel,
64 | "on_options_dialog_close":self.on_cancel,
65 | "on_error_ok":self.on_error_ok,
66 | "on_error_dialog_close":self.on_error_ok,
67 | "on_toggle_toggled":self.on_toggle_toggled
68 | })
69 | self.dialog = self.glade.get_widget("options_dialog")
70 | self.dialog.set_transient_for(component.get("Preferences").pref_dialog)
71 | self.err_dialog = self.glade.get_widget("error_dialog")
72 | self.err_dialog.set_transient_for(self.dialog)
73 | if watchdir_id:
74 | #We have an existing watchdir_id, we are editing
75 | self.glade.get_widget('opts_add_button').hide()
76 | self.glade.get_widget('opts_apply_button').show()
77 | self.watchdir_id = watchdir_id
78 | else:
79 | #We don't have an id, adding
80 | self.glade.get_widget('opts_add_button').show()
81 | self.glade.get_widget('opts_apply_button').hide()
82 | self.watchdir_id = None
83 |
84 | self.load_options(options)
85 | self.dialog.run()
86 |
87 | def load_options(self, options):
88 | self.glade.get_widget('enabled').set_active(options.get('enabled', False))
89 | self.glade.get_widget('append_extension_toggle').set_active(options.get('append_extension_toggle', False))
90 | self.glade.get_widget('append_extension').set_text(options.get('append_extension', '.added'))
91 | self.glade.get_widget('download_location_toggle').set_active(options.get('download_location_toggle', False))
92 | self.glade.get_widget('label').set_text(options.get('label', ''))
93 | self.glade.get_widget('label_toggle').set_active(options.get('label_toggle', False))
94 | for id in self.spin_ids + self.spin_int_ids:
95 | self.glade.get_widget(id).set_value(options.get(id, 0))
96 | self.glade.get_widget(id+'_toggle').set_active(options.get(id+'_toggle', False))
97 | for id in self.chk_ids:
98 | self.glade.get_widget(id).set_active(bool(options.get(id, True)))
99 | self.glade.get_widget(id+'_toggle').set_active(options.get(id+'_toggle', False))
100 | if not options.get('add_paused', True):
101 | self.glade.get_widget('isnt_add_paused').set_active(True)
102 | if not options.get('queue_to_top', True):
103 | self.glade.get_widget('isnt_queue_to_top').set_active(True)
104 | if not options.get('auto_managed', True):
105 | self.glade.get_widget('isnt_auto_managed').set_active(True)
106 | for field in ['move_completed_path', 'path', 'download_location']:
107 | if client.is_localhost():
108 | self.glade.get_widget(field+"_chooser").set_filename(options.get(field, os.path.expanduser("~")))
109 | self.glade.get_widget(field+"_chooser").show()
110 | self.glade.get_widget(field+"_entry").hide()
111 | else:
112 | self.glade.get_widget(field+"_entry").set_text(options.get(field, ""))
113 | self.glade.get_widget(field+"_entry").show()
114 | self.glade.get_widget(field+"_chooser").hide()
115 | self.set_sensitive()
116 |
117 | def on_get_enabled_plugins(result):
118 | if 'Label' in result:
119 | self.glade.get_widget('label_frame').show()
120 | else:
121 | self.glade.get_widget('label_frame').hide()
122 | self.glade.get_widget('label_toggle').set_active(False)
123 |
124 | client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
125 |
126 | def set_sensitive(self):
127 | maintoggles = ['download_location', 'append_extension', 'move_completed', 'label', \
128 | 'max_download_speed', 'max_upload_speed', 'max_connections', \
129 | 'max_upload_slots', 'add_paused', 'auto_managed', 'stop_at_ratio', 'queue_to_top']
130 | [self.on_toggle_toggled(self.glade.get_widget(x+'_toggle')) for x in maintoggles]
131 |
132 | def on_toggle_toggled(self, tb):
133 | toggle = str(tb.name).replace("_toggle", "")
134 | isactive = tb.get_active()
135 | if toggle == 'download_location':
136 | self.glade.get_widget('download_location_chooser').set_sensitive(isactive)
137 | self.glade.get_widget('download_location_entry').set_sensitive(isactive)
138 | elif toggle == 'append_extension':
139 | self.glade.get_widget('append_extension').set_sensitive(isactive)
140 | elif toggle == 'move_completed':
141 | self.glade.get_widget('move_completed_path_chooser').set_sensitive(isactive)
142 | self.glade.get_widget('move_completed_path_entry').set_sensitive(isactive)
143 | self.glade.get_widget('move_completed').set_active(isactive)
144 | elif toggle == 'label':
145 | self.glade.get_widget('label').set_sensitive(isactive)
146 | elif toggle == 'max_download_speed':
147 | self.glade.get_widget('max_download_speed').set_sensitive(isactive)
148 | elif toggle == 'max_upload_speed':
149 | self.glade.get_widget('max_upload_speed').set_sensitive(isactive)
150 | elif toggle == 'max_connections':
151 | self.glade.get_widget('max_connections').set_sensitive(isactive)
152 | elif toggle == 'max_upload_slots':
153 | self.glade.get_widget('max_upload_slots').set_sensitive(isactive)
154 | elif toggle == 'add_paused':
155 | self.glade.get_widget('add_paused').set_sensitive(isactive)
156 | self.glade.get_widget('isnt_add_paused').set_sensitive(isactive)
157 | elif toggle == 'queue_to_top':
158 | self.glade.get_widget('queue_to_top').set_sensitive(isactive)
159 | self.glade.get_widget('isnt_queue_to_top').set_sensitive(isactive)
160 | elif toggle == 'auto_managed':
161 | self.glade.get_widget('auto_managed').set_sensitive(isactive)
162 | self.glade.get_widget('isnt_auto_managed').set_sensitive(isactive)
163 | elif toggle == 'stop_at_ratio':
164 | self.glade.get_widget('remove_at_ratio_toggle').set_active(isactive)
165 | self.glade.get_widget('stop_ratio_toggle').set_active(isactive)
166 | self.glade.get_widget('stop_at_ratio').set_active(isactive)
167 | self.glade.get_widget('stop_ratio').set_sensitive(isactive)
168 | self.glade.get_widget('remove_at_ratio').set_sensitive(isactive)
169 |
170 | def on_apply(self, Event=None):
171 | client.autoadd.set_options(str(self.watchdir_id), self.generate_opts()).addCallbacks(self.on_added, self.on_error_show)
172 |
173 | def on_error_show(self, result):
174 | self.glade.get_widget('error_label').set_text(result.value.exception_msg)
175 | self.err_dialog = self.glade.get_widget('error_dialog')
176 | self.err_dialog.set_transient_for(self.dialog)
177 | result.cleanFailure()
178 | self.err_dialog.show()
179 |
180 | def on_added(self, result):
181 | self.dialog.destroy()
182 |
183 | def on_error_ok(self, Event=None):
184 | self.err_dialog.hide()
185 |
186 | def on_add(self, Event=None):
187 | client.autoadd.add(self.generate_opts()).addCallbacks(self.on_added, self.on_error_show)
188 |
189 | def on_cancel(self, Event=None):
190 | self.dialog.destroy()
191 |
192 | def generate_opts(self):
193 | # generate options dict based on gtk objects
194 | options = {}
195 | options['enabled'] = self.glade.get_widget('enabled').get_active()
196 | if client.is_localhost():
197 | options['path'] = self.glade.get_widget('path_chooser').get_filename()
198 | options['download_location'] = self.glade.get_widget('download_location_chooser').get_filename()
199 | options['move_completed_path'] = self.glade.get_widget('move_completed_path_chooser').get_filename()
200 | else:
201 | options['path'] = self.glade.get_widget('path_entry').get_text()
202 | options['download_location'] = self.glade.get_widget('download_location_entry').get_text()
203 | options['move_completed_path'] = self.glade.get_widget('move_completed_path_entry').get_text()
204 | options['append_extension_toggle'] = self.glade.get_widget('append_extension_toggle').get_active()
205 | options['append_extension'] = self.glade.get_widget('append_extension').get_text()
206 | options['download_location_toggle'] = self.glade.get_widget('download_location_toggle').get_active()
207 | options['label'] = self.glade.get_widget('label').get_text().lower()
208 | options['label_toggle'] = self.glade.get_widget('label_toggle').get_active()
209 |
210 | for id in self.spin_ids:
211 | options[id] = self.glade.get_widget(id).get_value()
212 | options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active()
213 | for id in self.spin_int_ids:
214 | options[id] = self.glade.get_widget(id).get_value_as_int()
215 | options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active()
216 | for id in self.chk_ids:
217 | options[id] = self.glade.get_widget(id).get_active()
218 | options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active()
219 | return options
220 |
221 |
222 | class GtkUI(GtkPluginBase):
223 | def enable(self):
224 |
225 | self.glade = gtk.glade.XML(get_resource("config.glade"))
226 | self.glade.signal_autoconnect({
227 | "on_add_button_clicked": self.on_add_button_clicked,
228 | "on_edit_button_clicked": self.on_edit_button_clicked,
229 | "on_remove_button_clicked": self.on_remove_button_clicked
230 | })
231 | self.opts_dialog = OptionsDialog()
232 |
233 | component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
234 | component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
235 | client.register_event_handler("AutoaddOptionsChangedEvent", self.on_options_changed_event)
236 |
237 | self.watchdirs = {}
238 |
239 | vbox = self.glade.get_widget("watchdirs_vbox")
240 | sw = gtk.ScrolledWindow()
241 | sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
242 | sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
243 |
244 | vbox.pack_start(sw, True, True, 0)
245 |
246 | self.store = self.create_model()
247 |
248 | self.treeView = gtk.TreeView(self.store)
249 | self.treeView.connect("cursor-changed", self.on_listitem_activated)
250 | self.treeView.connect("row-activated", self.on_edit_button_clicked)
251 | self.treeView.set_rules_hint(True)
252 |
253 | self.create_columns(self.treeView)
254 | sw.add(self.treeView)
255 | sw.show_all()
256 | component.get("Preferences").add_page("AutoAdd", self.glade.get_widget("prefs_box"))
257 | self.on_show_prefs()
258 |
259 |
260 | def disable(self):
261 | component.get("Preferences").remove_page("AutoAdd")
262 | component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
263 | component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
264 |
265 | def create_model(self):
266 |
267 | store = gtk.ListStore(str, bool, str)
268 | for watchdir_id, watchdir in self.watchdirs.iteritems():
269 | store.append([watchdir_id, watchdir['enabled'], watchdir['path']])
270 | return store
271 |
272 | def create_columns(self, treeView):
273 | rendererToggle = gtk.CellRendererToggle()
274 | column = gtk.TreeViewColumn("On", rendererToggle, activatable=True, active=1)
275 | column.set_sort_column_id(1)
276 | treeView.append_column(column)
277 | tt = gtk.Tooltip()
278 | tt.set_text('Double-click to toggle')
279 | treeView.set_tooltip_cell(tt, None, None, rendererToggle)
280 |
281 | rendererText = gtk.CellRendererText()
282 | column = gtk.TreeViewColumn("Path", rendererText, text=2)
283 | column.set_sort_column_id(2)
284 | treeView.append_column(column)
285 | tt2 = gtk.Tooltip()
286 | tt2.set_text('Double-click to edit')
287 | #treeView.set_tooltip_cell(tt2, None, column, None)
288 | treeView.set_has_tooltip(True)
289 |
290 | def load_watchdir_list(self):
291 | pass
292 |
293 | def add_watchdir_entry(self):
294 | pass
295 |
296 | def on_add_button_clicked(self, Event=None):
297 | #display options_window
298 | self.opts_dialog.show()
299 |
300 | def on_remove_button_clicked(self, Event=None):
301 | tree, tree_id = self.treeView.get_selection().get_selected()
302 | watchdir_id = str(self.store.get_value(tree_id, 0))
303 | if watchdir_id:
304 | client.autoadd.remove(watchdir_id)
305 |
306 | def on_edit_button_clicked(self, Event=None, a=None, col=None):
307 | tree, tree_id = self.treeView.get_selection().get_selected()
308 | watchdir_id = str(self.store.get_value(tree_id, 0))
309 | if watchdir_id:
310 | if col and col.get_title() == 'On':
311 | if self.watchdirs[watchdir_id]['enabled']:
312 | client.autoadd.disable_watchdir(watchdir_id)
313 | else:
314 | client.autoadd.enable_watchdir(watchdir_id)
315 | else:
316 | self.opts_dialog.show(self.watchdirs[watchdir_id], watchdir_id)
317 |
318 | def on_listitem_activated(self, treeview):
319 | tree, tree_id = self.treeView.get_selection().get_selected()
320 | if tree_id:
321 | self.glade.get_widget('edit_button').set_sensitive(True)
322 | self.glade.get_widget('remove_button').set_sensitive(True)
323 | else:
324 | self.glade.get_widget('edit_button').set_sensitive(False)
325 | self.glade.get_widget('remove_button').set_sensitive(False)
326 |
327 | def on_apply_prefs(self):
328 | log.debug("applying prefs for AutoAdd")
329 | for watchdir_id, watchdir in self.watchdirs.iteritems():
330 | client.autoadd.set_options(watchdir_id, watchdir)
331 |
332 | def on_show_prefs(self):
333 | client.autoadd.get_config().addCallback(self.cb_get_config)
334 |
335 | def on_options_changed_event(self):
336 | client.autoadd.get_config().addCallback(self.cb_get_config)
337 |
338 | def cb_get_config(self, config):
339 | """callback for on show_prefs"""
340 | self.watchdirs = config.get('watchdirs', {})
341 | self.store.clear()
342 | for watchdir_id, watchdir in self.watchdirs.iteritems():
343 | self.store.append([watchdir_id, watchdir['enabled'], watchdir['path']])
344 | # Disable the remove and edit buttons, because nothing in the store is selected
345 | self.glade.get_widget('remove_button').set_sensitive(False)
346 | self.glade.get_widget('edit_button').set_sensitive(False)
347 |
348 |
--------------------------------------------------------------------------------
/autoadd/data/autoadd_options.glade:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 6
7 | AutoAdd Error
8 | False
9 | True
10 | dialog
11 |
12 |
13 |
14 | True
15 |
16 |
17 | True
18 |
19 |
20 | True
21 | gtk-dialog-error
22 |
23 |
24 | False
25 | 0
26 |
27 |
28 |
29 |
30 | True
31 | 0.46000000834465027
32 | Error
33 | True
34 |
35 |
36 | False
37 | False
38 | 1
39 |
40 |
41 |
42 |
43 | 2
44 |
45 |
46 |
47 |
48 | True
49 | end
50 |
51 |
52 | gtk-ok
53 | -5
54 | True
55 | True
56 | True
57 | False
58 | True
59 |
60 |
61 |
62 | False
63 | False
64 | 0
65 |
66 |
67 |
68 |
69 | False
70 | end
71 | 0
72 |
73 |
74 |
75 |
76 |
77 |
78 | Watch Folder Properties
79 | False
80 | True
81 | dialog
82 |
83 |
84 |
85 | True
86 | vertical
87 |
88 |
89 | True
90 | vertical
91 |
92 |
93 | True
94 | True
95 |
96 |
97 | True
98 | 6
99 | vertical
100 |
101 |
102 | True
103 | 0
104 | none
105 |
106 |
107 | True
108 | 12
109 |
110 |
111 | True
112 | vertical
113 |
114 |
115 | True
116 |
117 |
118 | True
119 | True
120 | ●
121 |
122 |
123 | 0
124 |
125 |
126 |
127 |
128 | True
129 | select-folder
130 | Select A Folder
131 |
132 |
133 | 1
134 |
135 |
136 |
137 |
138 | 0
139 |
140 |
141 |
142 |
143 | Enable this watch folder
144 | True
145 | True
146 | False
147 | True
148 | True
149 |
150 |
151 |
152 | False
153 | False
154 | 1
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | True
164 | <b>Watch Folder</b>
165 | True
166 |
167 |
168 | label_item
169 |
170 |
171 |
172 |
173 | False
174 | 0
175 |
176 |
177 |
178 |
179 | True
180 | 0
181 | none
182 |
183 |
184 | True
185 | 12
186 |
187 |
188 | True
189 |
190 |
191 | True
192 | vertical
193 |
194 |
195 | Delete .torrent after adding
196 | True
197 | True
198 | False
199 | True
200 | True
201 |
202 |
203 | 0
204 |
205 |
206 |
207 |
208 | True
209 |
210 |
211 | Append extension after adding:
212 | True
213 | True
214 | False
215 | True
216 | isnt_append_extension
217 |
218 |
219 |
220 | 0
221 |
222 |
223 |
224 |
225 | True
226 | True
227 | •
228 | .added
229 |
230 |
231 | 1
232 |
233 |
234 |
235 |
236 | 1
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | True
248 | <b>Torrent File Action</b>
249 | True
250 |
251 |
252 | label_item
253 |
254 |
255 |
256 |
257 | 1
258 |
259 |
260 |
261 |
262 | True
263 | 0
264 | none
265 |
266 |
267 | True
268 | 12
269 |
270 |
271 | True
272 | vertical
273 |
274 |
275 | Set download location
276 | True
277 | True
278 | False
279 | True
280 | True
281 |
282 |
283 |
284 | False
285 | False
286 | 0
287 |
288 |
289 |
290 |
291 | True
292 |
293 |
294 | True
295 | True
296 | ●
297 |
298 |
299 | 0
300 |
301 |
302 |
303 |
304 | True
305 | select-folder
306 | Select A Folder
307 |
308 |
309 | 1
310 |
311 |
312 |
313 |
314 | False
315 | 1
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 | True
325 | <b>Download Location</b>
326 | True
327 |
328 |
329 | label_item
330 |
331 |
332 |
333 |
334 | False
335 | 2
336 |
337 |
338 |
339 |
340 | True
341 | 0
342 | none
343 |
344 |
345 | True
346 | 12
347 |
348 |
349 | True
350 | vertical
351 |
352 |
353 | Set move completed location
354 | True
355 | True
356 | False
357 | True
358 | True
359 |
360 |
361 |
362 | False
363 | False
364 | 0
365 |
366 |
367 |
368 |
369 | True
370 |
371 |
372 | True
373 | True
374 | ●
375 |
376 |
377 | 0
378 |
379 |
380 |
381 |
382 | True
383 | select-folder
384 | Select A Folder
385 |
386 |
387 | 1
388 |
389 |
390 |
391 |
392 | False
393 | False
394 | False
395 | True
396 | True
397 | True
398 |
399 |
400 | False
401 | False
402 | 2
403 |
404 |
405 |
406 |
407 | False
408 | 1
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 | True
418 | <b>Move Completed</b>
419 | True
420 |
421 |
422 | label_item
423 |
424 |
425 |
426 |
427 | False
428 | 3
429 |
430 |
431 |
432 |
433 | 0
434 | none
435 |
436 |
437 | True
438 | 12
439 |
440 |
441 | True
442 |
443 |
444 | Label:
445 | True
446 | True
447 | False
448 | True
449 | True
450 |
451 |
452 |
453 | False
454 | False
455 | 0
456 |
457 |
458 |
459 |
460 | True
461 | True
462 | ●
463 |
464 |
465 | 1
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 | True
475 | <b>Label</b>
476 | True
477 |
478 |
479 | label_item
480 |
481 |
482 |
483 |
484 | 4
485 |
486 |
487 |
488 |
489 |
490 |
491 | True
492 | Main
493 |
494 |
495 | False
496 | tab
497 |
498 |
499 |
500 |
501 | True
502 | 6
503 | vertical
504 |
505 |
506 |
507 |
508 |
509 | True
510 | 0
511 | none
512 |
513 |
514 | True
515 | 12
516 |
517 |
518 | True
519 | 3
520 | 4
521 | 3
522 |
523 |
524 | Max Upload Speed:
525 | True
526 | True
527 | False
528 | True
529 | True
530 |
531 |
532 |
533 | 1
534 | 2
535 | GTK_FILL
536 |
537 |
538 |
539 |
540 |
541 | Max Connections:
542 | True
543 | True
544 | False
545 | True
546 | True
547 |
548 |
549 |
550 | 2
551 | 3
552 | GTK_FILL
553 |
554 |
555 |
556 |
557 |
558 | Max Upload Slots:
559 | True
560 | True
561 | False
562 | True
563 | True
564 |
565 |
566 |
567 | 3
568 | 4
569 | GTK_FILL
570 |
571 |
572 |
573 |
574 |
575 | True
576 | True
577 | -1 -1 10000 1 10 0
578 | 1
579 | 1
580 |
581 |
582 | 1
583 | 2
584 |
585 |
586 |
587 |
588 |
589 | True
590 | True
591 | -1 -1 10000 1 10 0
592 | 1
593 | 1
594 |
595 |
596 | 1
597 | 2
598 | 1
599 | 2
600 |
601 |
602 |
603 |
604 |
605 | True
606 | True
607 | -1 -1 10000 1 10 0
608 | 1
609 |
610 |
611 | 1
612 | 2
613 | 2
614 | 3
615 |
616 |
617 |
618 |
619 |
620 | True
621 | True
622 | -1 -1 10000 1 10 0
623 | 1
624 |
625 |
626 | 1
627 | 2
628 | 3
629 | 4
630 |
631 |
632 |
633 |
634 |
635 | True
636 | 0
637 | 5
638 | KiB/s
639 |
640 |
641 | 2
642 | 3
643 | GTK_FILL
644 |
645 |
646 |
647 |
648 |
649 | True
650 | 0
651 | 5
652 | KiB/s
653 |
654 |
655 | 2
656 | 3
657 | 1
658 | 2
659 | GTK_FILL
660 |
661 |
662 |
663 |
664 |
665 | Max Download Speed:
666 | True
667 | True
668 | False
669 | True
670 | True
671 |
672 |
673 |
674 | GTK_FILL
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 | True
691 | <b>Bandwidth</b>
692 | True
693 |
694 |
695 | label_item
696 |
697 |
698 |
699 |
700 | 1
701 |
702 |
703 |
704 |
705 | True
706 | 0
707 | none
708 |
709 |
710 | True
711 | 12
712 |
713 |
714 | True
715 | 5
716 | 4
717 |
718 |
719 | True
720 |
721 |
722 | Stop seed at ratio:
723 | True
724 | True
725 | False
726 | True
727 | True
728 |
729 |
730 |
731 |
732 |
733 | 3
734 | 4
735 | GTK_FILL
736 |
737 |
738 |
739 |
740 | True
741 | 0
742 | 0
743 | 12
744 |
745 |
746 | Remove at ratio
747 | True
748 | True
749 | False
750 | True
751 | True
752 |
753 |
754 |
755 |
756 | 4
757 | 5
758 |
759 |
760 |
761 |
762 | Auto Managed:
763 | True
764 | True
765 | False
766 | True
767 | True
768 |
769 |
770 |
771 | 2
772 | 3
773 | GTK_FILL
774 |
775 |
776 |
777 |
778 |
779 | False
780 | True
781 | False
782 | True
783 | True
784 | True
785 |
786 |
787 | 2
788 | 3
789 | 4
790 | 5
791 | GTK_FILL
792 |
793 |
794 |
795 |
796 |
797 | False
798 | True
799 | False
800 | True
801 | True
802 | True
803 |
804 |
805 | 1
806 | 2
807 | 4
808 | 5
809 | GTK_FILL
810 |
811 |
812 |
813 |
814 |
815 | True
816 | True
817 | ●
818 | 2 0 100 0.10000000149 10 0
819 | 1
820 | 1
821 |
822 |
823 | 1
824 | 2
825 | 3
826 | 4
827 |
828 |
829 |
830 |
831 |
832 | True
833 | True
834 |
835 |
836 | Yes
837 | True
838 | True
839 | False
840 | True
841 | True
842 |
843 |
844 | 0
845 |
846 |
847 |
848 |
849 | No
850 | True
851 | True
852 | False
853 | True
854 | auto_managed
855 |
856 |
857 | 1
858 |
859 |
860 |
861 |
862 | 1
863 | 2
864 | 2
865 | 3
866 | GTK_FILL
867 | GTK_FILL
868 |
869 |
870 |
871 |
872 | False
873 | True
874 | False
875 | True
876 | True
877 | True
878 |
879 |
880 | 2
881 | 3
882 | 3
883 | 4
884 | GTK_FILL
885 |
886 |
887 |
888 |
889 |
890 | Add Paused:
891 | True
892 | True
893 | False
894 | True
895 |
896 |
897 |
898 |
899 |
900 | True
901 | True
902 |
903 |
904 | Yes
905 | True
906 | True
907 | False
908 | True
909 | True
910 |
911 |
912 | 0
913 |
914 |
915 |
916 |
917 | No
918 | True
919 | True
920 | False
921 | True
922 | add_paused
923 |
924 |
925 | 1
926 |
927 |
928 |
929 |
930 | 1
931 | 2
932 |
933 |
934 |
935 |
936 | Queue to:
937 | True
938 | True
939 | False
940 | True
941 |
942 |
943 |
944 | 1
945 | 2
946 |
947 |
948 |
949 |
950 | True
951 | True
952 |
953 |
954 | Top
955 | True
956 | True
957 | False
958 | True
959 | True
960 |
961 |
962 | 0
963 |
964 |
965 |
966 |
967 | Bottom
968 | True
969 | True
970 | False
971 | True
972 | queue_to_top
973 |
974 |
975 | 1
976 |
977 |
978 |
979 |
980 | 1
981 | 2
982 | 1
983 | 2
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 | True
1017 | <b>Queue</b>
1018 | True
1019 |
1020 |
1021 | label_item
1022 |
1023 |
1024 |
1025 |
1026 | 2
1027 |
1028 |
1029 |
1030 |
1031 | 1
1032 |
1033 |
1034 |
1035 |
1036 | True
1037 | Options
1038 |
1039 |
1040 | 1
1041 | False
1042 | tab
1043 |
1044 |
1045 |
1046 |
1047 | 0
1048 |
1049 |
1050 |
1051 |
1052 | True
1053 | vertical
1054 |
1055 |
1056 | 1
1057 |
1058 |
1059 |
1060 |
1061 | 2
1062 |
1063 |
1064 |
1065 |
1066 | True
1067 | end
1068 |
1069 |
1070 | gtk-cancel
1071 | True
1072 | True
1073 | True
1074 | False
1075 | True
1076 |
1077 |
1078 |
1079 | False
1080 | False
1081 | 0
1082 |
1083 |
1084 |
1085 |
1086 | gtk-add
1087 | True
1088 | True
1089 | True
1090 | False
1091 | True
1092 |
1093 |
1094 |
1095 | False
1096 | False
1097 | 1
1098 |
1099 |
1100 |
1101 |
1102 | gtk-apply
1103 | True
1104 | True
1105 | True
1106 | False
1107 | True
1108 |
1109 |
1110 |
1111 | False
1112 | False
1113 | 2
1114 |
1115 |
1116 |
1117 |
1118 | False
1119 | end
1120 | 0
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
--------------------------------------------------------------------------------