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

Subscribers

People subscribed via source and target branches