Merge lp:~e7appew/ubuntu/vivid/deluge/fix-set-trackers-cherry-picked into lp:ubuntu/vivid/deluge

Proposed by Carlos Maddela
Status: Merged
Approved by: Marc Deslauriers
Approved revision: 50
Merge reported by: Marc Deslauriers
Merged at revision: not available
Proposed branch: lp:~e7appew/ubuntu/vivid/deluge/fix-set-trackers-cherry-picked
Merge into: lp:ubuntu/vivid/deluge
Diff against target: 1093 lines (+1039/-3)
7 files modified
.pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge/core/torrent.py (+984/-0)
.pc/applied-patches (+1/-0)
debian/changelog (+8/-0)
debian/patches/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch (+38/-0)
debian/patches/series (+1/-0)
debian/patches/ubuntu.series (+1/-0)
deluge/core/torrent.py (+6/-3)
To merge this branch: bzr merge lp:~e7appew/ubuntu/vivid/deluge/fix-set-trackers-cherry-picked
Reviewer Review Type Date Requested Status
Marc Deslauriers Approve
Review via email: mp+271221@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Looks good, ack. Uploaded for processing by the SRU team. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch'
2=== added directory '.pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge'
3=== added directory '.pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge/core'
4=== added file '.pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge/core/torrent.py'
5--- .pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge/core/torrent.py 1970-01-01 00:00:00 +0000
6+++ .pc/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch/deluge/core/torrent.py 2015-09-16 02:26:39 +0000
7@@ -0,0 +1,984 @@
8+#
9+# torrent.py
10+#
11+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
12+#
13+# Deluge is free software.
14+#
15+# You may redistribute it and/or modify it under the terms of the
16+# GNU General Public License, as published by the Free Software
17+# Foundation; either version 3 of the License, or (at your option)
18+# any later version.
19+#
20+# deluge is distributed in the hope that it will be useful,
21+# but WITHOUT ANY WARRANTY; without even the implied warranty of
22+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23+# See the GNU General Public License for more details.
24+#
25+# You should have received a copy of the GNU General Public License
26+# along with deluge. If not, write to:
27+# The Free Software Foundation, Inc.,
28+# 51 Franklin Street, Fifth Floor
29+# Boston, MA 02110-1301, USA.
30+#
31+# In addition, as a special exception, the copyright holders give
32+# permission to link the code of portions of this program with the OpenSSL
33+# library.
34+# You must obey the GNU General Public License in all respects for all of
35+# the code used other than OpenSSL. If you modify file(s) with this
36+# exception, you may extend this exception to your version of the file(s),
37+# but you are not obligated to do so. If you do not wish to do so, delete
38+# this exception statement from your version. If you delete this exception
39+# statement from all source files in the program, then also delete it here.
40+#
41+
42+"""Internal Torrent class"""
43+
44+import os
45+import time
46+from urllib import unquote
47+from urlparse import urlparse
48+
49+from deluge._libtorrent import lt
50+
51+import deluge.common
52+import deluge.component as component
53+from deluge.configmanager import ConfigManager, get_config_dir
54+from deluge.log import LOG as log
55+from deluge.event import *
56+
57+TORRENT_STATE = deluge.common.TORRENT_STATE
58+
59+def sanitize_filepath(filepath, folder=False):
60+ """
61+ Returns a sanitized filepath to pass to libotorrent rename_file().
62+ The filepath will have backslashes substituted along with whitespace
63+ padding and duplicate slashes stripped. If `folder` is True a trailing
64+ slash is appended to the returned filepath.
65+ """
66+ def clean_filename(filename):
67+ filename = filename.strip()
68+ if filename.replace('.', '') == '':
69+ return ''
70+ return filename
71+
72+ if '\\' in filepath or '/' in filepath:
73+ folderpath = filepath.replace('\\', '/').split('/')
74+ folderpath = [clean_filename(x) for x in folderpath]
75+ newfilepath = '/'.join(filter(None, folderpath))
76+ else:
77+ newfilepath = clean_filename(filepath)
78+
79+ if folder is True:
80+ return newfilepath + '/'
81+ else:
82+ return newfilepath
83+
84+class TorrentOptions(dict):
85+ def __init__(self):
86+ config = ConfigManager("core.conf").config
87+ options_conf_map = {
88+ "max_connections": "max_connections_per_torrent",
89+ "max_upload_slots": "max_upload_slots_per_torrent",
90+ "max_upload_speed": "max_upload_speed_per_torrent",
91+ "max_download_speed": "max_download_speed_per_torrent",
92+ "prioritize_first_last_pieces": "prioritize_first_last_pieces",
93+ "compact_allocation": "compact_allocation",
94+ "download_location": "download_location",
95+ "auto_managed": "auto_managed",
96+ "stop_at_ratio": "stop_seed_at_ratio",
97+ "stop_ratio": "stop_seed_ratio",
98+ "remove_at_ratio": "remove_seed_at_ratio",
99+ "move_completed": "move_completed",
100+ "move_completed_path": "move_completed_path",
101+ "add_paused": "add_paused",
102+ }
103+ for opt_k, conf_k in options_conf_map.iteritems():
104+ self[opt_k] = config[conf_k]
105+ self["file_priorities"] = []
106+ self["mapped_files"] = {}
107+
108+class Torrent(object):
109+ """Torrent holds information about torrents added to the libtorrent session.
110+ """
111+ def __init__(self, handle, options, state=None, filename=None, magnet=None):
112+ log.debug("Creating torrent object %s", str(handle.info_hash()))
113+ # Get the core config
114+ self.config = ConfigManager("core.conf")
115+
116+ self.rpcserver = component.get("RPCServer")
117+
118+ # This dict holds previous status dicts returned for this torrent
119+ # We use this to return dicts that only contain changes from the previous
120+ # {session_id: status_dict, ...}
121+ self.prev_status = {}
122+ from twisted.internet.task import LoopingCall
123+ self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status)
124+ self.prev_status_cleanup_loop.start(10)
125+
126+ # Set the libtorrent handle
127+ self.handle = handle
128+ # Set the torrent_id for this torrent
129+ self.torrent_id = str(handle.info_hash())
130+
131+ # Let's us know if we're waiting on a lt alert
132+ self.waiting_on_resume_data = False
133+
134+ # Keep a list of file indexes we're waiting for file_rename alerts on
135+ # This also includes the old_folder and new_folder to know what signal to send
136+ # This is so we can send one folder_renamed signal instead of multiple
137+ # file_renamed signals.
138+ # [(old_folder, new_folder, [*indexes]), ...]
139+ self.waiting_on_folder_rename = []
140+
141+ # We store the filename just in case we need to make a copy of the torrentfile
142+ if not filename:
143+ # If no filename was provided, then just use the infohash
144+ filename = self.torrent_id
145+
146+ self.filename = filename
147+
148+ # Store the magnet uri used to add this torrent if available
149+ self.magnet = magnet
150+
151+ # Holds status info so that we don't need to keep getting it from lt
152+ self.status = self.handle.status()
153+
154+ try:
155+ self.torrent_info = self.handle.get_torrent_info()
156+ except RuntimeError:
157+ self.torrent_info = None
158+
159+ # Default total_uploaded to 0, this may be changed by the state
160+ self.total_uploaded = 0
161+
162+ # Set the default options
163+ self.options = TorrentOptions()
164+ self.options.update(options)
165+
166+ # We need to keep track if the torrent is finished in the state to prevent
167+ # some weird things on state load.
168+ self.is_finished = False
169+
170+ # Load values from state if we have it
171+ if state:
172+ # This is for saving the total uploaded between sessions
173+ self.total_uploaded = state.total_uploaded
174+ # Set the trackers
175+ self.set_trackers(state.trackers)
176+ # Set the filename
177+ self.filename = state.filename
178+ self.is_finished = state.is_finished
179+ else:
180+ # Tracker list
181+ self.trackers = []
182+ # Create a list of trackers
183+ for value in self.handle.trackers():
184+ if lt.version_major == 0 and lt.version_minor < 15:
185+ tracker = {}
186+ tracker["url"] = value.url
187+ tracker["tier"] = value.tier
188+ else:
189+ tracker = value
190+ self.trackers.append(tracker)
191+
192+ # Various torrent options
193+ self.handle.resolve_countries(True)
194+
195+ self.set_options(self.options)
196+
197+ # Status message holds error info about the torrent
198+ self.statusmsg = "OK"
199+
200+ # The torrents state
201+ self.update_state()
202+
203+ # The tracker status
204+ self.tracker_status = ""
205+
206+ # This gets updated when get_tracker_host is called
207+ self.tracker_host = None
208+
209+ if state:
210+ self.time_added = state.time_added
211+ else:
212+ self.time_added = time.time()
213+
214+ # Keep track if we're forcing a recheck of the torrent so that we can
215+ # repause it after its done if necessary
216+ self.forcing_recheck = False
217+ self.forcing_recheck_paused = False
218+
219+ log.debug("Torrent object created.")
220+
221+ ## Options methods ##
222+ def set_options(self, options):
223+ OPTIONS_FUNCS = {
224+ # Functions used for setting options
225+ "auto_managed": self.set_auto_managed,
226+ "download_location": self.set_save_path,
227+ "file_priorities": self.set_file_priorities,
228+ "max_connections": self.handle.set_max_connections,
229+ "max_download_speed": self.set_max_download_speed,
230+ "max_upload_slots": self.handle.set_max_uploads,
231+ "max_upload_speed": self.set_max_upload_speed,
232+ "prioritize_first_last_pieces": self.set_prioritize_first_last
233+ }
234+ for (key, value) in options.items():
235+ if OPTIONS_FUNCS.has_key(key):
236+ OPTIONS_FUNCS[key](value)
237+
238+ self.options.update(options)
239+
240+ def get_options(self):
241+ return self.options
242+
243+
244+ def set_max_connections(self, max_connections):
245+ self.options["max_connections"] = int(max_connections)
246+ self.handle.set_max_connections(max_connections)
247+
248+ def set_max_upload_slots(self, max_slots):
249+ self.options["max_upload_slots"] = int(max_slots)
250+ self.handle.set_max_uploads(max_slots)
251+
252+ def set_max_upload_speed(self, m_up_speed):
253+ self.options["max_upload_speed"] = m_up_speed
254+ if m_up_speed < 0:
255+ v = -1
256+ else:
257+ v = int(m_up_speed * 1024)
258+
259+ self.handle.set_upload_limit(v)
260+
261+ def set_max_download_speed(self, m_down_speed):
262+ self.options["max_download_speed"] = m_down_speed
263+ if m_down_speed < 0:
264+ v = -1
265+ else:
266+ v = int(m_down_speed * 1024)
267+ self.handle.set_download_limit(v)
268+
269+ def set_prioritize_first_last(self, prioritize):
270+ if prioritize:
271+ if self.handle.has_metadata():
272+ if self.handle.get_torrent_info().num_files() == 1:
273+ # We only do this if one file is in the torrent
274+ self.options["prioritize_first_last_pieces"] = prioritize
275+ priorities = [1] * self.handle.get_torrent_info().num_pieces()
276+ priorities[0] = 7
277+ priorities[-1] = 7
278+ self.handle.prioritize_pieces(priorities)
279+
280+ def set_auto_managed(self, auto_managed):
281+ self.options["auto_managed"] = auto_managed
282+ if not (self.handle.is_paused() and not self.handle.is_auto_managed()):
283+ self.handle.auto_managed(auto_managed)
284+ self.update_state()
285+
286+ def set_stop_ratio(self, stop_ratio):
287+ self.options["stop_ratio"] = stop_ratio
288+
289+ def set_stop_at_ratio(self, stop_at_ratio):
290+ self.options["stop_at_ratio"] = stop_at_ratio
291+
292+ def set_remove_at_ratio(self, remove_at_ratio):
293+ self.options["remove_at_ratio"] = remove_at_ratio
294+
295+ def set_move_completed(self, move_completed):
296+ self.options["move_completed"] = move_completed
297+
298+ def set_move_completed_path(self, move_completed_path):
299+ self.options["move_completed_path"] = move_completed_path
300+
301+ def set_file_priorities(self, file_priorities):
302+ if len(file_priorities) != len(self.get_files()):
303+ log.debug("file_priorities len != num_files")
304+ self.options["file_priorities"] = self.handle.file_priorities()
305+ return
306+
307+ if self.options["compact_allocation"]:
308+ log.debug("setting file priority with compact allocation does not work!")
309+ self.options["file_priorities"] = self.handle.file_priorities()
310+ return
311+
312+ log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
313+
314+ self.handle.prioritize_files(file_priorities)
315+
316+ if 0 in self.options["file_priorities"]:
317+ # We have previously marked a file 'Do Not Download'
318+ # Check to see if we have changed any 0's to >0 and change state accordingly
319+ for index, priority in enumerate(self.options["file_priorities"]):
320+ if priority == 0 and file_priorities[index] > 0:
321+ # We have a changed 'Do Not Download' to a download priority
322+ self.is_finished = False
323+ self.update_state()
324+ break
325+
326+ self.options["file_priorities"] = self.handle.file_priorities()
327+ if self.options["file_priorities"] != list(file_priorities):
328+ log.warning("File priorities were not set for this torrent")
329+
330+ # Set the first/last priorities if needed
331+ self.set_prioritize_first_last(self.options["prioritize_first_last_pieces"])
332+
333+ def set_trackers(self, trackers):
334+ """Sets trackers"""
335+ if trackers == None:
336+ trackers = []
337+ for value in self.handle.trackers():
338+ tracker = {}
339+ tracker["url"] = value.url
340+ tracker["tier"] = value.tier
341+ trackers.append(tracker)
342+ self.trackers = trackers
343+ self.tracker_host = None
344+ return
345+
346+ log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
347+ tracker_list = []
348+
349+ for tracker in trackers:
350+ new_entry = lt.announce_entry(tracker["url"])
351+ new_entry.tier = tracker["tier"]
352+ tracker_list.append(new_entry)
353+ self.handle.replace_trackers(tracker_list)
354+
355+ # Print out the trackers
356+ #for t in self.handle.trackers():
357+ # log.debug("tier: %s tracker: %s", t["tier"], t["url"])
358+ # Set the tracker list in the torrent object
359+ self.trackers = trackers
360+ if len(trackers) > 0:
361+ # Force a reannounce if there is at least 1 tracker
362+ self.force_reannounce()
363+
364+ self.tracker_host = None
365+
366+ ### End Options methods ###
367+
368+ def set_save_path(self, save_path):
369+ self.options["download_location"] = save_path
370+
371+ def set_tracker_status(self, status):
372+ """Sets the tracker status"""
373+ self.tracker_status = self.get_tracker_host() + ": " + status
374+
375+ def update_state(self):
376+ """Updates the state based on what libtorrent's state for the torrent is"""
377+ # Set the initial state based on the lt state
378+ LTSTATE = deluge.common.LT_TORRENT_STATE
379+ ltstate = int(self.handle.status().state)
380+
381+ # Set self.state to the ltstate right away just incase we don't hit some
382+ # of the logic below
383+ if ltstate in LTSTATE:
384+ self.state = LTSTATE[ltstate]
385+ else:
386+ self.state = str(ltstate)
387+
388+ log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
389+ log.debug("session.is_paused: %s", component.get("Core").session.is_paused())
390+
391+ # First we check for an error from libtorrent, and set the state to that
392+ # if any occurred.
393+ if len(self.handle.status().error) > 0:
394+ # This is an error'd torrent
395+ self.state = "Error"
396+ self.set_status_message(self.handle.status().error)
397+ if self.handle.is_paused():
398+ self.handle.auto_managed(False)
399+ return
400+
401+ if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]:
402+ if self.handle.is_paused():
403+ self.state = "Paused"
404+ else:
405+ self.state = "Checking"
406+ return
407+ elif ltstate == LTSTATE["Downloading"] or ltstate == LTSTATE["Downloading Metadata"]:
408+ self.state = "Downloading"
409+ elif ltstate == LTSTATE["Finished"] or ltstate == LTSTATE["Seeding"]:
410+ self.state = "Seeding"
411+ elif ltstate == LTSTATE["Allocating"]:
412+ self.state = "Allocating"
413+
414+ if self.handle.is_paused() and self.handle.is_auto_managed() and not component.get("Core").session.is_paused():
415+ self.state = "Queued"
416+ elif component.get("Core").session.is_paused() or (self.handle.is_paused() and not self.handle.is_auto_managed()):
417+ self.state = "Paused"
418+
419+ def set_state(self, state):
420+ """Accepts state strings, ie, "Paused", "Seeding", etc."""
421+ if state not in TORRENT_STATE:
422+ log.debug("Trying to set an invalid state %s", state)
423+ return
424+
425+ self.state = state
426+ return
427+
428+ def set_status_message(self, message):
429+ self.statusmsg = message
430+
431+ def get_eta(self):
432+ """Returns the ETA in seconds for this torrent"""
433+ if self.status == None:
434+ status = self.handle.status()
435+ else:
436+ status = self.status
437+
438+ if self.is_finished and self.options["stop_at_ratio"]:
439+ # We're a seed, so calculate the time to the 'stop_share_ratio'
440+ if not status.upload_payload_rate:
441+ return 0
442+ stop_ratio = self.options["stop_ratio"]
443+ return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
444+
445+ left = status.total_wanted - status.total_wanted_done
446+
447+ if left <= 0 or status.download_payload_rate == 0:
448+ return 0
449+
450+ try:
451+ eta = left / status.download_payload_rate
452+ except ZeroDivisionError:
453+ eta = 0
454+
455+ return eta
456+
457+ def get_ratio(self):
458+ """Returns the ratio for this torrent"""
459+ if self.status == None:
460+ status = self.handle.status()
461+ else:
462+ status = self.status
463+
464+ if status.total_done > 0:
465+ # We use 'total_done' if the downloaded value is 0
466+ downloaded = status.total_done
467+ else:
468+ # Return -1.0 to signify infinity
469+ return -1.0
470+
471+ return float(status.all_time_upload) / float(downloaded)
472+
473+ def get_files(self):
474+ """Returns a list of files this torrent contains"""
475+ if self.torrent_info == None and self.handle.has_metadata():
476+ torrent_info = self.handle.get_torrent_info()
477+ else:
478+ torrent_info = self.torrent_info
479+
480+ if not torrent_info:
481+ return []
482+
483+ ret = []
484+ files = torrent_info.files()
485+ for index, file in enumerate(files):
486+ ret.append({
487+ 'index': index,
488+ # Make path separators consistent across platforms
489+ 'path': file.path.decode("utf8").replace('\\', '/'),
490+ 'size': file.size,
491+ 'offset': file.offset
492+ })
493+ return ret
494+
495+ def get_peers(self):
496+ """Returns a list of peers and various information about them"""
497+ ret = []
498+ peers = self.handle.get_peer_info()
499+
500+ for peer in peers:
501+ # We do not want to report peers that are half-connected
502+ if peer.flags & peer.connecting or peer.flags & peer.handshake:
503+ continue
504+ try:
505+ client = str(peer.client).decode("utf-8")
506+ except UnicodeDecodeError:
507+ client = str(peer.client).decode("latin-1")
508+
509+ # Make country a proper string
510+ country = str()
511+ for c in peer.country:
512+ if not c.isalpha():
513+ country += " "
514+ else:
515+ country += c
516+
517+ ret.append({
518+ "client": client,
519+ "country": country,
520+ "down_speed": peer.payload_down_speed,
521+ "ip": "%s:%s" % (peer.ip[0], peer.ip[1]),
522+ "progress": peer.progress,
523+ "seed": peer.flags & peer.seed,
524+ "up_speed": peer.payload_up_speed,
525+ })
526+
527+ return ret
528+
529+ def get_queue_position(self):
530+ """Returns the torrents queue position"""
531+ return self.handle.queue_position()
532+
533+ def get_file_progress(self):
534+ """Returns the file progress as a list of floats.. 0.0 -> 1.0"""
535+ if not self.handle.has_metadata():
536+ return 0.0
537+
538+ file_progress = self.handle.file_progress()
539+ ret = []
540+ for i,f in enumerate(self.get_files()):
541+ try:
542+ ret.append(float(file_progress[i]) / float(f["size"]))
543+ except ZeroDivisionError:
544+ ret.append(0.0)
545+
546+ return ret
547+
548+ def get_tracker_host(self):
549+ """Returns just the hostname of the currently connected tracker
550+ if no tracker is connected, it uses the 1st tracker."""
551+ if self.tracker_host:
552+ return self.tracker_host
553+
554+ if not self.status:
555+ self.status = self.handle.status()
556+
557+ tracker = self.status.current_tracker
558+ if not tracker and self.trackers:
559+ tracker = self.trackers[0]["url"]
560+
561+ if tracker:
562+ url = urlparse(tracker.replace("udp://", "http://"))
563+ if hasattr(url, "hostname"):
564+ host = (url.hostname or 'DHT')
565+ # Check if hostname is an IP address and just return it if that's the case
566+ import socket
567+ try:
568+ socket.inet_aton(host)
569+ except socket.error:
570+ pass
571+ else:
572+ # This is an IP address because an exception wasn't raised
573+ return url.hostname
574+
575+ parts = host.split(".")
576+ if len(parts) > 2:
577+ if parts[-2] in ("co", "com", "net", "org") or parts[-1] in ("uk"):
578+ host = ".".join(parts[-3:])
579+ else:
580+ host = ".".join(parts[-2:])
581+ self.tracker_host = host
582+ return host
583+ return ""
584+
585+ def get_status(self, keys, diff=False):
586+ """
587+ Returns the status of the torrent based on the keys provided
588+
589+ :param keys: the keys to get the status on
590+ :type keys: list of str
591+ :param diff: if True, will return a diff of the changes since the last
592+ call to get_status based on the session_id
593+ :type diff: bool
594+
595+ :returns: a dictionary of the status keys and their values
596+ :rtype: dict
597+
598+ """
599+
600+ # Create the full dictionary
601+ self.status = self.handle.status()
602+ if self.handle.has_metadata():
603+ self.torrent_info = self.handle.get_torrent_info()
604+
605+ # Adjust progress to be 0-100 value
606+ progress = self.status.progress * 100
607+
608+ # Adjust status.distributed_copies to return a non-negative value
609+ distributed_copies = self.status.distributed_copies
610+ if distributed_copies < 0:
611+ distributed_copies = 0.0
612+
613+ # Calculate the seeds:peers ratio
614+ if self.status.num_incomplete == 0:
615+ # Use -1.0 to signify infinity
616+ seeds_peers_ratio = -1.0
617+ else:
618+ seeds_peers_ratio = self.status.num_complete / float(self.status.num_incomplete)
619+
620+ #if you add a key here->add it to core.py STATUS_KEYS too.
621+ full_status = {
622+ "active_time": self.status.active_time,
623+ "all_time_download": self.status.all_time_download,
624+ "compact": self.options["compact_allocation"],
625+ "distributed_copies": distributed_copies,
626+ "download_payload_rate": self.status.download_payload_rate,
627+ "file_priorities": self.options["file_priorities"],
628+ "hash": self.torrent_id,
629+ "is_auto_managed": self.options["auto_managed"],
630+ "is_finished": self.is_finished,
631+ "max_connections": self.options["max_connections"],
632+ "max_download_speed": self.options["max_download_speed"],
633+ "max_upload_slots": self.options["max_upload_slots"],
634+ "max_upload_speed": self.options["max_upload_speed"],
635+ "message": self.statusmsg,
636+ "move_on_completed_path": self.options["move_completed_path"],
637+ "move_on_completed": self.options["move_completed"],
638+ "move_completed_path": self.options["move_completed_path"],
639+ "move_completed": self.options["move_completed"],
640+ "next_announce": self.status.next_announce.seconds,
641+ "num_peers": self.status.num_peers - self.status.num_seeds,
642+ "num_seeds": self.status.num_seeds,
643+ "paused": self.status.paused,
644+ "prioritize_first_last": self.options["prioritize_first_last_pieces"],
645+ "progress": progress,
646+ "remove_at_ratio": self.options["remove_at_ratio"],
647+ "save_path": self.options["download_location"],
648+ "seeding_time": self.status.seeding_time,
649+ "seeds_peers_ratio": seeds_peers_ratio,
650+ "seed_rank": self.status.seed_rank,
651+ "state": self.state,
652+ "stop_at_ratio": self.options["stop_at_ratio"],
653+ "stop_ratio": self.options["stop_ratio"],
654+ "time_added": self.time_added,
655+ "total_done": self.status.total_done,
656+ "total_payload_download": self.status.total_payload_download,
657+ "total_payload_upload": self.status.total_payload_upload,
658+ "total_peers": self.status.num_incomplete,
659+ "total_seeds": self.status.num_complete,
660+ "total_uploaded": self.status.all_time_upload,
661+ "total_wanted": self.status.total_wanted,
662+ "tracker": self.status.current_tracker,
663+ "trackers": self.trackers,
664+ "tracker_status": self.tracker_status,
665+ "upload_payload_rate": self.status.upload_payload_rate
666+ }
667+
668+ def ti_comment():
669+ if self.handle.has_metadata():
670+ try:
671+ return self.torrent_info.comment().decode("utf8", "ignore")
672+ except UnicodeDecodeError:
673+ return self.torrent_info.comment()
674+ return ""
675+
676+ def ti_name():
677+ if self.handle.has_metadata():
678+ name = self.torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0]
679+ if not name:
680+ name = self.torrent_info.name()
681+ try:
682+ return name.decode("utf8", "ignore")
683+ except UnicodeDecodeError:
684+ return name
685+
686+ elif self.magnet:
687+ try:
688+ keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
689+ name = keys.get('dn')
690+ if not name:
691+ return self.torrent_id
692+ name = unquote(name).replace('+', ' ')
693+ try:
694+ return name.decode("utf8", "ignore")
695+ except UnicodeDecodeError:
696+ return name
697+ except:
698+ pass
699+
700+ return self.torrent_id
701+
702+ def ti_priv():
703+ if self.handle.has_metadata():
704+ return self.torrent_info.priv()
705+ return False
706+ def ti_total_size():
707+ if self.handle.has_metadata():
708+ return self.torrent_info.total_size()
709+ return 0
710+ def ti_num_files():
711+ if self.handle.has_metadata():
712+ return self.torrent_info.num_files()
713+ return 0
714+ def ti_num_pieces():
715+ if self.handle.has_metadata():
716+ return self.torrent_info.num_pieces()
717+ return 0
718+ def ti_piece_length():
719+ if self.handle.has_metadata():
720+ return self.torrent_info.piece_length()
721+ return 0
722+
723+ fns = {
724+ "comment": ti_comment,
725+ "eta": self.get_eta,
726+ "file_progress": self.get_file_progress,
727+ "files": self.get_files,
728+ "is_seed": self.handle.is_seed,
729+ "name": ti_name,
730+ "num_files": ti_num_files,
731+ "num_pieces": ti_num_pieces,
732+ "peers": self.get_peers,
733+ "piece_length": ti_piece_length,
734+ "private": ti_priv,
735+ "queue": self.handle.queue_position,
736+ "ratio": self.get_ratio,
737+ "total_size": ti_total_size,
738+ "tracker_host": self.get_tracker_host,
739+ }
740+
741+ # Create the desired status dictionary and return it
742+ status_dict = {}
743+
744+ if len(keys) == 0:
745+ status_dict = full_status
746+ for key in fns:
747+ status_dict[key] = fns[key]()
748+ else:
749+ for key in keys:
750+ if key in full_status:
751+ status_dict[key] = full_status[key]
752+ elif key in fns:
753+ status_dict[key] = fns[key]()
754+
755+ session_id = self.rpcserver.get_session_id()
756+ if diff:
757+ if session_id in self.prev_status:
758+ # We have a previous status dict, so lets make a diff
759+ status_diff = {}
760+ for key, value in status_dict.items():
761+ if key in self.prev_status[session_id]:
762+ if value != self.prev_status[session_id][key]:
763+ status_diff[key] = value
764+ else:
765+ status_diff[key] = value
766+
767+ self.prev_status[session_id] = status_dict
768+ return status_diff
769+
770+ self.prev_status[session_id] = status_dict
771+ return status_dict
772+
773+ return status_dict
774+
775+ def apply_options(self):
776+ """Applies the per-torrent options that are set."""
777+ self.handle.set_max_connections(self.max_connections)
778+ self.handle.set_max_uploads(self.max_upload_slots)
779+ self.handle.set_upload_limit(int(self.max_upload_speed * 1024))
780+ self.handle.set_download_limit(int(self.max_download_speed * 1024))
781+ self.handle.prioritize_files(self.file_priorities)
782+ self.handle.resolve_countries(True)
783+
784+ def pause(self):
785+ """Pause this torrent"""
786+ # Turn off auto-management so the torrent will not be unpaused by lt queueing
787+ self.handle.auto_managed(False)
788+ if self.handle.is_paused():
789+ # This torrent was probably paused due to being auto managed by lt
790+ # Since we turned auto_managed off, we should update the state which should
791+ # show it as 'Paused'. We need to emit a torrent_paused signal because
792+ # the torrent_paused alert from libtorrent will not be generated.
793+ self.update_state()
794+ component.get("EventManager").emit(TorrentStateChangedEvent(self.torrent_id, "Paused"))
795+ else:
796+ try:
797+ self.handle.pause()
798+ except Exception, e:
799+ log.debug("Unable to pause torrent: %s", e)
800+ return False
801+
802+ return True
803+
804+ def resume(self):
805+ """Resumes this torrent"""
806+
807+ if self.handle.is_paused() and self.handle.is_auto_managed():
808+ log.debug("Torrent is being auto-managed, cannot resume!")
809+ return
810+ else:
811+ # Reset the status message just in case of resuming an Error'd torrent
812+ self.set_status_message("OK")
813+
814+ if self.handle.is_finished():
815+ # If the torrent has already reached it's 'stop_seed_ratio' then do not do anything
816+ if self.options["stop_at_ratio"]:
817+ if self.get_ratio() >= self.options["stop_ratio"]:
818+ #XXX: This should just be returned in the RPC Response, no event
819+ #self.signals.emit_event("torrent_resume_at_stop_ratio")
820+ return
821+
822+ if self.options["auto_managed"]:
823+ # This torrent is to be auto-managed by lt queueing
824+ self.handle.auto_managed(True)
825+
826+ try:
827+ self.handle.resume()
828+ except:
829+ pass
830+
831+ return True
832+
833+ def connect_peer(self, ip, port):
834+ """adds manual peer"""
835+ try:
836+ self.handle.connect_peer((ip, int(port)), 0)
837+ except Exception, e:
838+ log.debug("Unable to connect to peer: %s", e)
839+ return False
840+ return True
841+
842+ def move_storage(self, dest):
843+ """Move a torrent's storage location"""
844+ try:
845+ dest = unicode(dest, "utf-8")
846+ except TypeError:
847+ # String is already unicode
848+ pass
849+
850+ if not os.path.exists(dest):
851+ try:
852+ # Try to make the destination path if it doesn't exist
853+ os.makedirs(dest)
854+ except IOError, e:
855+ log.exception(e)
856+ log.error("Could not move storage for torrent %s since %s does not exist and could not create the directory.", self.torrent_id, dest_u)
857+ return False
858+
859+ dest_bytes = dest.encode('utf-8')
860+ try:
861+ # libtorrent needs unicode object if wstrings are enabled, utf8 bytestring otherwise
862+ try:
863+ self.handle.move_storage(dest)
864+ except TypeError:
865+ self.handle.move_storage(dest_bytes)
866+ except Exception, e:
867+ log.error("Error calling libtorrent move_storage: %s" % e)
868+ return False
869+
870+ return True
871+
872+ def save_resume_data(self):
873+ """Signals libtorrent to build resume data for this torrent, it gets
874+ returned in a libtorrent alert"""
875+ self.handle.save_resume_data()
876+ self.waiting_on_resume_data = True
877+
878+ def on_metadata_received(self):
879+ if self.options["prioritize_first_last_pieces"]:
880+ self.set_prioritize_first_last(True)
881+ self.write_torrentfile()
882+
883+ def write_torrentfile(self):
884+ """Writes the torrent file"""
885+ path = "%s/%s.torrent" % (
886+ os.path.join(get_config_dir(), "state"),
887+ self.torrent_id)
888+ log.debug("Writing torrent file: %s", path)
889+ try:
890+ self.torrent_info = self.handle.get_torrent_info()
891+ # Regenerate the file priorities
892+ self.set_file_priorities([])
893+ md = lt.bdecode(self.torrent_info.metadata())
894+ torrent_file = {}
895+ torrent_file["info"] = md
896+ open(path, "wb").write(lt.bencode(torrent_file))
897+ except Exception, e:
898+ log.warning("Unable to save torrent file: %s", e)
899+
900+ def delete_torrentfile(self):
901+ """Deletes the .torrent file in the state"""
902+ path = "%s/%s.torrent" % (
903+ os.path.join(get_config_dir(), "state"),
904+ self.torrent_id)
905+ log.debug("Deleting torrent file: %s", path)
906+ try:
907+ os.remove(path)
908+ except Exception, e:
909+ log.warning("Unable to delete the torrent file: %s", e)
910+
911+ def force_reannounce(self):
912+ """Force a tracker reannounce"""
913+ try:
914+ self.handle.force_reannounce()
915+ except Exception, e:
916+ log.debug("Unable to force reannounce: %s", e)
917+ return False
918+
919+ return True
920+
921+ def scrape_tracker(self):
922+ """Scrape the tracker"""
923+ try:
924+ self.handle.scrape_tracker()
925+ except Exception, e:
926+ log.debug("Unable to scrape tracker: %s", e)
927+ return False
928+
929+ return True
930+
931+ def force_recheck(self):
932+ """Forces a recheck of the torrents pieces"""
933+ paused = self.handle.is_paused()
934+ try:
935+ self.handle.force_recheck()
936+ self.handle.resume()
937+ except Exception, e:
938+ log.debug("Unable to force recheck: %s", e)
939+ return False
940+ self.forcing_recheck = True
941+ self.forcing_recheck_paused = paused
942+ return True
943+
944+ def rename_files(self, filenames):
945+ """Renames files in the torrent. 'filenames' should be a list of
946+ (index, filename) pairs."""
947+ for index, filename in filenames:
948+ # Make sure filename is a unicode object
949+ try:
950+ filename = unicode(filename, "utf-8")
951+ except TypeError:
952+ pass
953+ filename = sanitize_filepath(filename)
954+ # libtorrent needs unicode object if wstrings are enabled, utf8 bytestring otherwise
955+ try:
956+ self.handle.rename_file(index, filename)
957+ except TypeError:
958+ self.handle.rename_file(index, filename.encode("utf-8"))
959+
960+ def rename_folder(self, folder, new_folder):
961+ """Renames a folder within a torrent. This basically does a file rename
962+ on all of the folders children."""
963+ log.debug("attempting to rename folder: %s to %s", folder, new_folder)
964+ if len(new_folder) < 1:
965+ log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder)
966+ return
967+
968+ new_folder = sanitize_filepath(new_folder, folder=True)
969+
970+ wait_on_folder = (folder, new_folder, [])
971+ for f in self.get_files():
972+ if f["path"].startswith(folder):
973+ # Keep a list of filerenames we're waiting on
974+ wait_on_folder[2].append(f["index"])
975+ new_path = f["path"].replace(folder, new_folder, 1)
976+ try:
977+ self.handle.rename_file(f["index"], new_path)
978+ except TypeError:
979+ self.handle.rename_file(f["index"], new_path.encode("utf-8"))
980+ self.waiting_on_folder_rename.append(wait_on_folder)
981+
982+ def cleanup_prev_status(self):
983+ """
984+ This method gets called to check the validity of the keys in the prev_status
985+ dict. If the key is no longer valid, the dict will be deleted.
986+
987+ """
988+ for key in self.prev_status.keys():
989+ if not self.rpcserver.is_session_valid(key):
990+ del self.prev_status[key]
991+
992
993=== modified file '.pc/applied-patches'
994--- .pc/applied-patches 2014-08-30 20:20:56 +0000
995+++ .pc/applied-patches 2015-09-16 02:26:39 +0000
996@@ -1,2 +1,3 @@
997 new_release_check.patch
998+Fix-AttributeError-in-set_trackers-with-lt-1.0.patch
999 ubuntu_indicator.patch
1000
1001=== modified file 'debian/changelog'
1002--- debian/changelog 2015-04-05 10:59:46 +0000
1003+++ debian/changelog 2015-09-16 02:26:39 +0000
1004@@ -1,3 +1,11 @@
1005+deluge (1.3.11-0ubuntu2.1) vivid; urgency=medium
1006+
1007+ * Non-maintainer upload.
1008+ * Fix AttributeError in set_trackers with lt 1.0, cherry-picked from
1009+ upstream (LP: #1487704) (#2223).
1010+
1011+ -- Carlos Maddela <maddela@labyrinth.net.au> Wed, 16 Sep 2015 11:55:31 +1000
1012+
1013 deluge (1.3.11-0ubuntu2) vivid; urgency=medium
1014
1015 * Import changes from Debian Testing:
1016
1017=== added file 'debian/patches/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch'
1018--- debian/patches/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch 1970-01-01 00:00:00 +0000
1019+++ debian/patches/Fix-AttributeError-in-set_trackers-with-lt-1.0.patch 2015-09-16 02:26:39 +0000
1020@@ -0,0 +1,38 @@
1021+From: Calum Lind <calumlind+deluge@gmail.com>
1022+Date: Sat, 22 Aug 2015 15:31:26 +0100
1023+Subject: Fix AttributeError in set_trackers with lt 1.0
1024+
1025+Description: Fix AttributeError in set_trackers with lt 1.0
1026+ When using a more recent version of libtorrent, set_trackers()
1027+ will result in an "AttributeError: 'dict' object has no attribute 'url'"
1028+ exception if the trackers parameter is None, just like upstream bug #2223.
1029+Author: Calum Lind <calumlind+deluge@gmail.com>
1030+Origin: upstream, http://git.deluge-torrent.org/deluge/commit/?h=1.3-stable&id=acf4fc4193d
1031+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/deluge/+bug/1487704
1032+Last-Update: 2015-08-22
1033+---
1034+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
1035+---
1036+ deluge/core/torrent.py | 9 ++++++---
1037+ 1 file changed, 6 insertions(+), 3 deletions(-)
1038+
1039+diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py
1040+index 7ca35e1..9003d8b 100644
1041+--- a/deluge/core/torrent.py
1042++++ b/deluge/core/torrent.py
1043+@@ -328,9 +328,12 @@ class Torrent(object):
1044+ if trackers == None:
1045+ trackers = []
1046+ for value in self.handle.trackers():
1047+- tracker = {}
1048+- tracker["url"] = value.url
1049+- tracker["tier"] = value.tier
1050++ if lt.version_major == 0 and lt.version_minor < 15:
1051++ tracker = {}
1052++ tracker["url"] = value.url
1053++ tracker["tier"] = value.tier
1054++ else:
1055++ tracker = value
1056+ trackers.append(tracker)
1057+ self.trackers = trackers
1058+ self.tracker_host = None
1059
1060=== modified file 'debian/patches/series'
1061--- debian/patches/series 2014-12-14 20:16:28 +0000
1062+++ debian/patches/series 2015-09-16 02:26:39 +0000
1063@@ -1,1 +1,2 @@
1064 new_release_check.patch
1065+Fix-AttributeError-in-set_trackers-with-lt-1.0.patch
1066
1067=== modified file 'debian/patches/ubuntu.series'
1068--- debian/patches/ubuntu.series 2014-08-30 20:20:56 +0000
1069+++ debian/patches/ubuntu.series 2015-09-16 02:26:39 +0000
1070@@ -1,2 +1,3 @@
1071 new_release_check.patch
1072+Fix-AttributeError-in-set_trackers-with-lt-1.0.patch
1073 ubuntu_indicator.patch
1074
1075=== modified file 'deluge/core/torrent.py'
1076--- deluge/core/torrent.py 2014-10-05 11:27:46 +0000
1077+++ deluge/core/torrent.py 2015-09-16 02:26:39 +0000
1078@@ -328,9 +328,12 @@
1079 if trackers == None:
1080 trackers = []
1081 for value in self.handle.trackers():
1082- tracker = {}
1083- tracker["url"] = value.url
1084- tracker["tier"] = value.tier
1085+ if lt.version_major == 0 and lt.version_minor < 15:
1086+ tracker = {}
1087+ tracker["url"] = value.url
1088+ tracker["tier"] = value.tier
1089+ else:
1090+ tracker = value
1091 trackers.append(tracker)
1092 self.trackers = trackers
1093 self.tracker_host = None

Subscribers

People subscribed via source and target branches