mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-03 06:58:42 +00:00
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
45 changed files with 4364 additions and 1299 deletions
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
|
||||
|
|
595
deluge/SimpleXMLRPCServer.py
Normal file
595
deluge/SimpleXMLRPCServer.py
Normal file
|
@ -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 = {
|
||||
|
@ -74,8 +77,29 @@ DEFAULT_PREFS = {
|
|||
"enabled_plugins": ["Queue"]
|
||||
}
|
||||
|
||||
class Core(dbus.service.Object):
|
||||
def __init__(self, path="/org/deluge_torrent/Core"):
|
||||
class Core(
|
||||
threading.Thread,
|
||||
ThreadingMixIn,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
def __init__(self):
|
||||
log.debug("Core init..")
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
# 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)
|
||||
self._on_alert_torrent_paused)
|
||||
|
||||
t = threading.Thread(target=self.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
gobject.threads_init()
|
||||
|
||||
log.debug("Starting main loop..")
|
||||
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]
|
||||
|
@ -321,104 +336,82 @@ class Core(dbus.service.Object):
|
|||
|
||||
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..")
|
||||
# Start the core as a thread and join it until it's done
|
||||
self.core = Core()
|
||||
self.core.start()
|
||||
self.core.join()
|
||||
|
||||
|
|
58
deluge/core/signalmanager.py
Normal file
58
deluge/core/signalmanager.py
Normal file
|
@ -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"""
|
||||
|
|
|
@ -172,12 +172,18 @@ class TorrentManager:
|
|||
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,69 +132,61 @@ 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 set_config(config, core=None):
|
||||
def get_config_value(key):
|
||||
return get_core().get_config_value(key)
|
||||
|
||||
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"""
|
|
@ -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(\
|
||||
|
|
199
deluge/ui/gtkui/connectionmanager.py
Normal file
199
deluge/ui/gtkui/connectionmanager.py
Normal file
|
@ -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()
|
375
deluge/ui/gtkui/glade/connection_manager.glade
Normal file
375
deluge/ui/gtkui/glade/connection_manager.glade
Normal file
|
@ -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
|
||||
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
|
||||
|
||||
|
@ -42,8 +42,13 @@ 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
|
||||
|
|
|
@ -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,21 +31,23 @@
|
|||
# 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.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.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.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):
|
||||
|
|
|
@ -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,9 +163,11 @@ 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)
|
||||
|
||||
|
@ -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
|
||||
|
|
111
deluge/ui/signalreceiver.py
Normal file
111
deluge/ui/signalreceiver.py
Normal file
|
@ -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]
|
||||
|
||||
|
1488
deluge/xmlrpclib.py
Normal file
1488
deluge/xmlrpclib.py
Normal file
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
|
||||
|
|
|
@ -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,7 +199,7 @@ 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);
|
||||
|
||||
|
@ -200,7 +208,8 @@ 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
|
||||
|
@ -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,16 +653,13 @@ namespace libtorrent
|
|||
m_peer_choked = true;
|
||||
t->get_policy().choked(*this);
|
||||
|
||||
if (peer_info_struct() == 0 || !peer_info_struct()->on_parole)
|
||||
{
|
||||
// if the peer is not in parole mode, clear the queued
|
||||
// up block requests
|
||||
if (!t->is_seed())
|
||||
{
|
||||
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)
|
||||
{
|
||||
p.abort_download(*i);
|
||||
}
|
||||
for (std::deque<piece_block>::const_iterator i = m_request_queue.begin()
|
||||
, end(m_request_queue.end()); i != end; ++i)
|
||||
{
|
||||
|
@ -661,10 +668,9 @@ namespace libtorrent
|
|||
p.abort_download(*i);
|
||||
}
|
||||
}
|
||||
|
||||
m_download_queue.clear();
|
||||
m_request_queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool match_request(peer_request const& r, piece_block const& b, int block_size)
|
||||
{
|
||||
|
@ -707,24 +713,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (b.piece_index != -1 && !t->is_seed())
|
||||
else if (!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,6 +986,7 @@ namespace libtorrent
|
|||
i->second.prev_amount_upload = 0;
|
||||
i->second.connection = &c;
|
||||
TORRENT_ASSERT(i->second.connection);
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -361,6 +361,7 @@ namespace libtorrent
|
|||
}
|
||||
|
||||
void release_files();
|
||||
void delete_files();
|
||||
void initialize(bool allocate_files);
|
||||
bool move_storage(fs::path save_path);
|
||||
size_type read(char* buf, int slot, int offset, int size);
|
||||
|
@ -447,7 +448,7 @@ namespace libtorrent
|
|||
}
|
||||
|
||||
// if the file is empty, just create it. But also make sure
|
||||
// the directory exits.
|
||||
// the directory exists.
|
||||
if (file_iter->size == 0)
|
||||
{
|
||||
file(m_save_path / file_iter->path, file::out);
|
||||
|
@ -470,6 +471,38 @@ namespace libtorrent
|
|||
std::vector<char>().swap(m_scratch_buffer);
|
||||
}
|
||||
|
||||
void storage::delete_files()
|
||||
{
|
||||
// make sure we don't have the files open
|
||||
m_files.release(this);
|
||||
std::vector<char>().swap(m_scratch_buffer);
|
||||
|
||||
// delete the files from disk
|
||||
std::set<std::string> directories;
|
||||
typedef std::set<std::string>::iterator iter_t;
|
||||
for (torrent_info::file_iterator i = m_info->begin_files(true)
|
||||
, end(m_info->end_files(true)); i != end; ++i)
|
||||
{
|
||||
std::string p = (m_save_path / i->path).string();
|
||||
fs::path bp = i->path.branch_path();
|
||||
std::pair<iter_t, bool> ret = directories.insert(bp.string());
|
||||
while (ret.second && !bp.empty())
|
||||
{
|
||||
bp = bp.branch_path();
|
||||
std::pair<iter_t, bool> ret = directories.insert(bp.string());
|
||||
}
|
||||
std::remove(p.c_str());
|
||||
}
|
||||
|
||||
// remove the directories. Reverse order to delete
|
||||
// subdirectories first
|
||||
std::for_each(directories.rbegin(), directories.rend()
|
||||
, bind((int(*)(char const*))&std::remove, bind(&std::string::c_str, _1)));
|
||||
|
||||
std::string p = (m_save_path / m_info->name()).string();
|
||||
std::remove(p.c_str());
|
||||
}
|
||||
|
||||
void storage::write_resume_data(entry& rd) const
|
||||
{
|
||||
std::vector<std::pair<size_type, std::time_t> > file_sizes
|
||||
|
@ -931,107 +964,6 @@ namespace libtorrent
|
|||
return new storage(ti, path, fp);
|
||||
}
|
||||
|
||||
bool supports_sparse_files(fs::path const& p)
|
||||
{
|
||||
TORRENT_ASSERT(p.is_complete());
|
||||
#if defined(_WIN32)
|
||||
// assume windows API is available
|
||||
DWORD max_component_len = 0;
|
||||
DWORD volume_flags = 0;
|
||||
std::string root_device = p.root_name() + "\\";
|
||||
#if defined(UNICODE)
|
||||
std::wstring wph(safe_convert(root_device));
|
||||
bool ret = ::GetVolumeInformation(wph.c_str(), 0
|
||||
, 0, 0, &max_component_len, &volume_flags, 0, 0);
|
||||
#else
|
||||
bool ret = ::GetVolumeInformation(root_device.c_str(), 0
|
||||
, 0, 0, &max_component_len, &volume_flags, 0, 0);
|
||||
#endif
|
||||
|
||||
if (!ret) return false;
|
||||
if (volume_flags & FILE_SUPPORTS_SPARSE_FILES)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__)
|
||||
// find the last existing directory of the save path
|
||||
fs::path query_path = p;
|
||||
while (!query_path.empty() && !exists(query_path))
|
||||
query_path = query_path.branch_path();
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
struct statfs fsinfo;
|
||||
int ret = statfs(query_path.native_directory_string().c_str(), &fsinfo);
|
||||
if (ret != 0) return false;
|
||||
|
||||
attrlist request;
|
||||
request.bitmapcount = ATTR_BIT_MAP_COUNT;
|
||||
request.reserved = 0;
|
||||
request.commonattr = 0;
|
||||
request.volattr = ATTR_VOL_CAPABILITIES;
|
||||
request.dirattr = 0;
|
||||
request.fileattr = 0;
|
||||
request.forkattr = 0;
|
||||
|
||||
struct vol_capabilities_attr_buf
|
||||
{
|
||||
unsigned long length;
|
||||
vol_capabilities_attr_t info;
|
||||
} vol_cap;
|
||||
|
||||
ret = getattrlist(fsinfo.f_mntonname, &request, &vol_cap
|
||||
, sizeof(vol_cap), 0);
|
||||
if (ret != 0) return false;
|
||||
|
||||
if (vol_cap.info.capabilities[VOL_CAPABILITIES_FORMAT]
|
||||
& (VOL_CAP_FMT_SPARSE_FILES | VOL_CAP_FMT_ZERO_RUNS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// workaround for bugs in Mac OS X where zero run is not reported
|
||||
if (!strcmp(fsinfo.f_fstypename, "hfs")
|
||||
|| !strcmp(fsinfo.f_fstypename, "ufs"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
struct statfs buf;
|
||||
int err = statfs(query_path.native_directory_string().c_str(), &buf);
|
||||
if (err == 0)
|
||||
{
|
||||
switch (buf.f_type)
|
||||
{
|
||||
case 0x5346544e: // NTFS
|
||||
case 0xEF51: // EXT2 OLD
|
||||
case 0xEF53: // EXT2 and EXT3
|
||||
case 0x00011954: // UFS
|
||||
case 0x52654973: // ReiserFS
|
||||
case 0x52345362: // Reiser4
|
||||
case 0x58465342: // XFS
|
||||
case 0x65735546: // NTFS-3G
|
||||
case 0x19540119: // UFS2
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
else
|
||||
{
|
||||
std::cerr << "statfs returned " << err << std::endl;
|
||||
std::cerr << "errno: " << errno << std::endl;
|
||||
std::cerr << "path: " << query_path.native_directory_string() << std::endl;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// TODO: POSIX implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- piece_manager -----------------------------------------------------
|
||||
|
||||
piece_manager::piece_manager(
|
||||
|
@ -1042,15 +974,16 @@ namespace libtorrent
|
|||
, disk_io_thread& io
|
||||
, storage_constructor_type sc)
|
||||
: m_storage(sc(ti, save_path, fp))
|
||||
, m_compact_mode(false)
|
||||
, m_fill_mode(true)
|
||||
, m_storage_mode(storage_mode_sparse)
|
||||
, m_info(ti)
|
||||
, m_save_path(complete(save_path))
|
||||
, m_current_slot(0)
|
||||
, m_out_of_place(false)
|
||||
, m_scratch_piece(-1)
|
||||
, m_storage_constructor(sc)
|
||||
, m_io_thread(io)
|
||||
, m_torrent(torrent)
|
||||
{
|
||||
m_fill_mode = !supports_sparse_files(save_path);
|
||||
}
|
||||
|
||||
piece_manager::~piece_manager()
|
||||
|
@ -1081,6 +1014,15 @@ namespace libtorrent
|
|||
m_io_thread.add_job(j, handler);
|
||||
}
|
||||
|
||||
void piece_manager::async_delete_files(
|
||||
boost::function<void(int, disk_io_job const&)> const& handler)
|
||||
{
|
||||
disk_io_job j;
|
||||
j.storage = this;
|
||||
j.action = disk_io_job::delete_files;
|
||||
m_io_thread.add_job(j, handler);
|
||||
}
|
||||
|
||||
void piece_manager::async_move_storage(fs::path const& p
|
||||
, boost::function<void(int, disk_io_job const&)> const& handler)
|
||||
{
|
||||
|
@ -1158,16 +1100,11 @@ namespace libtorrent
|
|||
m_piece_hasher.erase(i);
|
||||
}
|
||||
|
||||
int slot = m_piece_to_slot[piece];
|
||||
int slot = slot_for(piece);
|
||||
TORRENT_ASSERT(slot != has_no_slot);
|
||||
return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece));
|
||||
}
|
||||
|
||||
void piece_manager::release_files_impl()
|
||||
{
|
||||
m_storage->release_files();
|
||||
}
|
||||
|
||||
bool piece_manager::move_storage_impl(fs::path const& save_path)
|
||||
{
|
||||
if (m_storage->move_storage(save_path))
|
||||
|
@ -1177,26 +1114,39 @@ namespace libtorrent
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void piece_manager::export_piece_map(
|
||||
std::vector<int>& p) const
|
||||
std::vector<int>& p, std::vector<bool> const& have) const
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(m_mutex);
|
||||
|
||||
INVARIANT_CHECK;
|
||||
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
{
|
||||
p.clear();
|
||||
p.reserve(m_info->num_pieces());
|
||||
std::vector<int>::const_reverse_iterator last;
|
||||
for (last = m_slot_to_piece.rbegin();
|
||||
last != m_slot_to_piece.rend(); ++last)
|
||||
{
|
||||
if (*last != unallocated) break;
|
||||
if (*last != unallocated && have[*last]) break;
|
||||
}
|
||||
|
||||
for (std::vector<int>::const_iterator i =
|
||||
m_slot_to_piece.begin();
|
||||
i != last.base(); ++i)
|
||||
{
|
||||
p.push_back(*i);
|
||||
p.push_back(have[*i] ? *i : unassigned);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p.reserve(m_info->num_pieces());
|
||||
for (int i = 0; i < m_info->num_pieces(); ++i)
|
||||
{
|
||||
p.push_back(have[i] ? i : unassigned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1206,11 +1156,10 @@ namespace libtorrent
|
|||
|
||||
INVARIANT_CHECK;
|
||||
|
||||
if (m_storage_mode != storage_mode_compact) return;
|
||||
|
||||
TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size());
|
||||
TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0);
|
||||
|
||||
int slot_index = m_piece_to_slot[piece_index];
|
||||
|
||||
TORRENT_ASSERT(slot_index >= 0);
|
||||
|
||||
m_slot_to_piece[slot_index] = unassigned;
|
||||
|
@ -1218,12 +1167,6 @@ namespace libtorrent
|
|||
m_free_slots.push_back(slot_index);
|
||||
}
|
||||
|
||||
int piece_manager::slot_for_piece(int piece_index) const
|
||||
{
|
||||
TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces());
|
||||
return m_piece_to_slot[piece_index];
|
||||
}
|
||||
|
||||
unsigned long piece_manager::piece_crc(
|
||||
int slot_index
|
||||
, int block_size
|
||||
|
@ -1275,11 +1218,7 @@ namespace libtorrent
|
|||
TORRENT_ASSERT(buf);
|
||||
TORRENT_ASSERT(offset >= 0);
|
||||
TORRENT_ASSERT(size > 0);
|
||||
TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size());
|
||||
TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0
|
||||
&& m_piece_to_slot[piece_index] < (int)m_slot_to_piece.size());
|
||||
int slot = m_piece_to_slot[piece_index];
|
||||
TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size());
|
||||
int slot = slot_for(piece_index);
|
||||
return m_storage->read(buf, slot, offset, size);
|
||||
}
|
||||
|
||||
|
@ -1292,7 +1231,7 @@ namespace libtorrent
|
|||
TORRENT_ASSERT(buf);
|
||||
TORRENT_ASSERT(offset >= 0);
|
||||
TORRENT_ASSERT(size > 0);
|
||||
TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size());
|
||||
TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces());
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
|
@ -1317,7 +1256,6 @@ namespace libtorrent
|
|||
}
|
||||
|
||||
int slot = allocate_slot_for_piece(piece_index);
|
||||
TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size());
|
||||
m_storage->write(buf, slot, offset, size);
|
||||
}
|
||||
|
||||
|
@ -1426,6 +1364,7 @@ namespace libtorrent
|
|||
// that piece as unassigned, since this slot
|
||||
// is the correct place for the piece.
|
||||
m_slot_to_piece[other_slot] = unassigned;
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
m_free_slots.push_back(other_slot);
|
||||
}
|
||||
TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot);
|
||||
|
@ -1485,7 +1424,8 @@ namespace libtorrent
|
|||
bool piece_manager::check_fastresume(
|
||||
aux::piece_checker_data& data
|
||||
, std::vector<bool>& pieces
|
||||
, int& num_pieces, bool compact_mode)
|
||||
, int& num_pieces, storage_mode_t storage_mode
|
||||
, std::string& error_msg)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(m_mutex);
|
||||
|
||||
|
@ -1493,7 +1433,7 @@ namespace libtorrent
|
|||
|
||||
TORRENT_ASSERT(m_info->piece_length() > 0);
|
||||
|
||||
m_compact_mode = compact_mode;
|
||||
m_storage_mode = storage_mode;
|
||||
|
||||
// This will corrupt the storage
|
||||
// use while debugging to find
|
||||
|
@ -1503,8 +1443,12 @@ namespace libtorrent
|
|||
|
||||
m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot);
|
||||
m_slot_to_piece.resize(m_info->num_pieces(), unallocated);
|
||||
m_free_slots.clear();
|
||||
m_unallocated_slots.clear();
|
||||
TORRENT_ASSERT(m_free_slots.empty());
|
||||
TORRENT_ASSERT(m_unallocated_slots.empty());
|
||||
|
||||
// assume no piece is out of place (i.e. in a slot
|
||||
// other than the one it should be in)
|
||||
bool out_of_place = false;
|
||||
|
||||
pieces.clear();
|
||||
pieces.resize(m_info->num_pieces(), false);
|
||||
|
@ -1513,13 +1457,14 @@ namespace libtorrent
|
|||
// if we have fast-resume info
|
||||
// use it instead of doing the actual checking
|
||||
if (!data.piece_map.empty()
|
||||
&& data.piece_map.size() <= m_slot_to_piece.size())
|
||||
&& int(data.piece_map.size()) <= m_info->num_pieces())
|
||||
{
|
||||
for (int i = 0; i < (int)data.piece_map.size(); ++i)
|
||||
{
|
||||
m_slot_to_piece[i] = data.piece_map[i];
|
||||
if (data.piece_map[i] >= 0)
|
||||
{
|
||||
if (data.piece_map[i] != i) out_of_place = true;
|
||||
m_piece_to_slot[data.piece_map[i]] = i;
|
||||
int found_piece = data.piece_map[i];
|
||||
|
||||
|
@ -1537,27 +1482,54 @@ namespace libtorrent
|
|||
}
|
||||
else if (data.piece_map[i] == unassigned)
|
||||
{
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
m_free_slots.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
TORRENT_ASSERT(data.piece_map[i] == unallocated);
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
m_unallocated_slots.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
m_unallocated_slots.reserve(int(pieces.size() - data.piece_map.size()));
|
||||
for (int i = (int)data.piece_map.size(); i < (int)pieces.size(); ++i)
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
{
|
||||
m_unallocated_slots.reserve(int(m_info->num_pieces() - data.piece_map.size()));
|
||||
for (int i = (int)data.piece_map.size(); i < (int)m_info->num_pieces(); ++i)
|
||||
{
|
||||
m_unallocated_slots.push_back(i);
|
||||
}
|
||||
|
||||
if (m_unallocated_slots.empty())
|
||||
m_state = state_create_files;
|
||||
else if (m_compact_mode)
|
||||
m_state = state_create_files;
|
||||
{
|
||||
switch_to_full_mode();
|
||||
}
|
||||
}
|
||||
else
|
||||
m_state = state_allocating;
|
||||
{
|
||||
if (!out_of_place)
|
||||
{
|
||||
// if no piece is out of place
|
||||
// since we're in full allocation mode, we can
|
||||
// forget the piece allocation tables
|
||||
|
||||
std::vector<int>().swap(m_piece_to_slot);
|
||||
std::vector<int>().swap(m_slot_to_piece);
|
||||
m_state = state_create_files;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// in this case we're in full allocation mode, but
|
||||
// we're resuming a compact allocated storage
|
||||
m_state = state_expand_pieces;
|
||||
m_current_slot = 0;
|
||||
error_msg = "pieces needs to be reordered";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_state = state_create_files;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1572,18 +1544,13 @@ namespace libtorrent
|
|||
|
||||
| |
|
||||
| v
|
||||
| +------------+
|
||||
| | full_check |
|
||||
| +------------+
|
||||
| |
|
||||
| v
|
||||
| +------------+
|
||||
|->| allocating |
|
||||
| +------------+
|
||||
| |
|
||||
| v
|
||||
| +--------------+
|
||||
|->| create_files |
|
||||
| +------------+ +---------------+
|
||||
| | full_check |-->| expand_pieses |
|
||||
| +------------+ +---------------+
|
||||
| | |
|
||||
| v |
|
||||
| +--------------+ |
|
||||
+->| create_files | <------+
|
||||
+--------------+
|
||||
|
|
||||
v
|
||||
|
@ -1602,67 +1569,97 @@ namespace libtorrent
|
|||
std::pair<bool, float> piece_manager::check_files(
|
||||
std::vector<bool>& pieces, int& num_pieces, boost::recursive_mutex& mutex)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
boost::recursive_mutex::scoped_lock l_(mutex);
|
||||
TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true));
|
||||
|
||||
if (m_state == state_allocating)
|
||||
{
|
||||
if (m_compact_mode || m_unallocated_slots.empty())
|
||||
{
|
||||
m_state = state_create_files;
|
||||
return std::make_pair(false, 1.f);
|
||||
}
|
||||
|
||||
if (int(m_unallocated_slots.size()) == m_info->num_pieces()
|
||||
&& !m_fill_mode)
|
||||
{
|
||||
// if there is not a single file on disk, just
|
||||
// create the files
|
||||
m_state = state_create_files;
|
||||
return std::make_pair(false, 1.f);
|
||||
}
|
||||
|
||||
// if we're not in compact mode, make sure the
|
||||
// pieces are spread out and placed at their
|
||||
// final position.
|
||||
TORRENT_ASSERT(!m_unallocated_slots.empty());
|
||||
|
||||
if (!m_fill_mode)
|
||||
{
|
||||
// if we're not filling the allocation
|
||||
// just make sure we move the current pieces
|
||||
// into place, and just skip all other
|
||||
// allocation
|
||||
// allocate_slots returns true if it had to
|
||||
// move any data
|
||||
allocate_slots(m_unallocated_slots.size(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocate_slots(1);
|
||||
}
|
||||
|
||||
return std::make_pair(false, 1.f - (float)m_unallocated_slots.size()
|
||||
/ (float)m_slot_to_piece.size());
|
||||
}
|
||||
l_.unlock();
|
||||
#endif
|
||||
|
||||
if (m_state == state_create_files)
|
||||
{
|
||||
m_storage->initialize(!m_fill_mode && !m_compact_mode);
|
||||
|
||||
if (!m_unallocated_slots.empty() && !m_compact_mode)
|
||||
{
|
||||
TORRENT_ASSERT(!m_fill_mode);
|
||||
std::vector<int>().swap(m_unallocated_slots);
|
||||
std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned));
|
||||
m_free_slots.resize(m_info->num_pieces());
|
||||
for (int i = 0; i < m_info->num_pieces(); ++i)
|
||||
m_free_slots[i] = i;
|
||||
}
|
||||
|
||||
m_storage->initialize(m_storage_mode == storage_mode_allocate);
|
||||
m_state = state_finished;
|
||||
return std::make_pair(true, 1.f);
|
||||
}
|
||||
|
||||
if (m_state == state_expand_pieces)
|
||||
{
|
||||
INVARIANT_CHECK;
|
||||
|
||||
if (m_scratch_piece >= 0)
|
||||
{
|
||||
int piece = m_scratch_piece;
|
||||
int other_piece = m_slot_to_piece[piece];
|
||||
m_scratch_piece = -1;
|
||||
|
||||
if (other_piece >= 0)
|
||||
{
|
||||
if (m_scratch_buffer2.empty())
|
||||
m_scratch_buffer2.resize(m_info->piece_length());
|
||||
|
||||
m_storage->read(&m_scratch_buffer2[0], piece, 0, m_info->piece_size(other_piece));
|
||||
m_scratch_piece = other_piece;
|
||||
m_piece_to_slot[other_piece] = unassigned;
|
||||
}
|
||||
|
||||
// the slot where this piece belongs is
|
||||
// free. Just move the piece there.
|
||||
m_storage->write(&m_scratch_buffer[0], piece, 0, m_info->piece_size(piece));
|
||||
m_piece_to_slot[piece] = piece;
|
||||
m_slot_to_piece[piece] = piece;
|
||||
|
||||
if (other_piece >= 0)
|
||||
m_scratch_buffer.swap(m_scratch_buffer2);
|
||||
|
||||
return std::make_pair(false, (float)m_current_slot / m_info->num_pieces());
|
||||
}
|
||||
|
||||
while (m_current_slot < m_info->num_pieces()
|
||||
&& (m_slot_to_piece[m_current_slot] == m_current_slot
|
||||
|| m_slot_to_piece[m_current_slot] < 0))
|
||||
{
|
||||
++m_current_slot;
|
||||
}
|
||||
|
||||
if (m_current_slot == m_info->num_pieces())
|
||||
{
|
||||
m_state = state_create_files;
|
||||
std::vector<char>().swap(m_scratch_buffer);
|
||||
std::vector<char>().swap(m_scratch_buffer2);
|
||||
if (m_storage_mode != storage_mode_compact)
|
||||
{
|
||||
std::vector<int>().swap(m_piece_to_slot);
|
||||
std::vector<int>().swap(m_slot_to_piece);
|
||||
}
|
||||
return std::make_pair(false, 1.f);
|
||||
}
|
||||
|
||||
int piece = m_slot_to_piece[m_current_slot];
|
||||
TORRENT_ASSERT(piece >= 0);
|
||||
int other_piece = m_slot_to_piece[piece];
|
||||
if (other_piece >= 0)
|
||||
{
|
||||
// there is another piece in the slot
|
||||
// where this one goes. Store it in the scratch
|
||||
// buffer until next iteration.
|
||||
if (m_scratch_buffer.empty())
|
||||
m_scratch_buffer.resize(m_info->piece_length());
|
||||
|
||||
m_storage->read(&m_scratch_buffer[0], piece, 0, m_info->piece_size(other_piece));
|
||||
m_scratch_piece = other_piece;
|
||||
m_piece_to_slot[other_piece] = unassigned;
|
||||
}
|
||||
|
||||
// the slot where this piece belongs is
|
||||
// free. Just move the piece there.
|
||||
m_storage->move_slot(m_current_slot, piece);
|
||||
m_piece_to_slot[piece] = piece;
|
||||
m_slot_to_piece[m_current_slot] = unassigned;
|
||||
m_slot_to_piece[piece] = piece;
|
||||
|
||||
return std::make_pair(false, (float)m_current_slot / m_info->num_pieces());
|
||||
}
|
||||
|
||||
TORRENT_ASSERT(m_state == state_full_check);
|
||||
|
||||
// ------------------------
|
||||
|
@ -1674,12 +1671,13 @@ namespace libtorrent
|
|||
// initialization for the full check
|
||||
if (m_hash_to_piece.empty())
|
||||
{
|
||||
m_current_slot = 0;
|
||||
for (int i = 0; i < m_info->num_pieces(); ++i)
|
||||
{
|
||||
m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i));
|
||||
}
|
||||
boost::recursive_mutex::scoped_lock l(mutex);
|
||||
std::fill(pieces.begin(), pieces.end(), false);
|
||||
num_pieces = 0;
|
||||
}
|
||||
|
||||
m_piece_data.resize(int(m_info->piece_length()));
|
||||
|
@ -1694,6 +1692,10 @@ namespace libtorrent
|
|||
int piece_index = identify_data(m_piece_data, m_current_slot
|
||||
, pieces, num_pieces, m_hash_to_piece, mutex);
|
||||
|
||||
if (piece_index != m_current_slot
|
||||
&& piece_index >= 0)
|
||||
m_out_of_place = true;
|
||||
|
||||
TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true));
|
||||
TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0);
|
||||
|
||||
|
@ -1745,9 +1747,12 @@ namespace libtorrent
|
|||
std::vector<int>::iterator i =
|
||||
std::find(m_free_slots.begin(), m_free_slots.end(), other_slot);
|
||||
TORRENT_ASSERT(i != m_free_slots.end());
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
{
|
||||
m_free_slots.erase(i);
|
||||
m_free_slots.push_back(m_current_slot);
|
||||
}
|
||||
}
|
||||
|
||||
if (other_piece >= 0)
|
||||
m_storage->swap_slots(other_slot, m_current_slot);
|
||||
|
@ -1770,7 +1775,8 @@ namespace libtorrent
|
|||
m_slot_to_piece[other_slot] = piece_index;
|
||||
m_piece_to_slot[other_piece] = m_current_slot;
|
||||
|
||||
if (piece_index == unassigned)
|
||||
if (piece_index == unassigned
|
||||
&& m_storage_mode == storage_mode_compact)
|
||||
m_free_slots.push_back(other_slot);
|
||||
|
||||
if (piece_index >= 0)
|
||||
|
@ -1845,9 +1851,12 @@ namespace libtorrent
|
|||
std::vector<int>::iterator i =
|
||||
std::find(m_free_slots.begin(), m_free_slots.end(), slot1);
|
||||
TORRENT_ASSERT(i != m_free_slots.end());
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
{
|
||||
m_free_slots.erase(i);
|
||||
m_free_slots.push_back(slot2);
|
||||
}
|
||||
}
|
||||
|
||||
if (piece1 >= 0)
|
||||
{
|
||||
|
@ -1873,7 +1882,7 @@ namespace libtorrent
|
|||
// the slot was identified as piece 'piece_index'
|
||||
if (piece_index != unassigned)
|
||||
m_piece_to_slot[piece_index] = m_current_slot;
|
||||
else
|
||||
else if (m_storage_mode == storage_mode_compact)
|
||||
m_free_slots.push_back(m_current_slot);
|
||||
|
||||
m_slot_to_piece[m_current_slot] = piece_index;
|
||||
|
@ -1899,11 +1908,14 @@ namespace libtorrent
|
|||
(file_offset - current_offset + m_info->piece_length() - 1)
|
||||
/ m_info->piece_length());
|
||||
|
||||
if (m_storage_mode == storage_mode_compact)
|
||||
{
|
||||
for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i)
|
||||
{
|
||||
TORRENT_ASSERT(m_slot_to_piece[i] == unallocated);
|
||||
m_unallocated_slots.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// current slot will increase by one at the end of the for-loop too
|
||||
m_current_slot += skip_blocks - 1;
|
||||
|
@ -1917,8 +1929,39 @@ namespace libtorrent
|
|||
// clear the memory we've been using
|
||||
std::vector<char>().swap(m_piece_data);
|
||||
std::multimap<sha1_hash, int>().swap(m_hash_to_piece);
|
||||
m_state = state_allocating;
|
||||
|
||||
if (m_storage_mode != storage_mode_compact)
|
||||
{
|
||||
if (!m_out_of_place)
|
||||
{
|
||||
// if no piece is out of place
|
||||
// since we're in full allocation mode, we can
|
||||
// forget the piece allocation tables
|
||||
|
||||
std::vector<int>().swap(m_piece_to_slot);
|
||||
std::vector<int>().swap(m_slot_to_piece);
|
||||
m_state = state_create_files;
|
||||
return std::make_pair(false, 1.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in this case we're in full allocation mode, but
|
||||
// we're resuming a compact allocated storage
|
||||
m_state = state_expand_pieces;
|
||||
m_current_slot = 0;
|
||||
return std::make_pair(false, 0.f);
|
||||
}
|
||||
}
|
||||
else if (m_unallocated_slots.empty())
|
||||
{
|
||||
switch_to_full_mode();
|
||||
}
|
||||
m_state = state_create_files;
|
||||
|
||||
#ifndef NDEBUG
|
||||
boost::recursive_mutex::scoped_lock l(mutex);
|
||||
TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true));
|
||||
#endif
|
||||
return std::make_pair(false, 1.f);
|
||||
}
|
||||
|
||||
|
@ -1927,10 +1970,26 @@ namespace libtorrent
|
|||
return std::make_pair(false, (float)m_current_slot / m_info->num_pieces());
|
||||
}
|
||||
|
||||
void piece_manager::switch_to_full_mode()
|
||||
{
|
||||
TORRENT_ASSERT(m_storage_mode == storage_mode_compact);
|
||||
TORRENT_ASSERT(m_unallocated_slots.empty());
|
||||
// we have allocated all slots, switch to
|
||||
// full allocation mode in order to free
|
||||
// some unnecessary memory.
|
||||
m_storage_mode = storage_mode_sparse;
|
||||
std::vector<int>().swap(m_unallocated_slots);
|
||||
std::vector<int>().swap(m_free_slots);
|
||||
std::vector<int>().swap(m_piece_to_slot);
|
||||
std::vector<int>().swap(m_slot_to_piece);
|
||||
}
|
||||
|
||||
int piece_manager::allocate_slot_for_piece(int piece_index)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(m_mutex);
|
||||
|
||||
if (m_storage_mode != storage_mode_compact) return piece_index;
|
||||
|
||||
// INVARIANT_CHECK;
|
||||
|
||||
TORRENT_ASSERT(piece_index >= 0);
|
||||
|
@ -2030,25 +2089,26 @@ namespace libtorrent
|
|||
debug_log();
|
||||
#endif
|
||||
}
|
||||
|
||||
TORRENT_ASSERT(slot_index >= 0);
|
||||
TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size());
|
||||
|
||||
if (m_unallocated_slots.empty())
|
||||
{
|
||||
switch_to_full_mode();
|
||||
}
|
||||
|
||||
return slot_index;
|
||||
}
|
||||
|
||||
bool piece_manager::allocate_slots(int num_slots, bool abort_on_disk)
|
||||
{
|
||||
TORRENT_ASSERT(num_slots > 0);
|
||||
|
||||
boost::recursive_mutex::scoped_lock lock(m_mutex);
|
||||
TORRENT_ASSERT(num_slots > 0);
|
||||
|
||||
// INVARIANT_CHECK;
|
||||
|
||||
TORRENT_ASSERT(!m_unallocated_slots.empty());
|
||||
|
||||
const int stack_buffer_size = 16*1024;
|
||||
char zeroes[stack_buffer_size];
|
||||
memset(zeroes, 0, stack_buffer_size);
|
||||
TORRENT_ASSERT(m_storage_mode == storage_mode_compact);
|
||||
|
||||
bool written = false;
|
||||
|
||||
|
@ -2069,32 +2129,57 @@ namespace libtorrent
|
|||
m_piece_to_slot[pos] = pos;
|
||||
written = true;
|
||||
}
|
||||
else if (m_fill_mode)
|
||||
{
|
||||
int piece_size = int(m_info->piece_size(pos));
|
||||
int offset = 0;
|
||||
for (; piece_size > 0; piece_size -= stack_buffer_size
|
||||
, offset += stack_buffer_size)
|
||||
{
|
||||
m_storage->write(zeroes, pos, offset
|
||||
, (std::min)(piece_size, stack_buffer_size));
|
||||
}
|
||||
written = true;
|
||||
}
|
||||
m_unallocated_slots.erase(m_unallocated_slots.begin());
|
||||
m_slot_to_piece[new_free_slot] = unassigned;
|
||||
m_free_slots.push_back(new_free_slot);
|
||||
if (abort_on_disk && written) return true;
|
||||
if (abort_on_disk && written) break;
|
||||
}
|
||||
|
||||
TORRENT_ASSERT(m_free_slots.size() > 0);
|
||||
return written;
|
||||
}
|
||||
|
||||
int piece_manager::slot_for(int piece) const
|
||||
{
|
||||
if (m_storage_mode != storage_mode_compact) return piece;
|
||||
TORRENT_ASSERT(piece < int(m_piece_to_slot.size()));
|
||||
TORRENT_ASSERT(piece >= 0);
|
||||
return m_piece_to_slot[piece];
|
||||
}
|
||||
|
||||
int piece_manager::piece_for(int slot) const
|
||||
{
|
||||
if (m_storage_mode != storage_mode_compact) return slot;
|
||||
TORRENT_ASSERT(slot < int(m_slot_to_piece.size()));
|
||||
TORRENT_ASSERT(slot >= 0);
|
||||
return m_slot_to_piece[slot];
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void piece_manager::check_invariant() const
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(m_mutex);
|
||||
|
||||
if (m_unallocated_slots.empty() && m_state == state_finished)
|
||||
{
|
||||
TORRENT_ASSERT(m_storage_mode != storage_mode_compact);
|
||||
}
|
||||
|
||||
if (m_storage_mode != storage_mode_compact)
|
||||
{
|
||||
TORRENT_ASSERT(m_unallocated_slots.empty());
|
||||
TORRENT_ASSERT(m_free_slots.empty());
|
||||
}
|
||||
|
||||
if (m_storage_mode != storage_mode_compact
|
||||
&& m_state != state_expand_pieces
|
||||
&& m_state != state_full_check)
|
||||
{
|
||||
TORRENT_ASSERT(m_piece_to_slot.empty());
|
||||
TORRENT_ASSERT(m_slot_to_piece.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_piece_to_slot.empty()) return;
|
||||
|
||||
TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces());
|
||||
|
@ -2200,6 +2285,7 @@ namespace libtorrent
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORRENT_STORAGE_DEBUG
|
||||
void piece_manager::debug_log() const
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue