Move the old dbus branch to deluge-0.6-dbus and move the xmlrpc branch
to deluge-0.6.
This commit is contained in:
commit
d5888131a0
1
TODO
1
TODO
|
@ -11,3 +11,4 @@
|
|||
to add menuitems to the torrentmenu in an easy way.
|
||||
* Restart daemon function
|
||||
* Docstrings!
|
||||
* Update libtorrent and bindings for sparse_mode allocation and remove_torrent options
|
||||
|
|
|
@ -0,0 +1,595 @@
|
|||
"""Simple XML-RPC Server.
|
||||
|
||||
This module can be used to create simple XML-RPC servers
|
||||
by creating a server and either installing functions, a
|
||||
class instance, or by extending the SimpleXMLRPCServer
|
||||
class.
|
||||
|
||||
It can also be used to handle XML-RPC requests in a CGI
|
||||
environment using CGIXMLRPCRequestHandler.
|
||||
|
||||
A list of possible usage patterns follows:
|
||||
|
||||
1. Install functions:
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_function(pow)
|
||||
server.register_function(lambda x,y: x+y, 'add')
|
||||
server.serve_forever()
|
||||
|
||||
2. Install an instance:
|
||||
|
||||
class MyFuncs:
|
||||
def __init__(self):
|
||||
# make all of the string functions available through
|
||||
# string.func_name
|
||||
import string
|
||||
self.string = string
|
||||
def _listMethods(self):
|
||||
# implement this method so that system.listMethods
|
||||
# knows to advertise the strings methods
|
||||
return list_public_methods(self) + \
|
||||
['string.' + method for method in list_public_methods(self.string)]
|
||||
def pow(self, x, y): return pow(x, y)
|
||||
def add(self, x, y) : return x + y
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_introspection_functions()
|
||||
server.register_instance(MyFuncs())
|
||||
server.serve_forever()
|
||||
|
||||
3. Install an instance with custom dispatch method:
|
||||
|
||||
class Math:
|
||||
def _listMethods(self):
|
||||
# this method must be present for system.listMethods
|
||||
# to work
|
||||
return ['add', 'pow']
|
||||
def _methodHelp(self, method):
|
||||
# this method must be present for system.methodHelp
|
||||
# to work
|
||||
if method == 'add':
|
||||
return "add(2,3) => 5"
|
||||
elif method == 'pow':
|
||||
return "pow(x, y[, z]) => number"
|
||||
else:
|
||||
# By convention, return empty
|
||||
# string if no help is available
|
||||
return ""
|
||||
def _dispatch(self, method, params):
|
||||
if method == 'pow':
|
||||
return pow(*params)
|
||||
elif method == 'add':
|
||||
return params[0] + params[1]
|
||||
else:
|
||||
raise 'bad method'
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_introspection_functions()
|
||||
server.register_instance(Math())
|
||||
server.serve_forever()
|
||||
|
||||
4. Subclass SimpleXMLRPCServer:
|
||||
|
||||
class MathServer(SimpleXMLRPCServer):
|
||||
def _dispatch(self, method, params):
|
||||
try:
|
||||
# We are forcing the 'export_' prefix on methods that are
|
||||
# callable through XML-RPC to prevent potential security
|
||||
# problems
|
||||
func = getattr(self, 'export_' + method)
|
||||
except AttributeError:
|
||||
raise Exception('method "%s" is not supported' % method)
|
||||
else:
|
||||
return func(*params)
|
||||
|
||||
def export_add(self, x, y):
|
||||
return x + y
|
||||
|
||||
server = MathServer(("localhost", 8000))
|
||||
server.serve_forever()
|
||||
|
||||
5. CGI script:
|
||||
|
||||
server = CGIXMLRPCRequestHandler()
|
||||
server.register_function(pow)
|
||||
server.handle_request()
|
||||
"""
|
||||
|
||||
# Written by Brian Quinlan (brian@sweetapp.com).
|
||||
# Based on code written by Fredrik Lundh.
|
||||
|
||||
import xmlrpclib
|
||||
from xmlrpclib import Fault
|
||||
import SocketServer
|
||||
import BaseHTTPServer
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
||||
"""resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
||||
|
||||
Resolves a dotted attribute name to an object. Raises
|
||||
an AttributeError if any attribute in the chain starts with a '_'.
|
||||
|
||||
If the optional allow_dotted_names argument is false, dots are not
|
||||
supported and this function operates similar to getattr(obj, attr).
|
||||
"""
|
||||
|
||||
if allow_dotted_names:
|
||||
attrs = attr.split('.')
|
||||
else:
|
||||
attrs = [attr]
|
||||
|
||||
for i in attrs:
|
||||
if i.startswith('_'):
|
||||
raise AttributeError(
|
||||
'attempt to access private attribute "%s"' % i
|
||||
)
|
||||
else:
|
||||
obj = getattr(obj,i)
|
||||
return obj
|
||||
|
||||
def list_public_methods(obj):
|
||||
"""Returns a list of attribute strings, found in the specified
|
||||
object, which represent callable attributes"""
|
||||
|
||||
return [member for member in dir(obj)
|
||||
if not member.startswith('_') and
|
||||
callable(getattr(obj, member))]
|
||||
|
||||
def remove_duplicates(lst):
|
||||
"""remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
|
||||
|
||||
Returns a copy of a list without duplicates. Every list
|
||||
item must be hashable and the order of the items in the
|
||||
resulting list is not defined.
|
||||
"""
|
||||
u = {}
|
||||
for x in lst:
|
||||
u[x] = 1
|
||||
|
||||
return u.keys()
|
||||
|
||||
class SimpleXMLRPCDispatcher:
|
||||
"""Mix-in class that dispatches XML-RPC requests.
|
||||
|
||||
This class is used to register XML-RPC method handlers
|
||||
and then to dispatch them. There should never be any
|
||||
reason to instantiate this class directly.
|
||||
"""
|
||||
|
||||
def __init__(self, allow_none, encoding):
|
||||
self.funcs = {}
|
||||
self.instance = None
|
||||
self.allow_none = allow_none
|
||||
self.encoding = encoding
|
||||
|
||||
def register_instance(self, instance, allow_dotted_names=False):
|
||||
"""Registers an instance to respond to XML-RPC requests.
|
||||
|
||||
Only one instance can be installed at a time.
|
||||
|
||||
If the registered instance has a _dispatch method then that
|
||||
method will be called with the name of the XML-RPC method and
|
||||
its parameters as a tuple
|
||||
e.g. instance._dispatch('add',(2,3))
|
||||
|
||||
If the registered instance does not have a _dispatch method
|
||||
then the instance will be searched to find a matching method
|
||||
and, if found, will be called. Methods beginning with an '_'
|
||||
are considered private and will not be called by
|
||||
SimpleXMLRPCServer.
|
||||
|
||||
If a registered function matches a XML-RPC request, then it
|
||||
will be called instead of the registered instance.
|
||||
|
||||
If the optional allow_dotted_names argument is true and the
|
||||
instance does not have a _dispatch method, method names
|
||||
containing dots are supported and resolved, as long as none of
|
||||
the name segments start with an '_'.
|
||||
|
||||
*** SECURITY WARNING: ***
|
||||
|
||||
Enabling the allow_dotted_names options allows intruders
|
||||
to access your module's global variables and may allow
|
||||
intruders to execute arbitrary code on your machine. Only
|
||||
use this option on a secure, closed network.
|
||||
|
||||
"""
|
||||
|
||||
self.instance = instance
|
||||
self.allow_dotted_names = allow_dotted_names
|
||||
|
||||
def register_function(self, function, name = None):
|
||||
"""Registers a function to respond to XML-RPC requests.
|
||||
|
||||
The optional name argument can be used to set a Unicode name
|
||||
for the function.
|
||||
"""
|
||||
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
self.funcs[name] = function
|
||||
|
||||
def register_introspection_functions(self):
|
||||
"""Registers the XML-RPC introspection methods in the system
|
||||
namespace.
|
||||
|
||||
see http://xmlrpc.usefulinc.com/doc/reserved.html
|
||||
"""
|
||||
|
||||
self.funcs.update({'system.listMethods' : self.system_listMethods,
|
||||
'system.methodSignature' : self.system_methodSignature,
|
||||
'system.methodHelp' : self.system_methodHelp})
|
||||
|
||||
def register_multicall_functions(self):
|
||||
"""Registers the XML-RPC multicall method in the system
|
||||
namespace.
|
||||
|
||||
see http://www.xmlrpc.com/discuss/msgReader$1208"""
|
||||
|
||||
self.funcs.update({'system.multicall' : self.system_multicall})
|
||||
|
||||
def _marshaled_dispatch(self, data, dispatch_method = None):
|
||||
"""Dispatches an XML-RPC method from marshalled (XML) data.
|
||||
|
||||
XML-RPC methods are dispatched from the marshalled (XML) data
|
||||
using the _dispatch method and the result is returned as
|
||||
marshalled data. For backwards compatibility, a dispatch
|
||||
function can be provided as an argument (see comment in
|
||||
SimpleXMLRPCRequestHandler.do_POST) but overriding the
|
||||
existing method through subclassing is the prefered means
|
||||
of changing method dispatch behavior.
|
||||
"""
|
||||
|
||||
try:
|
||||
params, method = xmlrpclib.loads(data)
|
||||
|
||||
# generate response
|
||||
if dispatch_method is not None:
|
||||
response = dispatch_method(method, params)
|
||||
else:
|
||||
response = self._dispatch(method, params)
|
||||
# wrap response in a singleton tuple
|
||||
response = (response,)
|
||||
response = xmlrpclib.dumps(response, methodresponse=1,
|
||||
allow_none=self.allow_none, encoding=self.encoding)
|
||||
except Fault, fault:
|
||||
response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
|
||||
encoding=self.encoding)
|
||||
except:
|
||||
# report exception back to server
|
||||
response = xmlrpclib.dumps(
|
||||
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)),
|
||||
encoding=self.encoding, allow_none=self.allow_none,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def system_listMethods(self):
|
||||
"""system.listMethods() => ['add', 'subtract', 'multiple']
|
||||
|
||||
Returns a list of the methods supported by the server."""
|
||||
|
||||
methods = self.funcs.keys()
|
||||
if self.instance is not None:
|
||||
# Instance can implement _listMethod to return a list of
|
||||
# methods
|
||||
if hasattr(self.instance, '_listMethods'):
|
||||
methods = remove_duplicates(
|
||||
methods + self.instance._listMethods()
|
||||
)
|
||||
# if the instance has a _dispatch method then we
|
||||
# don't have enough information to provide a list
|
||||
# of methods
|
||||
elif not hasattr(self.instance, '_dispatch'):
|
||||
methods = remove_duplicates(
|
||||
methods + list_public_methods(self.instance)
|
||||
)
|
||||
methods.sort()
|
||||
return methods
|
||||
|
||||
def system_methodSignature(self, method_name):
|
||||
"""system.methodSignature('add') => [double, int, int]
|
||||
|
||||
Returns a list describing the signature of the method. In the
|
||||
above example, the add method takes two integers as arguments
|
||||
and returns a double result.
|
||||
|
||||
This server does NOT support system.methodSignature."""
|
||||
|
||||
# See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
|
||||
|
||||
return 'signatures not supported'
|
||||
|
||||
def system_methodHelp(self, method_name):
|
||||
"""system.methodHelp('add') => "Adds two integers together"
|
||||
|
||||
Returns a string containing documentation for the specified method."""
|
||||
|
||||
method = None
|
||||
if self.funcs.has_key(method_name):
|
||||
method = self.funcs[method_name]
|
||||
elif self.instance is not None:
|
||||
# Instance can implement _methodHelp to return help for a method
|
||||
if hasattr(self.instance, '_methodHelp'):
|
||||
return self.instance._methodHelp(method_name)
|
||||
# if the instance has a _dispatch method then we
|
||||
# don't have enough information to provide help
|
||||
elif not hasattr(self.instance, '_dispatch'):
|
||||
try:
|
||||
method = resolve_dotted_attribute(
|
||||
self.instance,
|
||||
method_name,
|
||||
self.allow_dotted_names
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Note that we aren't checking that the method actually
|
||||
# be a callable object of some kind
|
||||
if method is None:
|
||||
return ""
|
||||
else:
|
||||
import pydoc
|
||||
return pydoc.getdoc(method)
|
||||
|
||||
def system_multicall(self, call_list):
|
||||
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
|
||||
[[4], ...]
|
||||
|
||||
Allows the caller to package multiple XML-RPC calls into a single
|
||||
request.
|
||||
|
||||
See http://www.xmlrpc.com/discuss/msgReader$1208
|
||||
"""
|
||||
|
||||
results = []
|
||||
for call in call_list:
|
||||
method_name = call['methodName']
|
||||
params = call['params']
|
||||
|
||||
try:
|
||||
# XXX A marshalling error in any response will fail the entire
|
||||
# multicall. If someone cares they should fix this.
|
||||
results.append([self._dispatch(method_name, params)])
|
||||
except Fault, fault:
|
||||
results.append(
|
||||
{'faultCode' : fault.faultCode,
|
||||
'faultString' : fault.faultString}
|
||||
)
|
||||
except:
|
||||
results.append(
|
||||
{'faultCode' : 1,
|
||||
'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
|
||||
)
|
||||
return results
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
"""Dispatches the XML-RPC method.
|
||||
|
||||
XML-RPC calls are forwarded to a registered function that
|
||||
matches the called XML-RPC method name. If no such function
|
||||
exists then the call is forwarded to the registered instance,
|
||||
if available.
|
||||
|
||||
If the registered instance has a _dispatch method then that
|
||||
method will be called with the name of the XML-RPC method and
|
||||
its parameters as a tuple
|
||||
e.g. instance._dispatch('add',(2,3))
|
||||
|
||||
If the registered instance does not have a _dispatch method
|
||||
then the instance will be searched to find a matching method
|
||||
and, if found, will be called.
|
||||
|
||||
Methods beginning with an '_' are considered private and will
|
||||
not be called.
|
||||
"""
|
||||
|
||||
func = None
|
||||
try:
|
||||
# check to see if a matching function has been registered
|
||||
func = self.funcs[method]
|
||||
except KeyError:
|
||||
if self.instance is not None:
|
||||
# check for a _dispatch method
|
||||
if hasattr(self.instance, '_dispatch'):
|
||||
return self.instance._dispatch(method, params)
|
||||
else:
|
||||
# call instance method directly
|
||||
try:
|
||||
func = resolve_dotted_attribute(
|
||||
self.instance,
|
||||
method,
|
||||
self.allow_dotted_names
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if func is not None:
|
||||
return func(*params)
|
||||
else:
|
||||
raise Exception('method "%s" is not supported' % method)
|
||||
|
||||
class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
"""Simple XML-RPC request handler class.
|
||||
|
||||
Handles all HTTP POST requests and attempts to decode them as
|
||||
XML-RPC requests.
|
||||
"""
|
||||
|
||||
# Class attribute listing the accessible path components;
|
||||
# paths not on this list will result in a 404 error.
|
||||
rpc_paths = ('/', '/RPC2')
|
||||
|
||||
def is_rpc_path_valid(self):
|
||||
if self.rpc_paths:
|
||||
return self.path in self.rpc_paths
|
||||
else:
|
||||
# If .rpc_paths is empty, just assume all paths are legal
|
||||
return True
|
||||
|
||||
def do_POST(self):
|
||||
"""Handles the HTTP POST request.
|
||||
|
||||
Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
||||
which are forwarded to the server's _dispatch method for handling.
|
||||
"""
|
||||
|
||||
# Check that the path is legal
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
|
||||
try:
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
|
||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
||||
# could be overridden in this class, instead of in
|
||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
||||
# check to see if a subclass implements _dispatch and dispatch
|
||||
# using that method if present.
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None)
|
||||
)
|
||||
except: # This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid XML RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/xml")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def report_404 (self):
|
||||
# Report a 404 error
|
||||
self.send_response(404)
|
||||
response = 'No such page'
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def log_request(self, code='-', size='-'):
|
||||
"""Selectively log an accepted request."""
|
||||
|
||||
if self.server.logRequests:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
||||
|
||||
class SimpleXMLRPCServer(SocketServer.TCPServer,
|
||||
SimpleXMLRPCDispatcher):
|
||||
"""Simple XML-RPC server.
|
||||
|
||||
Simple XML-RPC server that allows functions and a single instance
|
||||
to be installed to handle requests. The default implementation
|
||||
attempts to dispatch XML-RPC calls to the functions or instance
|
||||
installed in the server. Override the _dispatch method inhereted
|
||||
from SimpleXMLRPCDispatcher to change this behavior.
|
||||
"""
|
||||
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
|
||||
logRequests=True, allow_none=False, encoding=None):
|
||||
self.logRequests = logRequests
|
||||
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler)
|
||||
|
||||
# [Bug #1222790] If possible, set close-on-exec flag; if a
|
||||
# method spawns a subprocess, the subprocess shouldn't have
|
||||
# the listening socket open.
|
||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
||||
flags |= fcntl.FD_CLOEXEC
|
||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
||||
|
||||
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
||||
"""Simple handler for XML-RPC data passed through CGI."""
|
||||
|
||||
def __init__(self, allow_none=False, encoding=None):
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
||||
|
||||
def handle_xmlrpc(self, request_text):
|
||||
"""Handle a single XML-RPC request"""
|
||||
|
||||
response = self._marshaled_dispatch(request_text)
|
||||
|
||||
print 'Content-Type: text/xml'
|
||||
print 'Content-Length: %d' % len(response)
|
||||
print
|
||||
sys.stdout.write(response)
|
||||
|
||||
def handle_get(self):
|
||||
"""Handle a single HTTP GET request.
|
||||
|
||||
Default implementation indicates an error because
|
||||
XML-RPC uses the POST method.
|
||||
"""
|
||||
|
||||
code = 400
|
||||
message, explain = \
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
|
||||
|
||||
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
|
||||
{
|
||||
'code' : code,
|
||||
'message' : message,
|
||||
'explain' : explain
|
||||
}
|
||||
print 'Status: %d %s' % (code, message)
|
||||
print 'Content-Type: text/html'
|
||||
print 'Content-Length: %d' % len(response)
|
||||
print
|
||||
sys.stdout.write(response)
|
||||
|
||||
def handle_request(self, request_text = None):
|
||||
"""Handle a single XML-RPC request passed through a CGI post method.
|
||||
|
||||
If no XML data is given then it is read from stdin. The resulting
|
||||
XML-RPC response is printed to stdout along with the correct HTTP
|
||||
headers.
|
||||
"""
|
||||
|
||||
if request_text is None and \
|
||||
os.environ.get('REQUEST_METHOD', None) == 'GET':
|
||||
self.handle_get()
|
||||
else:
|
||||
# POST data is normally available through stdin
|
||||
if request_text is None:
|
||||
request_text = sys.stdin.read()
|
||||
|
||||
self.handle_xmlrpc(request_text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'Running XML-RPC server on port 8000'
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_function(pow)
|
||||
server.register_function(lambda x,y: x+y, 'add')
|
||||
server.serve_forever()
|
|
@ -31,15 +31,17 @@
|
|||
# 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 dbus
|
||||
import dbus.service
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
import gettext
|
||||
import locale
|
||||
import pkg_resources
|
||||
import sys
|
||||
import pickle
|
||||
|
||||
import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer
|
||||
from SocketServer import ThreadingMixIn
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
import gobject
|
||||
import threading
|
||||
|
||||
import deluge.libtorrent as lt
|
||||
from deluge.configmanager import ConfigManager
|
||||
|
@ -47,6 +49,7 @@ import deluge.common
|
|||
from deluge.core.torrentmanager import TorrentManager
|
||||
from deluge.core.pluginmanager import PluginManager
|
||||
from deluge.core.alertmanager import AlertManager
|
||||
from deluge.core.signalmanager import SignalManager
|
||||
from deluge.log import LOG as log
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
|
@ -73,9 +76,30 @@ DEFAULT_PREFS = {
|
|||
"max_upload_slots_per_torrent": -1,
|
||||
"enabled_plugins": ["Queue"]
|
||||
}
|
||||
|
||||
class Core(
|
||||
threading.Thread,
|
||||
ThreadingMixIn,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
def __init__(self):
|
||||
log.debug("Core init..")
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
class Core(dbus.service.Object):
|
||||
def __init__(self, path="/org/deluge_torrent/Core"):
|
||||
# Setup the xmlrpc server
|
||||
try:
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(
|
||||
self, ("localhost", 58846), logRequests=False, allow_none=True)
|
||||
except:
|
||||
log.info("Daemon already running or port not available..")
|
||||
sys.exit(0)
|
||||
|
||||
# Register all export_* functions
|
||||
for func in dir(self):
|
||||
if func.startswith("export_"):
|
||||
self.register_function(getattr(self, "%s" % func), func[7:])
|
||||
|
||||
self.register_introspection_functions()
|
||||
|
||||
# Initialize gettext
|
||||
locale.setlocale(locale.LC_MESSAGES, '')
|
||||
locale.bindtextdomain("deluge",
|
||||
|
@ -89,13 +113,9 @@ class Core(dbus.service.Object):
|
|||
gettext.install("deluge",
|
||||
pkg_resources.resource_filename(
|
||||
"deluge", "i18n"))
|
||||
log.debug("Core init..")
|
||||
|
||||
# Setup DBUS
|
||||
bus_name = dbus.service.BusName("org.deluge_torrent.Deluge",
|
||||
bus=dbus.SessionBus())
|
||||
dbus.service.Object.__init__(self, bus_name, path)
|
||||
|
||||
def run(self):
|
||||
"""Starts the core"""
|
||||
# Get config
|
||||
self.config = ConfigManager("core.conf", DEFAULT_PREFS)
|
||||
|
||||
|
@ -119,33 +139,36 @@ class Core(dbus.service.Object):
|
|||
|
||||
# Register set functions in the Config
|
||||
self.config.register_set_function("listen_ports",
|
||||
self.on_set_listen_ports)
|
||||
self._on_set_listen_ports)
|
||||
self.config.register_set_function("random_port",
|
||||
self.on_set_random_port)
|
||||
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._on_set_random_port)
|
||||
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("enc_in_policy",
|
||||
self.on_set_encryption)
|
||||
self._on_set_encryption)
|
||||
self.config.register_set_function("enc_out_policy",
|
||||
self.on_set_encryption)
|
||||
self._on_set_encryption)
|
||||
self.config.register_set_function("enc_level",
|
||||
self.on_set_encryption)
|
||||
self._on_set_encryption)
|
||||
self.config.register_set_function("enc_prefer_rc4",
|
||||
self.on_set_encryption)
|
||||
self._on_set_encryption)
|
||||
self.config.register_set_function("max_connections_global",
|
||||
self.on_set_max_connections_global)
|
||||
self._on_set_max_connections_global)
|
||||
self.config.register_set_function("max_upload_speed",
|
||||
self.on_set_max_upload_speed)
|
||||
self._on_set_max_upload_speed)
|
||||
self.config.register_set_function("max_download_speed",
|
||||
self.on_set_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._on_set_max_upload_slots_global)
|
||||
|
||||
# Start the AlertManager
|
||||
self.alerts = AlertManager(self.session)
|
||||
|
||||
|
||||
# Start the SignalManager
|
||||
self.signals = SignalManager()
|
||||
|
||||
# Start the TorrentManager
|
||||
self.torrents = TorrentManager(self.session, self.alerts)
|
||||
|
||||
|
@ -154,16 +177,19 @@ class Core(dbus.service.Object):
|
|||
|
||||
# Register alert handlers
|
||||
self.alerts.register_handler("torrent_paused_alert",
|
||||
self.on_alert_torrent_paused)
|
||||
|
||||
log.debug("Starting main loop..")
|
||||
self._on_alert_torrent_paused)
|
||||
|
||||
t = threading.Thread(target=self.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
gobject.threads_init()
|
||||
|
||||
self.loop = gobject.MainLoop()
|
||||
self.loop.run()
|
||||
|
||||
|
||||
def _shutdown(self):
|
||||
"""This is called by a thread from shutdown()"""
|
||||
log.info("Shutting down core..")
|
||||
self.loop.quit()
|
||||
self.plugins.shutdown()
|
||||
self.torrents.shutdown()
|
||||
# Make sure the config file has been saved
|
||||
|
@ -171,24 +197,38 @@ class Core(dbus.service.Object):
|
|||
del self.config
|
||||
del deluge.configmanager
|
||||
del self.session
|
||||
self.loop.quit()
|
||||
|
||||
# Exported Methods
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="", out_signature="")
|
||||
def shutdown(self):
|
||||
def export_ping(self):
|
||||
"""A method to see if the core is running"""
|
||||
return True
|
||||
|
||||
def export_shutdown(self):
|
||||
"""Shutdown the core"""
|
||||
# Make shutdown an async call
|
||||
gobject.idle_add(self._shutdown)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="ssay", out_signature="b")
|
||||
def add_torrent_file(self, filename, save_path, filedump):
|
||||
def export_register_client(self, uri):
|
||||
"""Registers a client with the signal manager so that signals are
|
||||
sent to it."""
|
||||
self.signals.register_client(uri)
|
||||
|
||||
def export_deregister_client(self, uri):
|
||||
"""De-registers a client with the signal manager."""
|
||||
self.signals.deregister_client(uri)
|
||||
|
||||
def export_add_torrent_file(self, filename, save_path, filedump):
|
||||
"""Adds a torrent file to the libtorrent session
|
||||
This requires the torrents filename and a dump of it's content
|
||||
"""
|
||||
if save_path == "":
|
||||
save_path = None
|
||||
|
||||
|
||||
# Make sure we are sending a string to add()
|
||||
if not isinstance(filedump, str):
|
||||
filedump = filedump.data
|
||||
|
||||
torrent_id = self.torrents.add(filename, filedump=filedump,
|
||||
save_path=save_path)
|
||||
|
||||
|
@ -203,9 +243,7 @@ class Core(dbus.service.Object):
|
|||
# Return False because the torrent was not added successfully
|
||||
return False
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="ss", out_signature="b")
|
||||
def add_torrent_url(self, url, save_path):
|
||||
def export_add_torrent_url(self, url, save_path):
|
||||
log.info("Attempting to add url %s", url)
|
||||
|
||||
# Get the actual filename of the torrent from the url provided.
|
||||
|
@ -224,11 +262,9 @@ class Core(dbus.service.Object):
|
|||
return False
|
||||
|
||||
# Add the torrent to session
|
||||
return self.add_torrent_file(filename, save_path, filedump)
|
||||
return self.export_add_torrent_file(filename, save_path, filedump)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s", out_signature="")
|
||||
def remove_torrent(self, torrent_id):
|
||||
def export_remove_torrent(self, torrent_id):
|
||||
log.debug("Removing torrent %s from the core.", torrent_id)
|
||||
if self.torrents.remove(torrent_id):
|
||||
# Run the plugin hooks for 'post_torrent_remove'
|
||||
|
@ -236,43 +272,32 @@ class Core(dbus.service.Object):
|
|||
# Emit the torrent_removed signal
|
||||
self.torrent_removed(torrent_id)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s", out_signature="")
|
||||
def force_reannounce(self, torrent_id):
|
||||
def export_force_reannounce(self, torrent_id):
|
||||
log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id)
|
||||
self.torrents.force_reannounce(torrent_id)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s", out_signature="")
|
||||
def pause_torrent(self, torrent_id):
|
||||
def export_pause_torrent(self, torrent_id):
|
||||
log.debug("Pausing torrent %s", torrent_id)
|
||||
if not self.torrents.pause(torrent_id):
|
||||
log.warning("Error pausing torrent %s", torrent_id)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge")
|
||||
def pause_all_torrents(self):
|
||||
def export_pause_all_torrents(self):
|
||||
"""Pause all torrents in the session"""
|
||||
if not self.torrents.pause_all():
|
||||
log.warning("Error pausing all torrents..")
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge")
|
||||
def resume_all_torrents(self):
|
||||
def export_resume_all_torrents(self):
|
||||
"""Resume all torrents in the session"""
|
||||
if self.torrents.resume_all():
|
||||
# Emit the 'torrent_all_resumed' signal
|
||||
self.torrent_all_resumed()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s", out_signature="")
|
||||
def resume_torrent(self, torrent_id):
|
||||
def export_resume_torrent(self, torrent_id):
|
||||
log.debug("Resuming torrent %s", torrent_id)
|
||||
if self.torrents.resume(torrent_id):
|
||||
self.torrent_resumed(torrent_id)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="sas",
|
||||
out_signature="a{sv}")
|
||||
def get_torrent_status(self, torrent_id, keys):
|
||||
def export_get_torrent_status(self, torrent_id, keys):
|
||||
# Convert the array of strings to a python list of strings
|
||||
keys = deluge.common.pythonize(keys)
|
||||
# Build the status dictionary
|
||||
|
@ -286,33 +311,23 @@ class Core(dbus.service.Object):
|
|||
leftover_fields = list(set(keys) - set(status.keys()))
|
||||
if len(leftover_fields) > 0:
|
||||
status.update(self.plugins.get_status(torrent_id, leftover_fields))
|
||||
return status
|
||||
return xmlrpclib.Binary(pickle.dumps(status))
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="",
|
||||
out_signature="as")
|
||||
def get_session_state(self):
|
||||
def export_get_session_state(self):
|
||||
"""Returns a list of torrent_ids in the session."""
|
||||
# Get the torrent list from the TorrentManager
|
||||
return self.torrents.get_torrent_list()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge")
|
||||
def save_state(self):
|
||||
def export_save_state(self):
|
||||
"""Save the current session state to file."""
|
||||
# Have the TorrentManager save it's state
|
||||
self.torrents.save_state()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="",
|
||||
out_signature="a{sv}")
|
||||
def get_config(self):
|
||||
def export_get_config(self):
|
||||
"""Get all the preferences as a dictionary"""
|
||||
return self.config.get_config()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s",
|
||||
out_signature="v")
|
||||
def get_config_value(self, key):
|
||||
def export_get_config_value(self, key):
|
||||
"""Get the config value for key"""
|
||||
try:
|
||||
value = self.config[key]
|
||||
|
@ -320,105 +335,83 @@ class Core(dbus.service.Object):
|
|||
return None
|
||||
|
||||
return value
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="a{sv}")
|
||||
def set_config(self, config):
|
||||
|
||||
def export_set_config(self, config):
|
||||
"""Set the config with values from dictionary"""
|
||||
config = deluge.common.pythonize(config)
|
||||
# Load all the values into the configuration
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="i")
|
||||
def get_listen_port(self):
|
||||
|
||||
def export_get_listen_port(self):
|
||||
"""Returns the active listen port"""
|
||||
return self.session.listen_port()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="i")
|
||||
def get_num_connections(self):
|
||||
def export_get_num_connections(self):
|
||||
"""Returns the current number of connections"""
|
||||
return self.session.num_connections()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="d")
|
||||
def get_download_rate(self):
|
||||
def export_get_download_rate(self):
|
||||
"""Returns the payload download rate"""
|
||||
return self.session.status().payload_download_rate
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="d")
|
||||
def get_upload_rate(self):
|
||||
def export_get_upload_rate(self):
|
||||
"""Returns the payload upload rate"""
|
||||
return self.session.status().payload_upload_rate
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="as")
|
||||
def get_available_plugins(self):
|
||||
def export_get_available_plugins(self):
|
||||
"""Returns a list of plugins available in the core"""
|
||||
return self.plugins.get_available_plugins()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
out_signature="as")
|
||||
def get_enabled_plugins(self):
|
||||
def export_get_enabled_plugins(self):
|
||||
"""Returns a list of enabled plugins in the core"""
|
||||
return self.plugins.get_enabled_plugins()
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s")
|
||||
def enable_plugin(self, plugin):
|
||||
def export_enable_plugin(self, plugin):
|
||||
self.plugins.enable_plugin(plugin)
|
||||
|
||||
@dbus.service.method(dbus_interface="org.deluge_torrent.Deluge",
|
||||
in_signature="s")
|
||||
def disable_plugin(self, plugin):
|
||||
def export_disable_plugin(self, plugin):
|
||||
self.plugins.disable_plugin(plugin)
|
||||
|
||||
# Signals
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge",
|
||||
signature="s")
|
||||
def torrent_added(self, torrent_id):
|
||||
"""Emitted when a new torrent is added to the core"""
|
||||
log.debug("torrent_added signal emitted")
|
||||
self.signals.emit("torrent_added", torrent_id)
|
||||
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge",
|
||||
signature="s")
|
||||
def torrent_removed(self, torrent_id):
|
||||
"""Emitted when a torrent has been removed from the core"""
|
||||
log.debug("torrent_remove signal emitted")
|
||||
self.signals.emit("torrent_removed", torrent_id)
|
||||
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge",
|
||||
signature="s")
|
||||
def torrent_paused(self, torrent_id):
|
||||
"""Emitted when a torrent is paused"""
|
||||
log.debug("torrent_paused signal emitted")
|
||||
self.signals.emit("torrent_paused", torrent_id)
|
||||
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge",
|
||||
signature="s")
|
||||
def torrent_resumed(self, torrent_id):
|
||||
"""Emitted when a torrent is resumed"""
|
||||
log.debug("torrent_resumed signal emitted")
|
||||
self.signals.emit("torrent_resumed", torrent_id)
|
||||
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge")
|
||||
def torrent_all_paused(self):
|
||||
"""Emitted when all torrents have been paused"""
|
||||
log.debug("torrent_all_paused signal emitted")
|
||||
self.signals.emit("torrent_all_paused", torrent_id)
|
||||
|
||||
@dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge")
|
||||
def torrent_all_resumed(self):
|
||||
"""Emitted when all torrents have been resumed"""
|
||||
log.debug("torrent_all_resumed signal emitted")
|
||||
self.signals.emit("torrent_all_resumed", torrent_id)
|
||||
|
||||
# Config set functions
|
||||
def on_set_listen_ports(self, key, value):
|
||||
def _on_set_listen_ports(self, key, value):
|
||||
# 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])
|
||||
|
||||
def on_set_random_port(self, key, value):
|
||||
def _on_set_random_port(self, key, value):
|
||||
log.debug("random port value set to %s", value)
|
||||
# We need to check if the value has been changed to true and false
|
||||
# and then handle accordingly.
|
||||
|
@ -436,33 +429,33 @@ class Core(dbus.service.Object):
|
|||
listen_ports[1])
|
||||
self.session.listen_on(listen_ports[0], listen_ports[1])
|
||||
|
||||
def on_set_dht(self, key, value):
|
||||
def _on_set_dht(self, key, value):
|
||||
log.debug("dht value set to %s", value)
|
||||
if value:
|
||||
self.session.start_dht(None)
|
||||
else:
|
||||
self.session.stop_dht()
|
||||
|
||||
def on_set_upnp(self, key, value):
|
||||
def _on_set_upnp(self, key, value):
|
||||
log.debug("upnp value set to %s", value)
|
||||
if value:
|
||||
self.session.start_upnp()
|
||||
else:
|
||||
self.session.stop_upnp()
|
||||
|
||||
def on_set_natpmp(self, key, value):
|
||||
def _on_set_natpmp(self, key, value):
|
||||
log.debug("natpmp value set to %s", value)
|
||||
if value:
|
||||
self.session.start_natpmp()
|
||||
else:
|
||||
self.session.stop_natpmp()
|
||||
|
||||
def on_set_utpex(self, key, value):
|
||||
def _on_set_utpex(self, key, value):
|
||||
log.debug("utpex value set to %s", value)
|
||||
if value:
|
||||
self.session.add_extension(lt.create_ut_pex_plugin)
|
||||
|
||||
def on_set_encryption(self, key, value):
|
||||
def _on_set_encryption(self, key, value):
|
||||
log.debug("encryption value %s set to %s..", key, value)
|
||||
pe_settings = lt.pe_settings()
|
||||
pe_settings.out_enc_policy = \
|
||||
|
@ -479,26 +472,26 @@ class Core(dbus.service.Object):
|
|||
set.allowed_enc_level,
|
||||
set.prefer_rc4)
|
||||
|
||||
def on_set_max_connections_global(self, key, value):
|
||||
def _on_set_max_connections_global(self, key, value):
|
||||
log.debug("max_connections_global set to %s..", value)
|
||||
self.session.set_max_connections(value)
|
||||
|
||||
def on_set_max_upload_speed(self, key, value):
|
||||
def _on_set_max_upload_speed(self, key, value):
|
||||
log.debug("max_upload_speed set to %s..", value)
|
||||
# We need to convert Kb/s to B/s
|
||||
self.session.set_upload_rate_limit(int(value * 1024))
|
||||
|
||||
def on_set_max_download_speed(self, key, value):
|
||||
def _on_set_max_download_speed(self, key, value):
|
||||
log.debug("max_download_speed set to %s..", value)
|
||||
# We need to convert Kb/s to B/s
|
||||
self.session.set_download_rate_limit(int(value * 1024))
|
||||
|
||||
def on_set_max_upload_slots_global(self, key, value):
|
||||
def _on_set_max_upload_slots_global(self, key, value):
|
||||
log.debug("max_upload_slots_global set to %s..", value)
|
||||
self.session.set_max_uploads(value)
|
||||
|
||||
## Alert handlers ##
|
||||
def on_alert_torrent_paused(self, alert):
|
||||
def _on_alert_torrent_paused(self, alert):
|
||||
log.debug("on_alert_torrent_paused")
|
||||
# Get the torrent_id
|
||||
torrent_id = str(alert.handle.info_hash())
|
||||
|
|
|
@ -31,23 +31,13 @@
|
|||
# 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 dbus
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
|
||||
from deluge.core.core import Core
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class Daemon:
|
||||
def __init__(self):
|
||||
# Check to see if the daemon is already running and if not, start it
|
||||
bus = dbus.SessionBus()
|
||||
obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
iface = dbus.Interface(obj, "org.freedesktop.DBus")
|
||||
if iface.NameHasOwner("org.deluge_torrent.Deluge"):
|
||||
# Daemon is running so lets tell the user
|
||||
log.info("Daemon is already running..")
|
||||
else:
|
||||
# Daemon is not running so lets start up the core
|
||||
log.debug("Daemon is not running..")
|
||||
self.core = Core()
|
||||
# Start the core as a thread and join it until it's done
|
||||
self.core = Core()
|
||||
self.core.start()
|
||||
self.core.join()
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# signalmanager.py
|
||||
#
|
||||
# Copyright (C) 2007 Andrew Resch ('andar') <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 2 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 xmlrpclib
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class SignalManager:
|
||||
def __init__(self):
|
||||
self.clients = []
|
||||
|
||||
def deregister_client(self, uri):
|
||||
"""Deregisters a client"""
|
||||
log.debug("Deregistering %s as a signal reciever..", uri)
|
||||
self.clients.remove(self.clients.index(uri))
|
||||
|
||||
def register_client(self, uri):
|
||||
"""Registers a client to emit signals to."""
|
||||
log.debug("Registering %s as a signal reciever..", uri)
|
||||
self.clients.append(xmlrpclib.ServerProxy(uri))
|
||||
|
||||
def emit(self, signal, data):
|
||||
for client in self.clients:
|
||||
try:
|
||||
client.emit_signal(signal, data)
|
||||
except:
|
||||
log.warning("Unable to emit signal to client %s", client)
|
||||
|
|
@ -62,7 +62,7 @@ class Torrent:
|
|||
"""Returns the state of this torrent for saving to the session state"""
|
||||
status = self.handle.status()
|
||||
return (self.torrent_id, self.filename, self.compact, status.paused,
|
||||
self.save_path, self.total_uploaded)
|
||||
self.save_path, self.total_uploaded + status.total_payload_upload)
|
||||
|
||||
def get_eta(self):
|
||||
"""Returns the ETA in seconds for this torrent"""
|
||||
|
|
|
@ -171,13 +171,19 @@ class TorrentManager:
|
|||
# Make sure we are adding it with the correct allocation method.
|
||||
if compact is None:
|
||||
compact = self.config["compact_allocation"]
|
||||
|
||||
# Set the right storage_mode
|
||||
if compact:
|
||||
storage_mode = lt.storage_mode_t(1)
|
||||
else:
|
||||
storage_mode = lt.storage_mode_t(2)
|
||||
|
||||
try:
|
||||
handle = self.session.add_torrent(
|
||||
lt.torrent_info(torrent_filedump),
|
||||
str(save_path),
|
||||
resume_data=fastresume,
|
||||
compact_mode=compact,
|
||||
storage_mode=storage_mode,
|
||||
paused=paused)
|
||||
except RuntimeError:
|
||||
log.warning("Error adding torrent")
|
||||
|
@ -228,7 +234,7 @@ class TorrentManager:
|
|||
"""Remove a torrent from the manager"""
|
||||
try:
|
||||
# Remove from libtorrent session
|
||||
self.session.remove_torrent(self.torrents[torrent_id].handle)
|
||||
self.session.remove_torrent(self.torrents[torrent_id].handle, 0)
|
||||
except RuntimeError, KeyError:
|
||||
log.warning("Error removing torrent")
|
||||
return False
|
||||
|
|
|
@ -32,10 +32,9 @@
|
|||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
import os.path
|
||||
import pickle
|
||||
|
||||
import dbus
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
@ -44,16 +43,37 @@ import gtk, gtk.glade
|
|||
import deluge.common
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class CoreProxy:
|
||||
def __init__(self):
|
||||
self._uri = None
|
||||
self._core = None
|
||||
self._on_new_core_callbacks = []
|
||||
|
||||
def connect_on_new_core(self, callback):
|
||||
"""Connect a callback to be called when a new core is connected to."""
|
||||
self._on_new_core_callbacks.append(callback)
|
||||
|
||||
def set_core_uri(self, uri):
|
||||
log.info("Setting core uri as %s", uri)
|
||||
self._uri = uri
|
||||
# Get a new core
|
||||
self.get_core()
|
||||
|
||||
def get_core(self):
|
||||
if self._core is None and self._uri is not None:
|
||||
log.debug("Creating ServerProxy..")
|
||||
self._core = xmlrpclib.ServerProxy(self._uri)
|
||||
# Call any callbacks registered
|
||||
for callback in self._on_new_core_callbacks:
|
||||
callback()
|
||||
|
||||
return self._core
|
||||
|
||||
_core = CoreProxy()
|
||||
|
||||
def get_core():
|
||||
"""Get the core object and return it"""
|
||||
log.debug("Getting core proxy object from DBUS..")
|
||||
# Get the proxy object from DBUS
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object("org.deluge_torrent.Deluge",
|
||||
"/org/deluge_torrent/Core")
|
||||
core = dbus.Interface(proxy, "org.deluge_torrent.Deluge")
|
||||
log.debug("Got core proxy object..")
|
||||
return core
|
||||
return _core.get_core()
|
||||
|
||||
def get_core_plugin(plugin):
|
||||
"""Get the core plugin object and return it"""
|
||||
|
@ -64,11 +84,17 @@ def get_core_plugin(plugin):
|
|||
core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin)
|
||||
return core
|
||||
|
||||
def connect_on_new_core(callback):
|
||||
"""Connect a callback whenever a new core is connected to."""
|
||||
return _core.connect_on_new_core(callback)
|
||||
|
||||
def set_core_uri(uri):
|
||||
"""Sets the core uri"""
|
||||
return _core.set_core_uri(uri)
|
||||
|
||||
def shutdown():
|
||||
"""Shutdown the core daemon"""
|
||||
core = get_core()
|
||||
core.shutdown()
|
||||
return
|
||||
get_core().shutdown()
|
||||
|
||||
def add_torrent_file(torrent_files):
|
||||
"""Adds torrent files to the core
|
||||
|
@ -78,25 +104,25 @@ def add_torrent_file(torrent_files):
|
|||
log.debug("No torrent files selected..")
|
||||
return
|
||||
log.debug("Attempting to add torrent files: %s", torrent_files)
|
||||
core = get_core()
|
||||
for torrent_file in torrent_files:
|
||||
# Open the .torrent file for reading because we need to send it's
|
||||
# contents to the core.
|
||||
f = open(torrent_file, "rb")
|
||||
# Get the filename because the core doesn't want a path.
|
||||
(path, filename) = os.path.split(torrent_file)
|
||||
result = core.add_torrent_file(filename, str(), f.read())
|
||||
fdump = xmlrpclib.Binary(f.read())
|
||||
f.close()
|
||||
result = get_core().add_torrent_file(filename, str(), fdump)
|
||||
|
||||
if result is False:
|
||||
# The torrent was not added successfully.
|
||||
log.warning("Torrent %s was not added successfully.", filename)
|
||||
|
||||
def add_torrent_url(torrent_url):
|
||||
"""Adds torrents to the core via url"""
|
||||
core = get_core()
|
||||
from deluge.common import is_url
|
||||
if is_url(torrent_url):
|
||||
result = core.add_torrent_url(torrent_url, str())
|
||||
result = get_core().add_torrent_url(torrent_url, str())
|
||||
if result is False:
|
||||
# The torrent url was not added successfully.
|
||||
log.warning("Torrent %s was not added successfully.", torrent_url)
|
||||
|
@ -106,70 +132,62 @@ def add_torrent_url(torrent_url):
|
|||
def remove_torrent(torrent_ids):
|
||||
"""Removes torrent_ids from the core.. Expects a list of torrent_ids"""
|
||||
log.debug("Attempting to removing torrents: %s", torrent_ids)
|
||||
core = get_core()
|
||||
for torrent_id in torrent_ids:
|
||||
core.remove_torrent(torrent_id)
|
||||
get_core().remove_torrent(torrent_id)
|
||||
|
||||
def pause_torrent(torrent_ids):
|
||||
"""Pauses torrent_ids"""
|
||||
core = get_core()
|
||||
for torrent_id in torrent_ids:
|
||||
core.pause_torrent(torrent_id)
|
||||
get_core().pause_torrent(torrent_id)
|
||||
|
||||
def resume_torrent(torrent_ids):
|
||||
"""Resume torrent_ids"""
|
||||
core = get_core()
|
||||
for torrent_id in torrent_ids:
|
||||
core.resume_torrent(torrent_id)
|
||||
get_core().resume_torrent(torrent_id)
|
||||
|
||||
def force_reannounce(torrent_ids):
|
||||
"""Reannounce to trackers"""
|
||||
core = get_core()
|
||||
for torrent_id in torrent_ids:
|
||||
core.force_reannounce(torrent_id)
|
||||
get_core().force_reannounce(torrent_id)
|
||||
|
||||
def get_torrent_status(core, torrent_id, keys):
|
||||
def get_torrent_status(torrent_id, keys):
|
||||
"""Builds the status dictionary and returns it"""
|
||||
return deluge.common.pythonize(core.get_torrent_status(torrent_id, keys))
|
||||
status = get_core().get_torrent_status(torrent_id, keys)
|
||||
return pickle.loads(status.data)
|
||||
|
||||
def get_session_state(core=None):
|
||||
# Get the core if not supplied
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return deluge.common.pythonize(core.get_session_state())
|
||||
def get_session_state():
|
||||
return get_core().get_session_state()
|
||||
|
||||
def get_config(core=None):
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return deluge.common.pythonize(core.get_config())
|
||||
|
||||
def get_config_value(key, core=None):
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return deluge.common.pythonize(core.get_config_value(key))
|
||||
def get_config():
|
||||
return get_core().get_config()
|
||||
|
||||
def get_config_value(key):
|
||||
return get_core().get_config_value(key)
|
||||
|
||||
def set_config(config, core=None):
|
||||
def set_config(config):
|
||||
if config == {}:
|
||||
return
|
||||
if core is None:
|
||||
core = get_core()
|
||||
core.set_config(config)
|
||||
get_core().set_config(config)
|
||||
|
||||
def get_listen_port(core=None):
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return int(core.get_listen_port())
|
||||
def get_listen_port():
|
||||
return int(get_core().get_listen_port())
|
||||
|
||||
def get_available_plugins(core=None):
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return deluge.common.pythonize(core.get_available_plugins())
|
||||
def get_available_plugins():
|
||||
return get_core().get_available_plugins()
|
||||
|
||||
def get_enabled_plugins(core=None):
|
||||
if core is None:
|
||||
core = get_core()
|
||||
return deluge.common.pythonize(core.get_enabled_plugins())
|
||||
def get_enabled_plugins():
|
||||
return get_core().get_enabled_plugins()
|
||||
|
||||
def get_download_rate():
|
||||
return get_core().get_download_rate()
|
||||
|
||||
def get_upload_rate():
|
||||
return get_core().get_upload_rate()
|
||||
|
||||
def get_num_connections():
|
||||
return get_core().get_num_connections()
|
||||
|
||||
def open_url_in_browser(url):
|
||||
"""Opens link in the desktop's default browser"""
|
||||
def start_browser():
|
|
@ -37,13 +37,13 @@ import gtk
|
|||
import pkg_resources
|
||||
|
||||
import deluge.common
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
|
||||
class AboutDialog:
|
||||
def __init__(self):
|
||||
# Get the glade file for the about dialog
|
||||
def url_hook(dialog, url):
|
||||
functions.open_url_in_browser(url)
|
||||
client.open_url_in_browser(url)
|
||||
gtk.about_dialog_set_url_hook(url_hook)
|
||||
self.about = gtk.glade.XML(pkg_resources.resource_filename(\
|
||||
"deluge.ui.gtkui", "glade/aboutdialog.glade")).get_widget(\
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
#
|
||||
# connectionmanager.py
|
||||
#
|
||||
# Copyright (C) 2007 Andrew Resch ('andar') <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 2 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 gtk, gtk.glade
|
||||
import pkg_resources
|
||||
import gobject
|
||||
import socket
|
||||
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
import deluge.common
|
||||
import deluge.ui.client as client
|
||||
from deluge.configmanager import ConfigManager
|
||||
from deluge.log import LOG as log
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"hosts": ["localhost:58846"]
|
||||
}
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self, window):
|
||||
# Get the glade file for the connection manager
|
||||
self.glade = gtk.glade.XML(
|
||||
pkg_resources.resource_filename("deluge.ui.gtkui",
|
||||
"glade/connection_manager.glade"))
|
||||
|
||||
self.window = window
|
||||
self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG)
|
||||
self.connection_manager = self.glade.get_widget("connection_manager")
|
||||
self.hostlist = self.glade.get_widget("hostlist")
|
||||
self.connection_manager.set_icon(deluge.common.get_logo(16))
|
||||
|
||||
self.glade.get_widget("image1").set_from_pixbuf(
|
||||
deluge.common.get_logo(32))
|
||||
|
||||
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
|
||||
|
||||
# Fill in hosts from config file
|
||||
for host in self.config["hosts"]:
|
||||
row = self.liststore.append()
|
||||
self.liststore.set_value(row, 1, host)
|
||||
|
||||
# Setup host list treeview
|
||||
self.hostlist.set_model(self.liststore)
|
||||
render = gtk.CellRendererPixbuf()
|
||||
column = gtk.TreeViewColumn("Status", render, pixbuf=0)
|
||||
self.hostlist.append_column(column)
|
||||
render = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Host", render, text=1)
|
||||
self.hostlist.append_column(column)
|
||||
|
||||
self.glade.signal_autoconnect({
|
||||
"on_button_addhost_clicked": self.on_button_addhost_clicked,
|
||||
"on_button_removehost_clicked": self.on_button_removehost_clicked,
|
||||
"on_button_startdaemon_clicked": \
|
||||
self.on_button_startdaemon_clicked,
|
||||
"on_button_cancel_clicked": self.on_button_cancel_clicked,
|
||||
"on_button_connect_clicked": self.on_button_connect_clicked,
|
||||
})
|
||||
|
||||
self.connection_manager.connect("delete-event", self.on_delete_event)
|
||||
|
||||
def show(self):
|
||||
self.update_timer = gobject.timeout_add(5000, self.update)
|
||||
self.update()
|
||||
self.connection_manager.show_all()
|
||||
|
||||
def hide(self):
|
||||
self.connection_manager.hide()
|
||||
gobject.source_remove(self.update_timer)
|
||||
|
||||
def update(self):
|
||||
"""Updates the host status"""
|
||||
def update_row(model=None, path=None, row=None, columns=None):
|
||||
uri = model.get_value(row, 1)
|
||||
uri = "http://" + uri
|
||||
online = True
|
||||
host = None
|
||||
try:
|
||||
host = xmlrpclib.ServerProxy(uri)
|
||||
host.ping()
|
||||
except socket.error:
|
||||
print "socket.error!"
|
||||
online = False
|
||||
|
||||
print "online: ", online
|
||||
del host
|
||||
|
||||
if online:
|
||||
image = gtk.STOCK_YES
|
||||
else:
|
||||
image = gtk.STOCK_NO
|
||||
|
||||
pixbuf = self.connection_manager.render_icon(
|
||||
image, gtk.ICON_SIZE_MENU)
|
||||
|
||||
model.set_value(row, 0, pixbuf)
|
||||
|
||||
self.liststore.foreach(update_row)
|
||||
return True
|
||||
|
||||
def save(self):
|
||||
"""Save the current host list to file"""
|
||||
def append_row(model=None, path=None, row=None, columns=None):
|
||||
hostlist.append(model.get_value(row, 1))
|
||||
|
||||
hostlist = []
|
||||
self.liststore.foreach(append_row, hostlist)
|
||||
self.config["hosts"] = hostlist
|
||||
self.config.save()
|
||||
|
||||
## Callbacks
|
||||
def on_delete_event(self, widget, event):
|
||||
self.hide()
|
||||
return True
|
||||
|
||||
def on_button_addhost_clicked(self, widget):
|
||||
log.debug("on_button_addhost_clicked")
|
||||
dialog = self.glade.get_widget("addhost_dialog")
|
||||
dialog.set_icon(deluge.common.get_logo(16))
|
||||
hostname_entry = self.glade.get_widget("entry_hostname")
|
||||
port_spinbutton = self.glade.get_widget("spinbutton_port")
|
||||
response = dialog.run()
|
||||
if response == 1:
|
||||
# We add the host
|
||||
hostname = hostname_entry.get_text()
|
||||
if hostname.startswith("http://"):
|
||||
hostname = hostname[7:]
|
||||
|
||||
# Check to make sure the hostname is at least 1 character long
|
||||
if len(hostname) < 1:
|
||||
dialog.hide()
|
||||
return
|
||||
|
||||
# Get the port and concatenate the hostname string
|
||||
port = port_spinbutton.get_value_as_int()
|
||||
hostname = hostname + ":" + str(port)
|
||||
row = self.liststore.append()
|
||||
self.liststore.set_value(row, 1, hostname)
|
||||
# Save the host list to file
|
||||
self.save()
|
||||
|
||||
dialog.hide()
|
||||
|
||||
def on_button_removehost_clicked(self, widget):
|
||||
log.debug("on_button_removehost_clicked")
|
||||
# Get the selected rows
|
||||
paths = self.hostlist.get_selection().get_selected_rows()[1]
|
||||
for path in paths:
|
||||
self.liststore.remove(self.liststore.get_iter(path))
|
||||
|
||||
# Save the host list
|
||||
self.save()
|
||||
|
||||
def on_button_startdaemon_clicked(self, widget):
|
||||
log.debug("on_button_startdaemon_clicked")
|
||||
|
||||
def on_button_cancel_clicked(self, widget):
|
||||
log.debug("on_button_cancel_clicked")
|
||||
self.hide()
|
||||
|
||||
def on_button_connect_clicked(self, widget):
|
||||
log.debug("on_button_connect_clicked")
|
||||
paths = self.hostlist.get_selection().get_selected_rows()[1]
|
||||
row = self.liststore.get_iter(paths[0])
|
||||
uri = self.liststore.get_value(row, 1)
|
||||
uri = "http://" + uri
|
||||
client.set_core_uri(uri)
|
||||
self.window.start()
|
||||
self.hide()
|
|
@ -0,0 +1,375 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.2.2 on Tue Oct 16 23:05:06 2007 by andrew@fragment-->
|
||||
<glade-interface>
|
||||
<widget class="GtkDialog" id="connection_manager">
|
||||
<property name="has_focus">True</property>
|
||||
<property name="is_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="title" translatable="yes">Connection Manager</property>
|
||||
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||
<property name="default_width">350</property>
|
||||
<property name="default_height">300</property>
|
||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||
<property name="has_separator">False</property>
|
||||
<child internal-child="vbox">
|
||||
<widget class="GtkVBox" id="dialog-vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes"><big><b>Connection Manager</b></big></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkViewport" id="viewport1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="resize_mode">GTK_RESIZE_QUEUE</property>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="hostlist">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="headers_clickable">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_START</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_addhost">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-add</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_button_addhost_clicked"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_removehost">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-remove</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_button_removehost_clicked"/>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_startdaemon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_button_startdaemon_clicked"/>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="stock">gtk-execute</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Start local daemon</property>
|
||||
<property name="use_underline">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">10</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkExpander" id="expander1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="checkbutton1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">checkbutton</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="action_area">
|
||||
<widget class="GtkHButtonBox" id="dialog-action_area2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_cancel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_button_cancel_clicked"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_connect">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-connect</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_button_connect_clicked"/>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkDialog" id="addhost_dialog">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="title" translatable="yes">Add Host</property>
|
||||
<property name="window_position">GTK_WIN_POS_CENTER</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||
<property name="has_separator">False</property>
|
||||
<child internal-child="vbox">
|
||||
<widget class="GtkVBox" id="dialog-vbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Hostname:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="entry_hostname">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Port:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSpinButton" id="spinbutton_port">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="max_length">5</property>
|
||||
<property name="width_chars">5</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="adjustment">58846 1 65535 1 10 10</property>
|
||||
<property name="numeric">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="action_area">
|
||||
<widget class="GtkHButtonBox" id="dialog-action_area3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_addhost_cancel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="button_addhost_add">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-add</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">1</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
File diff suppressed because it is too large
Load Diff
|
@ -93,15 +93,16 @@ class GtkUI:
|
|||
self.mainwindow = MainWindow()
|
||||
|
||||
# Start the signal receiver
|
||||
self.signal_receiver = Signals(self)
|
||||
|
||||
#self.signal_receiver = Signals(self)
|
||||
|
||||
# Initalize the plugins
|
||||
self.plugins = PluginManager(self)
|
||||
|
||||
# Start the mainwindow and show it
|
||||
self.mainwindow.start()
|
||||
#self.mainwindow.start()
|
||||
|
||||
# Start the gtk main loop
|
||||
gtk.gdk.threads_init()
|
||||
gtk.main()
|
||||
|
||||
log.debug("gtkui shutting down..")
|
||||
|
@ -112,6 +113,6 @@ class GtkUI:
|
|||
|
||||
# Clean-up
|
||||
del self.mainwindow
|
||||
del self.signal_receiver
|
||||
# del self.signal_receiver
|
||||
del self.plugins
|
||||
del deluge.configmanager
|
||||
|
|
|
@ -37,6 +37,7 @@ import gtk, gtk.glade
|
|||
import gobject
|
||||
import pkg_resources
|
||||
|
||||
import deluge.ui.client as client
|
||||
from deluge.configmanager import ConfigManager
|
||||
from menubar import MenuBar
|
||||
from toolbar import ToolBar
|
||||
|
@ -45,6 +46,7 @@ from torrentdetails import TorrentDetails
|
|||
from preferences import Preferences
|
||||
from systemtray import SystemTray
|
||||
from statusbar import StatusBar
|
||||
from connectionmanager import ConnectionManager
|
||||
import deluge.common
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
@ -81,15 +83,19 @@ class MainWindow:
|
|||
self.preferences = Preferences(self)
|
||||
self.systemtray = SystemTray(self)
|
||||
self.statusbar = StatusBar(self)
|
||||
|
||||
def start(self):
|
||||
"""Start the update thread and show the window"""
|
||||
self.update_timer = gobject.timeout_add(1000, self.update)
|
||||
self.connectionmanager = ConnectionManager(self)
|
||||
client.connect_on_new_core(self.start)
|
||||
if not(self.config["start_in_tray"] and \
|
||||
self.config["enable_system_tray"]) and not \
|
||||
self.window.get_property("visible"):
|
||||
log.debug("Showing window")
|
||||
self.show()
|
||||
self.connectionmanager.show()
|
||||
|
||||
def start(self):
|
||||
"""Start the update thread and show the window"""
|
||||
self.torrentview.start()
|
||||
self.update_timer = gobject.timeout_add(1000, self.update)
|
||||
|
||||
def update(self):
|
||||
# Don't update the UI if the the window is minimized.
|
||||
|
@ -121,7 +127,10 @@ class MainWindow:
|
|||
|
||||
def quit(self):
|
||||
# Stop the update timer from running
|
||||
gobject.source_remove(self.update_timer)
|
||||
try:
|
||||
gobject.source_remove(self.update_timer)
|
||||
except:
|
||||
pass
|
||||
del self.systemtray
|
||||
del self.menubar
|
||||
del self.toolbar
|
||||
|
|
|
@ -36,7 +36,7 @@ pygtk.require('2.0')
|
|||
import gtk, gtk.glade
|
||||
import pkg_resources
|
||||
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
|
@ -70,6 +70,8 @@ class MenuBar:
|
|||
## Edit Menu
|
||||
"on_menuitem_preferences_activate": \
|
||||
self.on_menuitem_preferences_activate,
|
||||
"on_menuitem_connectionmanager_activate": \
|
||||
self.on_menuitem_connectionmanager_activate,
|
||||
|
||||
## View Menu
|
||||
"on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled,
|
||||
|
@ -97,14 +99,14 @@ class MenuBar:
|
|||
def on_menuitem_addtorrent_activate(self, data=None):
|
||||
log.debug("on_menuitem_addtorrent_activate")
|
||||
from addtorrentdialog import AddTorrentDialog
|
||||
functions.add_torrent_file(AddTorrentDialog().run())
|
||||
client.add_torrent_file(AddTorrentDialog().run())
|
||||
|
||||
def on_menuitem_addurl_activate(self, data=None):
|
||||
log.debug("on_menuitem_addurl_activate")
|
||||
from addtorrenturl import AddTorrentUrl
|
||||
result = AddTorrentUrl().run()
|
||||
if result is not None:
|
||||
functions.add_torrent_url(result)
|
||||
client.add_torrent_url(result)
|
||||
|
||||
def on_menuitem_clear_activate(self, data=None):
|
||||
log.debug("on_menuitem_clear_activate")
|
||||
|
@ -113,7 +115,7 @@ class MenuBar:
|
|||
log.debug("on_menuitem_quitdaemon_activate")
|
||||
# Tell the core to shutdown
|
||||
self.window.quit()
|
||||
functions.shutdown()
|
||||
client.shutdown()
|
||||
|
||||
def on_menuitem_quit_activate(self, data=None):
|
||||
log.debug("on_menuitem_quit_activate")
|
||||
|
@ -124,20 +126,24 @@ class MenuBar:
|
|||
log.debug("on_menuitem_preferences_activate")
|
||||
self.window.preferences.show()
|
||||
|
||||
def on_menuitem_connectionmanager_activate(self, data=None):
|
||||
log.debug("on_menuitem_connectionmanager_activate")
|
||||
self.window.connectionmanager.show()
|
||||
|
||||
## Torrent Menu ##
|
||||
def on_menuitem_pause_activate(self, data=None):
|
||||
log.debug("on_menuitem_pause_activate")
|
||||
functions.pause_torrent(
|
||||
client.pause_torrent(
|
||||
self.window.torrentview.get_selected_torrents())
|
||||
|
||||
def on_menuitem_resume_activate(self, data=None):
|
||||
log.debug("on_menuitem_resume_activate")
|
||||
functions.resume_torrent(
|
||||
client.resume_torrent(
|
||||
self.window.torrentview.get_selected_torrents())
|
||||
|
||||
def on_menuitem_updatetracker_activate(self, data=None):
|
||||
log.debug("on_menuitem_updatetracker_activate")
|
||||
functions.force_reannounce(
|
||||
client.force_reannounce(
|
||||
self.window.torrentview.get_selected_torrents())
|
||||
|
||||
def on_menuitem_edittrackers_activate(self, data=None):
|
||||
|
@ -145,7 +151,7 @@ class MenuBar:
|
|||
|
||||
def on_menuitem_remove_activate(self, data=None):
|
||||
log.debug("on_menuitem_remove_activate")
|
||||
functions.remove_torrent(
|
||||
client.remove_torrent(
|
||||
self.window.torrentview.get_selected_torrents())
|
||||
|
||||
## View Menu ##
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
import deluge.pluginmanagerbase
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
from deluge.configmanager import ConfigManager
|
||||
from deluge.log import LOG as log
|
||||
|
||||
|
@ -41,16 +41,21 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase):
|
|||
|
||||
self.config = ConfigManager("gtkui.conf")
|
||||
self._gtkui = gtkui
|
||||
|
||||
|
||||
# Register a callback with the client
|
||||
client.connect_on_new_core(self.start)
|
||||
|
||||
def start(self):
|
||||
"""Start the plugin manager"""
|
||||
# Update the enabled_plugins from the core
|
||||
enabled_plugins = functions.get_enabled_plugins()
|
||||
enabled_plugins = client.get_enabled_plugins()
|
||||
enabled_plugins += self.config["enabled_plugins"]
|
||||
enabled_plugins = list(set(enabled_plugins))
|
||||
self.config["enabled_plugins"] = enabled_plugins
|
||||
|
||||
deluge.pluginmanagerbase.PluginManagerBase.__init__(
|
||||
self, "gtkui.conf", "deluge.plugin.ui.gtk")
|
||||
|
||||
|
||||
def get_torrentview(self):
|
||||
"""Returns a reference to the torrentview component"""
|
||||
return self._gtkui.mainwindow.torrentview
|
||||
|
|
|
@ -37,7 +37,7 @@ import gtk, gtk.glade
|
|||
import pkg_resources
|
||||
|
||||
from deluge.log import LOG as log
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
import deluge.common
|
||||
from deluge.configmanager import ConfigManager
|
||||
|
||||
|
@ -51,7 +51,6 @@ class Preferences:
|
|||
self.pref_dialog.set_icon(deluge.common.get_logo(32))
|
||||
self.treeview = self.glade.get_widget("treeview")
|
||||
self.notebook = self.glade.get_widget("notebook")
|
||||
self.core = functions.get_core()
|
||||
self.gtkui_config = ConfigManager("gtkui.conf")
|
||||
# Setup the liststore for the categories (tab pages)
|
||||
self.liststore = gtk.ListStore(int, str)
|
||||
|
@ -104,7 +103,7 @@ class Preferences:
|
|||
self.liststore.append([index, name])
|
||||
|
||||
def show(self):
|
||||
self.core_config = functions.get_config(self.core)
|
||||
self.core_config = client.get_config()
|
||||
# Update the preferences dialog to reflect current config settings
|
||||
|
||||
## Downloads tab ##
|
||||
|
@ -134,7 +133,7 @@ class Preferences:
|
|||
self.glade.get_widget("spin_port_max").set_value(
|
||||
self.core_config["listen_ports"][1])
|
||||
self.glade.get_widget("active_port_label").set_text(
|
||||
str(functions.get_listen_port(self.core)))
|
||||
str(client.get_listen_port()))
|
||||
self.glade.get_widget("chk_random_port").set_active(
|
||||
self.core_config["random_port"])
|
||||
self.glade.get_widget("chk_dht").set_active(
|
||||
|
@ -193,8 +192,8 @@ class Preferences:
|
|||
self.gtkui_config["send_info"])
|
||||
|
||||
## Plugins tab ##
|
||||
all_plugins = functions.get_available_plugins()
|
||||
enabled_plugins = functions.get_enabled_plugins()
|
||||
all_plugins = client.get_available_plugins()
|
||||
enabled_plugins = client.get_enabled_plugins()
|
||||
# Clear the existing list so we don't duplicate entries.
|
||||
self.plugin_liststore.clear()
|
||||
# Iterate through the lists and add them to the liststore
|
||||
|
@ -310,7 +309,7 @@ class Preferences:
|
|||
config_to_set[key] = new_core_config[key]
|
||||
|
||||
# Set each changed config value in the core
|
||||
functions.set_config(config_to_set, self.core)
|
||||
client.set_config(config_to_set)
|
||||
|
||||
# Update the configuration
|
||||
self.core_config.update(config_to_set)
|
||||
|
@ -387,8 +386,8 @@ class Preferences:
|
|||
def on_test_port_clicked(self, data):
|
||||
log.debug("on_test_port_clicked")
|
||||
url = "http://deluge-torrent.org/test-port.php?port=%s" % \
|
||||
functions.get_listen_port(self.core)
|
||||
functions.open_url_in_browser(url)
|
||||
client.get_listen_port()
|
||||
client.open_url_in_browser(url)
|
||||
|
||||
def on_plugin_toggled(self, renderer, path):
|
||||
log.debug("on_plugin_toggled")
|
||||
|
|
|
@ -31,23 +31,25 @@
|
|||
# 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 deluge.ui.functions as functions
|
||||
from deluge.ui.signalreceiver import SignalReceiver
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class Signals:
|
||||
def __init__(self, ui):
|
||||
self.ui = ui
|
||||
self.core = functions.get_core()
|
||||
self.core.connect_to_signal("torrent_added", self.torrent_added_signal)
|
||||
self.core.connect_to_signal("torrent_removed",
|
||||
self.torrent_removed_signal)
|
||||
self.core.connect_to_signal("torrent_paused", self.torrent_paused)
|
||||
self.core.connect_to_signal("torrent_resumed", self.torrent_resumed)
|
||||
self.core.connect_to_signal("torrent_all_paused",
|
||||
self.receiver = SignalReceiver(6667, "http://localhost:56684")
|
||||
self.receiver.start()
|
||||
self.receiver.connect_to_signal("torrent_added",
|
||||
self.torrent_added_signal)
|
||||
self.receiver.connect_to_signal("torrent_removed",
|
||||
self.torrent_removed_signal)
|
||||
self.receiver.connect_to_signal("torrent_paused", self.torrent_paused)
|
||||
self.receiver.connect_to_signal("torrent_resumed", self.torrent_resumed)
|
||||
self.receiver.connect_to_signal("torrent_all_paused",
|
||||
self.torrent_all_paused)
|
||||
self.core.connect_to_signal("torrent_all_resumed",
|
||||
self.receiver.connect_to_signal("torrent_all_resumed",
|
||||
self.torrent_all_resumed)
|
||||
|
||||
|
||||
def torrent_added_signal(self, torrent_id):
|
||||
log.debug("torrent_added signal received..")
|
||||
log.debug("torrent id: %s", torrent_id)
|
||||
|
|
|
@ -34,13 +34,12 @@
|
|||
import gtk
|
||||
|
||||
import deluge.common
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
|
||||
class StatusBar:
|
||||
def __init__(self, window):
|
||||
self.window = window
|
||||
self.statusbar = self.window.main_glade.get_widget("statusbar")
|
||||
self.core = functions.get_core()
|
||||
|
||||
# Add a HBox to the statusbar after removing the initial label widget
|
||||
self.hbox = gtk.HBox()
|
||||
|
@ -69,39 +68,36 @@ class StatusBar:
|
|||
expand=False, fill=False)
|
||||
|
||||
# Update once before showing
|
||||
self.update()
|
||||
# self.update()
|
||||
self.statusbar.show_all()
|
||||
|
||||
def update(self):
|
||||
# Set the max connections label
|
||||
max_connections = functions.get_config_value("max_connections_global",
|
||||
core=self.core)
|
||||
max_connections = client.get_config_value("max_connections_global")
|
||||
if max_connections < 0:
|
||||
max_connections = _("Unlimited")
|
||||
|
||||
self.label_connections.set_text("%s (%s)" % (
|
||||
self.core.get_num_connections(), max_connections))
|
||||
client.get_num_connections(), max_connections))
|
||||
|
||||
# Set the download speed label
|
||||
max_download_speed = functions.get_config_value("max_download_speed",
|
||||
core=self.core)
|
||||
max_download_speed = client.get_config_value("max_download_speed")
|
||||
if max_download_speed < 0:
|
||||
max_download_speed = _("Unlimited")
|
||||
else:
|
||||
max_download_speed = "%s %s" % (max_download_speed, _("KiB/s"))
|
||||
|
||||
self.label_download_speed.set_text("%s/s (%s)" % (
|
||||
deluge.common.fsize(self.core.get_download_rate()),
|
||||
deluge.common.fsize(client.get_download_rate()),
|
||||
max_download_speed))
|
||||
|
||||
# Set the upload speed label
|
||||
max_upload_speed = functions.get_config_value("max_upload_speed",
|
||||
core=self.core)
|
||||
max_upload_speed = client.get_config_value("max_upload_speed")
|
||||
if max_upload_speed < 0:
|
||||
max_upload_speed = _("Unlimited")
|
||||
else:
|
||||
max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s"))
|
||||
|
||||
self.label_upload_speed.set_text("%s/s (%s)" % (
|
||||
deluge.common.fsize(self.core.get_upload_rate()),
|
||||
deluge.common.fsize(client.get_upload_rate()),
|
||||
max_upload_speed))
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
import gtk
|
||||
import pkg_resources
|
||||
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
import deluge.common
|
||||
from deluge.configmanager import ConfigManager
|
||||
from deluge.log import LOG as log
|
||||
|
@ -49,7 +49,6 @@ class SystemTray:
|
|||
def enable(self):
|
||||
"""Enables the system tray icon."""
|
||||
log.debug("Enabling the system tray icon..")
|
||||
self.core = functions.get_core()
|
||||
self.tray = gtk.status_icon_new_from_icon_name('deluge')
|
||||
self.tray.connect("activate", self.on_tray_clicked)
|
||||
self.tray.connect("popup-menu", self.on_tray_popup)
|
||||
|
@ -79,6 +78,7 @@ class SystemTray:
|
|||
self.tray_glade.get_widget("upload-limit-image").set_from_file(
|
||||
deluge.common.get_pixmap("seeding16.png"))
|
||||
|
||||
def start(self):
|
||||
# Build the bandwidth speed limit menus
|
||||
self.build_tray_bwsetsubmenu()
|
||||
|
||||
|
@ -86,14 +86,13 @@ class SystemTray:
|
|||
# Create the Download speed list sub-menu
|
||||
submenu_bwdownset = self.build_menu_radio_list(
|
||||
self.config["tray_download_speed_list"], self.tray_setbwdown,
|
||||
functions.get_config_value("max_download_speed",
|
||||
core=self.core), _("KiB/s"), show_notset=True,
|
||||
show_other=True)
|
||||
client.get_config_value("max_download_speed"),
|
||||
_("KiB/s"), show_notset=True, show_other=True)
|
||||
|
||||
# Create the Upload speed list sub-menu
|
||||
submenu_bwupset = self.build_menu_radio_list(
|
||||
self.config["tray_upload_speed_list"], self.tray_setbwup,
|
||||
functions.get_config_value("max_upload_speed", core=self.core),
|
||||
client.get_config_value("max_upload_speed"),
|
||||
_("KiB/s"), show_notset=True, show_other=True)
|
||||
|
||||
# Add the sub-menus to the tray menu
|
||||
|
@ -161,7 +160,7 @@ class SystemTray:
|
|||
def on_menuitem_add_torrent_activate(self, menuitem):
|
||||
log.debug("on_menuitem_add_torrent_activate")
|
||||
from addtorrentdialog import AddTorrentDialog
|
||||
functions.add_torrent_file(AddTorrentDialog().run())
|
||||
client.add_torrent_file(AddTorrentDialog().run())
|
||||
|
||||
def on_menuitem_pause_all_activate(self, menuitem):
|
||||
log.debug("on_menuitem_pause_all_activate")
|
||||
|
@ -185,13 +184,13 @@ class SystemTray:
|
|||
log.debug("on_menuitem_quitdaemon_activate")
|
||||
if self.window.visible():
|
||||
self.window.quit()
|
||||
functions.shutdown()
|
||||
client.shutdown()
|
||||
else:
|
||||
if self.config["lock_tray"] == True:
|
||||
self.unlock_tray("quitdaemon")
|
||||
else:
|
||||
self.window.quit()
|
||||
functions.shutdown()
|
||||
client.shutdown()
|
||||
|
||||
def build_menu_radio_list(self, value_list, callback, pref_value=None,
|
||||
suffix=None, show_notset=False, notset_label=None, notset_lessthan=0,
|
||||
|
@ -282,7 +281,7 @@ class SystemTray:
|
|||
spin_title.set_text(_("%s Speed (KiB/s):" % string))
|
||||
spin_speed = dialog_glade.get_widget("spin_speed")
|
||||
spin_speed.set_value(
|
||||
functions.get_config_value(core_key, core=self.core))
|
||||
client.get_config_value(core_key))
|
||||
spin_speed.select_region(0, -1)
|
||||
response = speed_dialog.run()
|
||||
if response == 1: # OK Response
|
||||
|
@ -295,7 +294,7 @@ class SystemTray:
|
|||
# Set the config in the core
|
||||
value = float(value)
|
||||
config_to_set = {core_key: value}
|
||||
functions.set_config(config_to_set, core=self.core)
|
||||
client.set_config(config_to_set)
|
||||
|
||||
# Update the tray speed limit list
|
||||
if value not in self.config[ui_key] and value >= 0:
|
||||
|
@ -339,7 +338,7 @@ window, please enter your password"))
|
|||
log.debug("Showing main window via tray")
|
||||
self.window.show()
|
||||
elif comingnext == "quitdaemon":
|
||||
functions.shutdown()
|
||||
client.shutdown()
|
||||
self.window.hide()
|
||||
self.window.quit()
|
||||
elif comingnext == "quitui":
|
||||
|
|
|
@ -38,7 +38,7 @@ pygtk.require('2.0')
|
|||
import gtk, gtk.glade
|
||||
import gettext
|
||||
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
import deluge.common
|
||||
from deluge.log import LOG as log
|
||||
|
||||
|
@ -47,8 +47,6 @@ class TorrentDetails:
|
|||
self.window = window
|
||||
glade = self.window.main_glade
|
||||
|
||||
self.core = functions.get_core()
|
||||
|
||||
self.notebook = glade.get_widget("torrent_info")
|
||||
self.details_tab = glade.get_widget("torrentdetails_tab")
|
||||
|
||||
|
@ -95,9 +93,7 @@ class TorrentDetails:
|
|||
"upload_payload_rate", "num_peers", "num_seeds", "total_peers",
|
||||
"total_seeds", "eta", "ratio", "tracker", "next_announce",
|
||||
"tracker_status", "save_path"]
|
||||
status = functions.get_torrent_status(self.core,
|
||||
selected,
|
||||
status_keys)
|
||||
status = client.get_torrent_status(selected, status_keys)
|
||||
|
||||
# Check to see if we got valid data from the core
|
||||
if status is None:
|
||||
|
|
|
@ -40,7 +40,7 @@ import gettext
|
|||
import gobject
|
||||
|
||||
import deluge.common
|
||||
import deluge.ui.functions as functions
|
||||
import deluge.ui.client as client
|
||||
from deluge.log import LOG as log
|
||||
import deluge.ui.gtkui.listview as listview
|
||||
|
||||
|
@ -104,7 +104,6 @@ class TorrentView(listview.ListView):
|
|||
listview.ListView.__init__(self,
|
||||
self.window.main_glade.get_widget("torrent_view"))
|
||||
log.debug("TorrentView Init..")
|
||||
self.core = functions.get_core()
|
||||
|
||||
# Register the columns menu with the listview so it gets updated
|
||||
# accordingly.
|
||||
|
@ -164,12 +163,14 @@ class TorrentView(listview.ListView):
|
|||
self.treeview.get_selection().connect("changed",
|
||||
self.on_selection_changed)
|
||||
|
||||
def start(self):
|
||||
"""Start the torrentview"""
|
||||
# We need to get the core session state to know which torrents are in
|
||||
# the session so we can add them to our list.
|
||||
session_state = functions.get_session_state(self.core)
|
||||
session_state = client.get_session_state()
|
||||
for torrent_id in session_state:
|
||||
self.add_row(torrent_id)
|
||||
|
||||
|
||||
def update(self, columns=None):
|
||||
"""Update the view. If columns is not None, it will attempt to only
|
||||
update those columns selected.
|
||||
|
@ -213,7 +214,7 @@ class TorrentView(listview.ListView):
|
|||
|
||||
# Remove duplicates from status_key list
|
||||
status_keys = list(set(status_keys))
|
||||
status = functions.get_torrent_status(self.core, torrent_id,
|
||||
status = client.get_torrent_status(torrent_id,
|
||||
status_keys)
|
||||
|
||||
# Set values for each column in the row
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
#
|
||||
# signalreceiver.py
|
||||
#
|
||||
# Copyright (C) 2007 Andrew Resch ('andar') <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 2 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 sys
|
||||
import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer
|
||||
from SocketServer import ThreadingMixIn
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
import threading
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class SignalReceiver(
|
||||
threading.Thread,
|
||||
ThreadingMixIn,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
|
||||
def __init__(self, port, core_uri):
|
||||
log.debug("SignalReceiver init..")
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.port = port
|
||||
|
||||
# Daemonize the thread so it exits when the main program does
|
||||
self.setDaemon(True)
|
||||
|
||||
# Setup the xmlrpc server
|
||||
try:
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(
|
||||
self, ("localhost", port), logRequests=False, allow_none=True)
|
||||
except:
|
||||
log.info("SignalReceiver already running or port not available..")
|
||||
sys.exit(0)
|
||||
|
||||
self.signals = {}
|
||||
|
||||
# Register the emit_signal function
|
||||
self.register_function(self.emit_signal)
|
||||
|
||||
# Register the signal receiver with the core
|
||||
# FIXME: send actual URI not localhost
|
||||
core = xmlrpclib.ServerProxy(core_uri)
|
||||
core.register_client("http://localhost:" + str(port))
|
||||
|
||||
|
||||
def __del__(self):
|
||||
core.deregister_client("http://localhost:" + str(self.port))
|
||||
|
||||
def run(self):
|
||||
"""This gets called when we start the thread"""
|
||||
t = threading.Thread(target=self.serve_forever)
|
||||
t.start()
|
||||
|
||||
def emit_signal(self, signal, data):
|
||||
"""Exported method used by the core to emit a signal to the client"""
|
||||
log.debug("Received signal %s with data %s from core..", signal, data)
|
||||
try:
|
||||
if data != None:
|
||||
for callback in self.signals[signal]:
|
||||
try:
|
||||
callback(data)
|
||||
except:
|
||||
log.warning("Unable to call callback for signal %s",
|
||||
signal)
|
||||
else:
|
||||
for callback in self.signals[signal]:
|
||||
try:
|
||||
callback()
|
||||
except:
|
||||
log.warning("Unable to call callback for signal %s",
|
||||
signal)
|
||||
except KeyError:
|
||||
log.debug("There are no callbacks registered for this signal..")
|
||||
|
||||
def connect_to_signal(self, signal, callback):
|
||||
"""Connect to a signal"""
|
||||
try:
|
||||
self.signals[signal].append(callback)
|
||||
except KeyError:
|
||||
self.signals[signal] = [callback]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -87,10 +87,10 @@ namespace
|
|||
|
||||
torrent_handle add_torrent(session& s, torrent_info const& ti
|
||||
, boost::filesystem::path const& save, entry const& resume
|
||||
, bool compact, bool paused)
|
||||
, storage_mode_t storage_mode, bool paused)
|
||||
{
|
||||
allow_threading_guard guard;
|
||||
return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor);
|
||||
return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor);
|
||||
}
|
||||
|
||||
} // namespace unnamed
|
||||
|
@ -154,6 +154,17 @@ void bind_session()
|
|||
#endif
|
||||
;
|
||||
|
||||
enum_<storage_mode_t>("storage_mode_t")
|
||||
.value("storage_mode_allocate", storage_mode_allocate)
|
||||
.value("storage_mode_compact", storage_mode_compact)
|
||||
.value("storage_mode_sparse", storage_mode_sparse)
|
||||
;
|
||||
|
||||
enum_<session::options_t>("options_t")
|
||||
.value("none", session::none)
|
||||
.value("delete_files", session::delete_files)
|
||||
;
|
||||
|
||||
class_<session, boost::noncopyable>("session", session_doc, no_init)
|
||||
.def(
|
||||
init<fingerprint>(arg("fingerprint")=fingerprint("LT",0,1,0,0), session_init_doc)
|
||||
|
@ -174,8 +185,8 @@ void bind_session()
|
|||
.def(
|
||||
"add_torrent", &add_torrent
|
||||
, (
|
||||
arg("resume_data") = entry(), arg("compact_mode") = true
|
||||
, arg("paused") = false
|
||||
arg("resume_data") = entry(), arg("storage_mode") = storage_mode_sparse,
|
||||
arg("paused") = false
|
||||
)
|
||||
, session_add_torrent_doc
|
||||
)
|
||||
|
@ -235,8 +246,6 @@ void bind_session()
|
|||
.def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc)
|
||||
;
|
||||
|
||||
def("supports_sparse_files", &supports_sparse_files);
|
||||
|
||||
register_ptr_to_python<std::auto_ptr<alert> >();
|
||||
}
|
||||
|
||||
|
|
|
@ -252,6 +252,16 @@ namespace libtorrent
|
|||
{ return std::auto_ptr<alert>(new storage_moved_alert(*this)); }
|
||||
};
|
||||
|
||||
struct TORRENT_EXPORT torrent_deleted_alert: torrent_alert
|
||||
{
|
||||
torrent_deleted_alert(torrent_handle const& h, std::string const& msg)
|
||||
: torrent_alert(h, alert::warning, msg)
|
||||
{}
|
||||
|
||||
virtual std::auto_ptr<alert> clone() const
|
||||
{ return std::auto_ptr<alert>(new torrent_deleted_alert(*this)); }
|
||||
};
|
||||
|
||||
struct TORRENT_EXPORT torrent_paused_alert: torrent_alert
|
||||
{
|
||||
torrent_paused_alert(torrent_handle const& h, std::string const& msg)
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace libtorrent
|
|||
checker_impl(session_impl& s): m_ses(s), m_abort(false) {}
|
||||
void operator()();
|
||||
piece_checker_data* find_torrent(const sha1_hash& info_hash);
|
||||
void remove_torrent(sha1_hash const& info_hash);
|
||||
void remove_torrent(sha1_hash const& info_hash, int options);
|
||||
|
||||
#ifndef NDEBUG
|
||||
void check_invariant() const;
|
||||
|
@ -254,7 +254,7 @@ namespace libtorrent
|
|||
boost::intrusive_ptr<torrent_info> ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, storage_constructor_type sc
|
||||
, bool paused
|
||||
, void* userdata);
|
||||
|
@ -265,12 +265,12 @@ namespace libtorrent
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, storage_constructor_type sc
|
||||
, bool paused
|
||||
, void* userdata);
|
||||
|
||||
void remove_torrent(torrent_handle const& h);
|
||||
void remove_torrent(torrent_handle const& h, int options);
|
||||
|
||||
std::vector<torrent_handle> get_torrents();
|
||||
|
||||
|
@ -371,12 +371,6 @@ namespace libtorrent
|
|||
|
||||
void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih);
|
||||
|
||||
// handles disk io requests asynchronously
|
||||
// peers have pointers into the disk buffer
|
||||
// pool, and must be destructed before this
|
||||
// object.
|
||||
disk_io_thread m_disk_thread;
|
||||
|
||||
// this pool is used to allocate and recycle send
|
||||
// buffers from.
|
||||
boost::pool<> m_send_buffers;
|
||||
|
@ -395,6 +389,12 @@ namespace libtorrent
|
|||
// when they are destructed.
|
||||
file_pool m_files;
|
||||
|
||||
// handles disk io requests asynchronously
|
||||
// peers have pointers into the disk buffer
|
||||
// pool, and must be destructed before this
|
||||
// object.
|
||||
disk_io_thread m_disk_thread;
|
||||
|
||||
// this is a list of half-open tcp connections
|
||||
// (only outgoing connections)
|
||||
// this has to be one of the last
|
||||
|
|
|
@ -64,6 +64,7 @@ namespace libtorrent
|
|||
, hash
|
||||
, move_storage
|
||||
, release_files
|
||||
, delete_files
|
||||
};
|
||||
|
||||
action_t action;
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace libtorrent
|
|||
void set_non_prioritized(bool b)
|
||||
{ m_non_prioritized = b; }
|
||||
|
||||
void fast_reconnect(bool r) { m_fast_reconnect = r; }
|
||||
void fast_reconnect(bool r);
|
||||
bool fast_reconnect() const { return m_fast_reconnect; }
|
||||
|
||||
// this adds an announcement in the announcement queue
|
||||
|
|
|
@ -156,6 +156,8 @@ namespace libtorrent
|
|||
// this is true if the peer is a seed
|
||||
bool seed;
|
||||
|
||||
int fast_reconnects;
|
||||
|
||||
// true if this peer currently is unchoked
|
||||
// because of an optimistic unchoke.
|
||||
// when the optimistic unchoke is moved to
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace libtorrent
|
|||
: m_impl(impl) {}
|
||||
boost::shared_ptr<aux::session_impl> m_impl;
|
||||
};
|
||||
|
||||
|
||||
class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer
|
||||
{
|
||||
public:
|
||||
|
@ -140,7 +140,7 @@ namespace libtorrent
|
|||
torrent_info const& ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data = entry()
|
||||
, bool compact_mode = true
|
||||
, storage_mode_t storage_mode = storage_mode_sparse
|
||||
, bool paused = false
|
||||
, storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED;
|
||||
|
||||
|
@ -148,7 +148,7 @@ namespace libtorrent
|
|||
boost::intrusive_ptr<torrent_info> ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data = entry()
|
||||
, bool compact_mode = true
|
||||
, storage_mode_t storage_mode = storage_mode_sparse
|
||||
, bool paused = false
|
||||
, storage_constructor_type sc = default_storage_constructor
|
||||
, void* userdata = 0);
|
||||
|
@ -159,7 +159,7 @@ namespace libtorrent
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data = entry()
|
||||
, bool compact_mode = true
|
||||
, storage_mode_t storage_mode = storage_mode_sparse
|
||||
, bool paused = false
|
||||
, storage_constructor_type sc = default_storage_constructor
|
||||
, void* userdata = 0);
|
||||
|
@ -219,7 +219,13 @@ namespace libtorrent
|
|||
// number of half open connections.
|
||||
int num_connections() const;
|
||||
|
||||
void remove_torrent(const torrent_handle& h);
|
||||
enum options_t
|
||||
{
|
||||
none = 0,
|
||||
delete_files = 1
|
||||
};
|
||||
|
||||
void remove_torrent(const torrent_handle& h, int options = none);
|
||||
|
||||
void set_settings(session_settings const& s);
|
||||
session_settings const& settings();
|
||||
|
|
|
@ -99,7 +99,7 @@ namespace libtorrent
|
|||
, allow_multiple_connections_per_ip(false)
|
||||
, max_failcount(3)
|
||||
, min_reconnect_time(60)
|
||||
, peer_connect_timeout(10)
|
||||
, peer_connect_timeout(7)
|
||||
, ignore_limits_on_local_network(true)
|
||||
, connection_speed(20)
|
||||
, send_redundant_have(false)
|
||||
|
|
|
@ -71,6 +71,13 @@ namespace libtorrent
|
|||
struct file_pool;
|
||||
struct disk_io_job;
|
||||
|
||||
enum storage_mode_t
|
||||
{
|
||||
storage_mode_allocate = 0,
|
||||
storage_mode_sparse,
|
||||
storage_mode_compact
|
||||
};
|
||||
|
||||
#if defined(_WIN32) && defined(UNICODE)
|
||||
|
||||
TORRENT_EXPORT std::wstring safe_convert(std::string const& s);
|
||||
|
@ -144,6 +151,10 @@ namespace libtorrent
|
|||
// writing. This is called when a torrent has finished
|
||||
// downloading.
|
||||
virtual void release_files() = 0;
|
||||
|
||||
// this will close all open files and delete them
|
||||
virtual void delete_files() = 0;
|
||||
|
||||
virtual ~storage_interface() {}
|
||||
};
|
||||
|
||||
|
@ -155,10 +166,6 @@ namespace libtorrent
|
|||
boost::intrusive_ptr<torrent_info const> ti
|
||||
, fs::path const& path, file_pool& fp);
|
||||
|
||||
// returns true if the filesystem the path relies on supports
|
||||
// sparse files or automatic zero filling of files.
|
||||
TORRENT_EXPORT bool supports_sparse_files(fs::path const& p);
|
||||
|
||||
struct disk_io_thread;
|
||||
|
||||
class TORRENT_EXPORT piece_manager
|
||||
|
@ -180,7 +187,8 @@ namespace libtorrent
|
|||
~piece_manager();
|
||||
|
||||
bool check_fastresume(aux::piece_checker_data& d
|
||||
, std::vector<bool>& pieces, int& num_pieces, bool compact_mode);
|
||||
, std::vector<bool>& pieces, int& num_pieces, storage_mode_t storage_mode
|
||||
, std::string& error_msg);
|
||||
std::pair<bool, float> check_files(std::vector<bool>& pieces
|
||||
, int& num_pieces, boost::recursive_mutex& mutex);
|
||||
|
||||
|
@ -191,8 +199,8 @@ namespace libtorrent
|
|||
bool verify_resume_data(entry& rd, std::string& error);
|
||||
|
||||
bool is_allocating() const
|
||||
{ return m_state == state_allocating; }
|
||||
|
||||
{ return m_state == state_expand_pieces; }
|
||||
|
||||
void mark_failed(int index);
|
||||
|
||||
unsigned long piece_crc(
|
||||
|
@ -200,8 +208,9 @@ namespace libtorrent
|
|||
, int block_size
|
||||
, piece_picker::block_info const* bi);
|
||||
|
||||
int slot_for_piece(int piece_index) const;
|
||||
|
||||
int slot_for(int piece) const;
|
||||
int piece_for(int slot) const;
|
||||
|
||||
void async_read(
|
||||
peer_request const& r
|
||||
, boost::function<void(int, disk_io_job const&)> const& handler
|
||||
|
@ -221,6 +230,10 @@ namespace libtorrent
|
|||
boost::function<void(int, disk_io_job const&)> const& handler
|
||||
= boost::function<void(int, disk_io_job const&)>());
|
||||
|
||||
void async_delete_files(
|
||||
boost::function<void(int, disk_io_job const&)> const& handler
|
||||
= boost::function<void(int, disk_io_job const&)>());
|
||||
|
||||
void async_move_storage(fs::path const& p
|
||||
, boost::function<void(int, disk_io_job const&)> const& handler);
|
||||
|
||||
|
@ -228,10 +241,11 @@ namespace libtorrent
|
|||
// slots to the piece that is stored (or
|
||||
// partially stored) there. -2 is the index
|
||||
// of unassigned pieces and -1 is unallocated
|
||||
void export_piece_map(std::vector<int>& pieces) const;
|
||||
void export_piece_map(std::vector<int>& pieces
|
||||
, std::vector<bool> const& have) const;
|
||||
|
||||
bool compact_allocation() const
|
||||
{ return m_compact_mode; }
|
||||
{ return m_storage_mode == storage_mode_compact; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string name() const { return m_info->name(); }
|
||||
|
@ -261,9 +275,11 @@ namespace libtorrent
|
|||
, int offset
|
||||
, int size);
|
||||
|
||||
void switch_to_full_mode();
|
||||
sha1_hash hash_for_piece_impl(int piece);
|
||||
|
||||
void release_files_impl();
|
||||
void release_files_impl() { m_storage->release_files(); }
|
||||
void delete_files_impl() { m_storage->delete_files(); }
|
||||
|
||||
bool move_storage_impl(fs::path const& save_path);
|
||||
|
||||
|
@ -276,19 +292,7 @@ namespace libtorrent
|
|||
#endif
|
||||
boost::scoped_ptr<storage_interface> m_storage;
|
||||
|
||||
// if this is true, pieces are always allocated at the
|
||||
// lowest possible slot index. If it is false, pieces
|
||||
// are always written to their final place immediately
|
||||
bool m_compact_mode;
|
||||
|
||||
// if this is true, pieces that haven't been downloaded
|
||||
// will be filled with zeroes. Not filling with zeroes
|
||||
// will not work in some cases (where a seek cannot pass
|
||||
// the end of the file).
|
||||
bool m_fill_mode;
|
||||
|
||||
// a bitmask representing the pieces we have
|
||||
std::vector<bool> m_have_piece;
|
||||
storage_mode_t m_storage_mode;
|
||||
|
||||
boost::intrusive_ptr<torrent_info const> m_info;
|
||||
|
||||
|
@ -329,10 +333,21 @@ namespace libtorrent
|
|||
state_create_files,
|
||||
// checking the files
|
||||
state_full_check,
|
||||
// allocating files (in non-compact mode)
|
||||
state_allocating
|
||||
// move pieces to their final position
|
||||
state_expand_pieces
|
||||
} m_state;
|
||||
int m_current_slot;
|
||||
// used during check. If any piece is found
|
||||
// that is not in its final position, this
|
||||
// is set to true
|
||||
bool m_out_of_place;
|
||||
// used to move pieces while expanding
|
||||
// the storage from compact allocation
|
||||
// to full allocation
|
||||
std::vector<char> m_scratch_buffer;
|
||||
std::vector<char> m_scratch_buffer2;
|
||||
// the piece that is in the scratch buffer
|
||||
int m_scratch_piece;
|
||||
|
||||
// this is saved in case we need to instantiate a new
|
||||
// storage (osed when remapping files)
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace libtorrent
|
|||
, boost::intrusive_ptr<torrent_info> tf
|
||||
, fs::path const& save_path
|
||||
, tcp::endpoint const& net_interface
|
||||
, bool compact_mode
|
||||
, storage_mode_t m_storage_mode
|
||||
, int block_size
|
||||
, storage_constructor_type sc
|
||||
, bool paused);
|
||||
|
@ -116,7 +116,7 @@ namespace libtorrent
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, tcp::endpoint const& net_interface
|
||||
, bool compact_mode
|
||||
, storage_mode_t m_storage_mode
|
||||
, int block_size
|
||||
, storage_constructor_type sc
|
||||
, bool paused);
|
||||
|
@ -177,6 +177,8 @@ namespace libtorrent
|
|||
void resume();
|
||||
bool is_paused() const { return m_paused; }
|
||||
|
||||
void delete_files();
|
||||
|
||||
// ============ start deprecation =============
|
||||
void filter_piece(int index, bool filter);
|
||||
void filter_pieces(std::vector<bool> const& bitmask);
|
||||
|
@ -550,6 +552,7 @@ namespace libtorrent
|
|||
|
||||
private:
|
||||
|
||||
void on_files_deleted(int ret, disk_io_job const& j);
|
||||
void on_files_released(int ret, disk_io_job const& j);
|
||||
void on_torrent_paused(int ret, disk_io_job const& j);
|
||||
void on_storage_moved(int ret, disk_io_job const& j);
|
||||
|
@ -751,7 +754,7 @@ namespace libtorrent
|
|||
fs::path m_save_path;
|
||||
|
||||
// determines the storage state for this torrent.
|
||||
const bool m_compact_mode;
|
||||
storage_mode_t m_storage_mode;
|
||||
|
||||
// defaults to 16 kiB, but can be set by the user
|
||||
// when creating the torrent
|
||||
|
|
|
@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/torrent_info.hpp"
|
||||
#include "libtorrent/time.hpp"
|
||||
#include "libtorrent/config.hpp"
|
||||
#include "libtorrent/storage.hpp"
|
||||
|
||||
namespace libtorrent
|
||||
{
|
||||
|
@ -106,7 +107,7 @@ namespace libtorrent
|
|||
, num_connections(0)
|
||||
, uploads_limit(0)
|
||||
, connections_limit(0)
|
||||
, compact_mode(false)
|
||||
, storage_mode(storage_mode_sparse)
|
||||
{}
|
||||
|
||||
enum state_t
|
||||
|
@ -216,7 +217,7 @@ namespace libtorrent
|
|||
|
||||
// true if the torrent is saved in compact mode
|
||||
// false if it is saved in full allocation mode
|
||||
bool compact_mode;
|
||||
storage_mode_t storage_mode;
|
||||
};
|
||||
|
||||
struct TORRENT_EXPORT block_info
|
||||
|
|
|
@ -204,7 +204,6 @@ namespace libtorrent
|
|||
|
||||
// if this fails, we need to reconnect
|
||||
// fast.
|
||||
pi->connected = time_now() - seconds(m_ses.settings().min_reconnect_time);
|
||||
fast_reconnect(true);
|
||||
|
||||
write_pe1_2_dhkey();
|
||||
|
@ -802,9 +801,9 @@ namespace libtorrent
|
|||
{
|
||||
boost::shared_ptr<torrent> t = associated_torrent().lock();
|
||||
TORRENT_ASSERT(t);
|
||||
while (!request_queue().empty())
|
||||
while (!download_queue().empty())
|
||||
{
|
||||
piece_block const& b = request_queue().front();
|
||||
piece_block const& b = download_queue().front();
|
||||
peer_request r;
|
||||
r.piece = b.piece_index;
|
||||
r.start = b.block_index * t->block_size();
|
||||
|
|
|
@ -125,6 +125,7 @@ namespace libtorrent
|
|||
, boost::function<void(int, disk_io_job const&)> const& f)
|
||||
{
|
||||
TORRENT_ASSERT(!j.callback);
|
||||
TORRENT_ASSERT(j.storage);
|
||||
boost::mutex::scoped_lock l(m_mutex);
|
||||
|
||||
std::deque<disk_io_job>::reverse_iterator i = m_jobs.rbegin();
|
||||
|
@ -220,6 +221,7 @@ namespace libtorrent
|
|||
bool free_buffer = true;
|
||||
try
|
||||
{
|
||||
TORRENT_ASSERT(j.storage);
|
||||
#ifdef TORRENT_DISK_STATS
|
||||
ptime start = time_now();
|
||||
#endif
|
||||
|
@ -288,6 +290,12 @@ namespace libtorrent
|
|||
#endif
|
||||
j.storage->release_files_impl();
|
||||
break;
|
||||
case disk_io_job::delete_files:
|
||||
#ifdef TORRENT_DISK_STATS
|
||||
m_log << log_time() << " delete" << std::endl;
|
||||
#endif
|
||||
j.storage->delete_files_impl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
|
|
|
@ -394,6 +394,16 @@ namespace libtorrent
|
|||
#endif
|
||||
}
|
||||
|
||||
void peer_connection::fast_reconnect(bool r)
|
||||
{
|
||||
if (peer_info_struct() && peer_info_struct()->fast_reconnects > 1) return;
|
||||
m_fast_reconnect = r;
|
||||
peer_info_struct()->connected = time_now()
|
||||
- seconds(m_ses.settings().min_reconnect_time
|
||||
* m_ses.settings().max_failcount);
|
||||
if (peer_info_struct()) ++peer_info_struct()->fast_reconnects;
|
||||
}
|
||||
|
||||
void peer_connection::announce_piece(int index)
|
||||
{
|
||||
// dont announce during handshake
|
||||
|
@ -643,27 +653,23 @@ namespace libtorrent
|
|||
m_peer_choked = true;
|
||||
t->get_policy().choked(*this);
|
||||
|
||||
if (!t->is_seed())
|
||||
if (peer_info_struct() == 0 || !peer_info_struct()->on_parole)
|
||||
{
|
||||
piece_picker& p = t->picker();
|
||||
// remove all pieces from this peers download queue and
|
||||
// remove the 'downloading' flag from piece_picker.
|
||||
for (std::deque<piece_block>::iterator i = m_download_queue.begin();
|
||||
i != m_download_queue.end(); ++i)
|
||||
// if the peer is not in parole mode, clear the queued
|
||||
// up block requests
|
||||
if (!t->is_seed())
|
||||
{
|
||||
p.abort_download(*i);
|
||||
}
|
||||
for (std::deque<piece_block>::const_iterator i = m_request_queue.begin()
|
||||
, end(m_request_queue.end()); i != end; ++i)
|
||||
{
|
||||
// since this piece was skipped, clear it and allow it to
|
||||
// be requested from other peers
|
||||
p.abort_download(*i);
|
||||
piece_picker& p = t->picker();
|
||||
for (std::deque<piece_block>::const_iterator i = m_request_queue.begin()
|
||||
, end(m_request_queue.end()); i != end; ++i)
|
||||
{
|
||||
// since this piece was skipped, clear it and allow it to
|
||||
// be requested from other peers
|
||||
p.abort_download(*i);
|
||||
}
|
||||
}
|
||||
m_request_queue.clear();
|
||||
}
|
||||
|
||||
m_download_queue.clear();
|
||||
m_request_queue.clear();
|
||||
}
|
||||
|
||||
bool match_request(peer_request const& r, piece_block const& b, int block_size)
|
||||
|
@ -707,23 +713,17 @@ namespace libtorrent
|
|||
{
|
||||
b = *i;
|
||||
m_download_queue.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i = std::find_if(m_request_queue.begin(), m_request_queue.end()
|
||||
, bind(match_request, boost::cref(r), _1, t->block_size()));
|
||||
|
||||
if (i != m_request_queue.end())
|
||||
|
||||
// if the peer is in parole mode, keep the request
|
||||
if (peer_info_struct() && peer_info_struct()->on_parole)
|
||||
{
|
||||
b = *i;
|
||||
m_request_queue.erase(i);
|
||||
m_request_queue.push_front(b);
|
||||
}
|
||||
else if (!t->is_seed())
|
||||
{
|
||||
piece_picker& p = t->picker();
|
||||
p.abort_download(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (b.piece_index != -1 && !t->is_seed())
|
||||
{
|
||||
piece_picker& p = t->picker();
|
||||
p.abort_download(b);
|
||||
}
|
||||
#ifdef TORRENT_VERBOSE_LOGGING
|
||||
else
|
||||
|
@ -1932,7 +1932,6 @@ namespace libtorrent
|
|||
|
||||
void peer_connection::timed_out()
|
||||
{
|
||||
if (m_peer_info) ++m_peer_info->failcount;
|
||||
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
|
||||
(*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string()
|
||||
<< "\n";
|
||||
|
|
|
@ -1732,7 +1732,6 @@ namespace libtorrent
|
|||
++i->writing;
|
||||
info.state = block_info::state_writing;
|
||||
if (info.num_peers > 0) --info.num_peers;
|
||||
TORRENT_ASSERT(info.num_peers >= 0);
|
||||
|
||||
if (i->requested == 0)
|
||||
{
|
||||
|
@ -1855,7 +1854,6 @@ namespace libtorrent
|
|||
|
||||
block_info& info = i->info[block.block_index];
|
||||
--info.num_peers;
|
||||
TORRENT_ASSERT(info.num_peers >= 0);
|
||||
if (info.num_peers > 0) return;
|
||||
|
||||
if (i->info[block.block_index].state == block_info::state_finished
|
||||
|
|
|
@ -986,7 +986,8 @@ namespace libtorrent
|
|||
i->second.prev_amount_upload = 0;
|
||||
i->second.connection = &c;
|
||||
TORRENT_ASSERT(i->second.connection);
|
||||
i->second.connected = time_now();
|
||||
if (!c.fast_reconnect())
|
||||
i->second.connected = time_now();
|
||||
// m_last_optimistic_disconnect = time_now();
|
||||
}
|
||||
|
||||
|
@ -1045,10 +1046,10 @@ namespace libtorrent
|
|||
|
||||
// we don't have any info about this peer.
|
||||
// add a new entry
|
||||
peer p(remote, peer::connectable, src);
|
||||
i = m_peers.insert(std::make_pair(remote.address(), p));
|
||||
i = m_peers.insert(std::make_pair(remote.address()
|
||||
, peer(remote, peer::connectable, src)));
|
||||
#ifndef TORRENT_DISABLE_ENCRYPTION
|
||||
if (flags & 0x01) p.pe_support = true;
|
||||
if (flags & 0x01) i->second.pe_support = true;
|
||||
#endif
|
||||
if (flags & 0x02) i->second.seed = true;
|
||||
|
||||
|
@ -1503,6 +1504,7 @@ namespace libtorrent
|
|||
, failcount(0)
|
||||
, hashfails(0)
|
||||
, seed(false)
|
||||
, fast_reconnects(0)
|
||||
, optimistically_unchoked(false)
|
||||
, last_optimistically_unchoked(min_time())
|
||||
, connected(min_time())
|
||||
|
|
|
@ -186,28 +186,28 @@ namespace libtorrent
|
|||
torrent_info const& ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, bool paused
|
||||
, storage_constructor_type sc)
|
||||
{
|
||||
TORRENT_ASSERT(!ti.m_half_metadata);
|
||||
boost::intrusive_ptr<torrent_info> tip(new torrent_info(ti));
|
||||
return m_impl->add_torrent(tip, save_path, resume_data
|
||||
, compact_mode, sc, paused, 0);
|
||||
, storage_mode, sc, paused, 0);
|
||||
}
|
||||
|
||||
torrent_handle session::add_torrent(
|
||||
boost::intrusive_ptr<torrent_info> ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, bool paused
|
||||
, storage_constructor_type sc
|
||||
, void* userdata)
|
||||
{
|
||||
TORRENT_ASSERT(!ti->m_half_metadata);
|
||||
return m_impl->add_torrent(ti, save_path, resume_data
|
||||
, compact_mode, sc, paused, userdata);
|
||||
, storage_mode, sc, paused, userdata);
|
||||
}
|
||||
|
||||
torrent_handle session::add_torrent(
|
||||
|
@ -216,18 +216,18 @@ namespace libtorrent
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, entry const& e
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, bool paused
|
||||
, storage_constructor_type sc
|
||||
, void* userdata)
|
||||
{
|
||||
return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e
|
||||
, compact_mode, sc, paused, userdata);
|
||||
, storage_mode, sc, paused, userdata);
|
||||
}
|
||||
|
||||
void session::remove_torrent(const torrent_handle& h)
|
||||
void session::remove_torrent(const torrent_handle& h, int options)
|
||||
{
|
||||
m_impl->remove_torrent(h);
|
||||
m_impl->remove_torrent(h, options);
|
||||
}
|
||||
|
||||
bool session::listen_on(
|
||||
|
|
|
@ -189,9 +189,11 @@ namespace detail
|
|||
t->parse_resume_data(t->resume_data, t->torrent_ptr->torrent_file()
|
||||
, error_msg);
|
||||
|
||||
// lock the session to add the new torrent
|
||||
session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
|
||||
|
||||
if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning))
|
||||
{
|
||||
session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
|
||||
m_ses.m_alerts.post_alert(fastresume_rejected_alert(
|
||||
t->torrent_ptr->get_handle()
|
||||
, error_msg));
|
||||
|
@ -202,8 +204,6 @@ namespace detail
|
|||
#endif
|
||||
}
|
||||
|
||||
// lock the session to add the new torrent
|
||||
session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
|
||||
mutex::scoped_lock l2(m_mutex);
|
||||
|
||||
if (m_torrents.empty() || m_torrents.front() != t)
|
||||
|
@ -328,7 +328,7 @@ namespace detail
|
|||
boost::tie(finished, progress) = processing->torrent_ptr->check_files();
|
||||
|
||||
{
|
||||
mutex::scoped_lock l(m_mutex);
|
||||
mutex::scoped_lock l2(m_mutex);
|
||||
|
||||
INVARIANT_CHECK;
|
||||
|
||||
|
@ -340,9 +340,9 @@ namespace detail
|
|||
m_processing.pop_front();
|
||||
|
||||
// make sure the lock order is correct
|
||||
l.unlock();
|
||||
l2.unlock();
|
||||
session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
|
||||
l.lock();
|
||||
l2.lock();
|
||||
processing->torrent_ptr->abort();
|
||||
|
||||
processing.reset();
|
||||
|
@ -481,7 +481,7 @@ namespace detail
|
|||
return 0;
|
||||
}
|
||||
|
||||
void checker_impl::remove_torrent(sha1_hash const& info_hash)
|
||||
void checker_impl::remove_torrent(sha1_hash const& info_hash, int options)
|
||||
{
|
||||
INVARIANT_CHECK;
|
||||
for (std::deque<boost::shared_ptr<piece_checker_data> >::iterator i
|
||||
|
@ -490,6 +490,8 @@ namespace detail
|
|||
if ((*i)->info_hash == info_hash)
|
||||
{
|
||||
TORRENT_ASSERT((*i)->processing == false);
|
||||
if (options & session::delete_files)
|
||||
(*i)->torrent_ptr->delete_files();
|
||||
m_torrents.erase(i);
|
||||
return;
|
||||
}
|
||||
|
@ -500,6 +502,8 @@ namespace detail
|
|||
if ((*i)->info_hash == info_hash)
|
||||
{
|
||||
TORRENT_ASSERT((*i)->processing == false);
|
||||
if (options & session::delete_files)
|
||||
(*i)->torrent_ptr->delete_files();
|
||||
m_processing.erase(i);
|
||||
return;
|
||||
}
|
||||
|
@ -565,8 +569,19 @@ namespace detail
|
|||
, m_checker_impl(*this)
|
||||
{
|
||||
#ifdef WIN32
|
||||
// windows XP has a limit of 10 simultaneous connections
|
||||
m_half_open.limit(8);
|
||||
// windows XP has a limit on the number of
|
||||
// simultaneous half-open TCP connections
|
||||
DWORD windows_version = ::GetVersion();
|
||||
if ((windows_version & 0xff) >= 6)
|
||||
{
|
||||
// on vista the limit is 5 (in home edition)
|
||||
m_half_open.limit(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// on XP SP2 it's 10
|
||||
m_half_open.limit(8);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel;
|
||||
|
@ -1623,7 +1638,7 @@ namespace detail
|
|||
boost::intrusive_ptr<torrent_info> ti
|
||||
, fs::path const& save_path
|
||||
, entry const& resume_data
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, storage_constructor_type sc
|
||||
, bool paused
|
||||
, void* userdata)
|
||||
|
@ -1655,7 +1670,7 @@ namespace detail
|
|||
// the thread
|
||||
boost::shared_ptr<torrent> torrent_ptr(
|
||||
new torrent(*this, m_checker_impl, ti, save_path
|
||||
, m_listen_interface, compact_mode, 16 * 1024
|
||||
, m_listen_interface, storage_mode, 16 * 1024
|
||||
, sc, paused));
|
||||
torrent_ptr->start();
|
||||
|
||||
|
@ -1701,7 +1716,7 @@ namespace detail
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, entry const&
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, storage_constructor_type sc
|
||||
, bool paused
|
||||
, void* userdata)
|
||||
|
@ -1735,7 +1750,7 @@ namespace detail
|
|||
// the thread
|
||||
boost::shared_ptr<torrent> torrent_ptr(
|
||||
new torrent(*this, m_checker_impl, tracker_url, info_hash, name
|
||||
, save_path, m_listen_interface, compact_mode, 16 * 1024
|
||||
, save_path, m_listen_interface, storage_mode, 16 * 1024
|
||||
, sc, paused));
|
||||
torrent_ptr->start();
|
||||
|
||||
|
@ -1754,7 +1769,7 @@ namespace detail
|
|||
return torrent_handle(this, &m_checker_impl, info_hash);
|
||||
}
|
||||
|
||||
void session_impl::remove_torrent(const torrent_handle& h)
|
||||
void session_impl::remove_torrent(const torrent_handle& h, int options)
|
||||
{
|
||||
if (h.m_ses != this) return;
|
||||
TORRENT_ASSERT(h.m_chk == &m_checker_impl || h.m_chk == 0);
|
||||
|
@ -1769,6 +1784,8 @@ namespace detail
|
|||
if (i != m_torrents.end())
|
||||
{
|
||||
torrent& t = *i->second;
|
||||
if (options & session::delete_files)
|
||||
t.delete_files();
|
||||
t.abort();
|
||||
|
||||
if ((!t.is_paused() || t.should_request())
|
||||
|
@ -1815,7 +1832,7 @@ namespace detail
|
|||
if (d != 0)
|
||||
{
|
||||
if (d->processing) d->abort = true;
|
||||
else m_checker_impl.remove_torrent(h.m_info_hash);
|
||||
else m_checker_impl.remove_torrent(h.m_info_hash, options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -154,7 +154,7 @@ namespace libtorrent
|
|||
, boost::intrusive_ptr<torrent_info> tf
|
||||
, fs::path const& save_path
|
||||
, tcp::endpoint const& net_interface
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, int block_size
|
||||
, storage_constructor_type sc
|
||||
, bool paused)
|
||||
|
@ -195,7 +195,7 @@ namespace libtorrent
|
|||
, m_total_redundant_bytes(0)
|
||||
, m_net_interface(net_interface.address(), 0)
|
||||
, m_save_path(complete(save_path))
|
||||
, m_compact_mode(compact_mode)
|
||||
, m_storage_mode(storage_mode)
|
||||
, m_default_block_size(block_size)
|
||||
, m_connections_initialized(true)
|
||||
, m_settings(ses.settings())
|
||||
|
@ -215,7 +215,7 @@ namespace libtorrent
|
|||
, char const* name
|
||||
, fs::path const& save_path
|
||||
, tcp::endpoint const& net_interface
|
||||
, bool compact_mode
|
||||
, storage_mode_t storage_mode
|
||||
, int block_size
|
||||
, storage_constructor_type sc
|
||||
, bool paused)
|
||||
|
@ -255,7 +255,7 @@ namespace libtorrent
|
|||
, m_total_redundant_bytes(0)
|
||||
, m_net_interface(net_interface.address(), 0)
|
||||
, m_save_path(complete(save_path))
|
||||
, m_compact_mode(compact_mode)
|
||||
, m_storage_mode(storage_mode)
|
||||
, m_default_block_size(block_size)
|
||||
, m_connections_initialized(false)
|
||||
, m_settings(ses.settings())
|
||||
|
@ -1032,6 +1032,16 @@ namespace libtorrent
|
|||
m_announce_timer.cancel();
|
||||
}
|
||||
|
||||
void torrent::on_files_deleted(int ret, disk_io_job const& j)
|
||||
{
|
||||
session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
|
||||
|
||||
if (alerts().should_post(alert::warning))
|
||||
{
|
||||
alerts().post_alert(torrent_deleted_alert(get_handle(), "files deleted"));
|
||||
}
|
||||
}
|
||||
|
||||
void torrent::on_files_released(int ret, disk_io_job const& j)
|
||||
{
|
||||
/*
|
||||
|
@ -1668,8 +1678,6 @@ namespace libtorrent
|
|||
|
||||
try
|
||||
{
|
||||
TORRENT_ASSERT(m_connections.find(a) == m_connections.end());
|
||||
|
||||
// add the newly connected peer to this torrent's peer list
|
||||
TORRENT_ASSERT(m_connections.find(a) == m_connections.end());
|
||||
m_connections.insert(
|
||||
|
@ -1883,10 +1891,13 @@ namespace libtorrent
|
|||
std::make_pair(a, boost::get_pointer(c)));
|
||||
m_ses.m_connections.insert(std::make_pair(s, c));
|
||||
|
||||
int timeout = settings().peer_connect_timeout;
|
||||
if (peerinfo) timeout += 3 * peerinfo->failcount;
|
||||
|
||||
m_ses.m_half_open.enqueue(
|
||||
bind(&peer_connection::connect, c, _1)
|
||||
, bind(&peer_connection::timed_out, c)
|
||||
, seconds(settings().peer_connect_timeout));
|
||||
, seconds(timeout));
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -2215,10 +2226,22 @@ namespace libtorrent
|
|||
bool done = true;
|
||||
try
|
||||
{
|
||||
std::string error_msg;
|
||||
TORRENT_ASSERT(m_storage);
|
||||
TORRENT_ASSERT(m_owning_storage.get());
|
||||
done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces
|
||||
, m_compact_mode);
|
||||
, m_storage_mode, error_msg);
|
||||
|
||||
if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning))
|
||||
{
|
||||
m_ses.m_alerts.post_alert(fastresume_rejected_alert(
|
||||
get_handle(), error_msg));
|
||||
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
|
||||
(*m_ses.m_logger) << "fastresume data for "
|
||||
<< torrent_file().name() << " rejected: "
|
||||
<< error_msg << "\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -2378,8 +2401,6 @@ namespace libtorrent
|
|||
|
||||
piece_manager& torrent::filesystem()
|
||||
{
|
||||
INVARIANT_CHECK;
|
||||
|
||||
TORRENT_ASSERT(m_owning_storage.get());
|
||||
return *m_owning_storage;
|
||||
}
|
||||
|
@ -2537,6 +2558,29 @@ namespace libtorrent
|
|||
return limit;
|
||||
}
|
||||
|
||||
void torrent::delete_files()
|
||||
{
|
||||
#if defined(TORRENT_VERBOSE_LOGGING)
|
||||
for (peer_iterator i = m_connections.begin();
|
||||
i != m_connections.end(); ++i)
|
||||
{
|
||||
(*i->second->m_logger) << "*** DELETING FILES IN TORRENT\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
disconnect_all();
|
||||
m_paused = true;
|
||||
// tell the tracker that we stopped
|
||||
m_event = tracker_request::stopped;
|
||||
|
||||
if (m_owning_storage.get())
|
||||
{
|
||||
TORRENT_ASSERT(m_storage);
|
||||
m_storage->async_delete_files(
|
||||
bind(&torrent::on_files_deleted, shared_from_this(), _1, _2));
|
||||
}
|
||||
}
|
||||
|
||||
void torrent::pause()
|
||||
{
|
||||
INVARIANT_CHECK;
|
||||
|
@ -2768,7 +2812,7 @@ namespace libtorrent
|
|||
!boost::bind(&peer_connection::is_connecting
|
||||
, boost::bind(&std::map<tcp::endpoint,peer_connection*>::value_type::second, _1)));
|
||||
|
||||
st.compact_mode = m_compact_mode;
|
||||
st.storage_mode = m_storage_mode;
|
||||
|
||||
st.num_complete = m_complete;
|
||||
st.num_incomplete = m_incomplete;
|
||||
|
|
|
@ -661,7 +661,7 @@ namespace libtorrent
|
|||
|
||||
if (!t->valid_metadata()) return entry();
|
||||
|
||||
t->filesystem().export_piece_map(piece_index);
|
||||
std::vector<bool> have_pieces = t->pieces();
|
||||
|
||||
entry ret(entry::dictionary_t);
|
||||
|
||||
|
@ -673,10 +673,6 @@ namespace libtorrent
|
|||
const sha1_hash& info_hash = t->torrent_file().info_hash();
|
||||
ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end());
|
||||
|
||||
ret["slots"] = entry(entry::list_t);
|
||||
entry::list_type& slots = ret["slots"].list();
|
||||
std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots));
|
||||
|
||||
// blocks per piece
|
||||
int num_blocks_per_piece =
|
||||
static_cast<int>(t->torrent_file().piece_length()) / t->block_size();
|
||||
|
@ -706,6 +702,8 @@ namespace libtorrent
|
|||
// the unfinished piece's index
|
||||
piece_struct["piece"] = i->index;
|
||||
|
||||
have_pieces[i->index] = true;
|
||||
|
||||
std::string bitmask;
|
||||
const int num_bitmask_bytes
|
||||
= (std::max)(num_blocks_per_piece / 8, 1);
|
||||
|
@ -722,10 +720,10 @@ namespace libtorrent
|
|||
}
|
||||
piece_struct["bitmask"] = bitmask;
|
||||
|
||||
TORRENT_ASSERT(t->filesystem().slot_for_piece(i->index) >= 0);
|
||||
TORRENT_ASSERT(t->filesystem().slot_for(i->index) >= 0);
|
||||
unsigned long adler
|
||||
= t->filesystem().piece_crc(
|
||||
t->filesystem().slot_for_piece(i->index)
|
||||
t->filesystem().slot_for(i->index)
|
||||
, t->block_size()
|
||||
, i->info);
|
||||
|
||||
|
@ -735,6 +733,11 @@ namespace libtorrent
|
|||
up.push_back(piece_struct);
|
||||
}
|
||||
}
|
||||
|
||||
t->filesystem().export_piece_map(piece_index, have_pieces);
|
||||
entry::list_type& slots = ret["slots"].list();
|
||||
std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots));
|
||||
|
||||
// write local peers
|
||||
|
||||
entry::list_type& peer_list = ret["peers"].list();
|
||||
|
|
Loading…
Reference in New Issue