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