diff --git a/plugins/TorrentPieces/__init__.py b/plugins/TorrentPieces/__init__.py index b83d2bb34..b1beac58a 100644 --- a/plugins/TorrentPieces/__init__.py +++ b/plugins/TorrentPieces/__init__.py @@ -18,7 +18,7 @@ plugin_name = _("Torrent Pieces") plugin_author = "Micah Bucy" -plugin_version = "0.1" +plugin_version = "0.2" plugin_description = _(""" Adds a pieces tab which gives piece by piece progress for a torrent. Each piece is represented by a small progress bar. @@ -33,7 +33,11 @@ of blocks finished as well as the peer speed for that piece. When the plugin initializes, such as when enabling the plugin or when a different torrent is selected, the cpu will spike. This is normal, as initialization must get information on every piece from libtorrent, -and the cpu will normalize once all of the information is retrieved.""") +and the cpu will normalize once all of the information is retrieved. + +This plugin supports multifile torrents. If a file is skipped, it does not +show up in the pieces tab. +""") def deluge_init(deluge_path): global path @@ -80,12 +84,14 @@ class TorrentPieces: break def update(self): + update_files_removed = self.manager.update_files_removed unique_id = self.parent.get_selected_torrent() - # If no torrents added or more than one torrent selected if unique_id is None: + #if no torrents added or more than one torrent selected self.tab_pieces.clear_pieces_store() return - if unique_id != self.tab_pieces.unique_id: + if unique_id != self.tab_pieces.unique_id or unique_id in update_files_removed.keys(): + #if different torrent was selected or file priorities were changed. self.manager.disconnect_event(self.manager.constants['EVENT_PIECE_FINISHED'], self.tab_pieces.handle_event) self.manager.disconnect_event(self.manager.constants['EVENT_BLOCK_FINISHED'], self.tab_pieces.handle_event) self.manager.disconnect_event(self.manager.constants['EVENT_BLOCK_DOWNLOADING'], self.tab_pieces.handle_event) diff --git a/plugins/TorrentPieces/tab_pieces.py b/plugins/TorrentPieces/tab_pieces.py index 54ced227e..6b20db5b4 100644 --- a/plugins/TorrentPieces/tab_pieces.py +++ b/plugins/TorrentPieces/tab_pieces.py @@ -1,11 +1,11 @@ +from itertools import izip import gtk - import math class PiecesManager(object): def __init__(self, viewport, manager): self.viewport = viewport - self.table = None + self.vbox = None self.manager = manager self.progress = [] self.tooltips = [] @@ -17,6 +17,8 @@ class PiecesManager(object): self.unique_id = -1 self.peer_speed = [] self.piece_info = [] + self.first_indexes = [] + self.last_indexes = [] def set_unique_id(self, unique_id): self.unique_id = unique_id @@ -24,59 +26,219 @@ class PiecesManager(object): def clear_pieces_store(self): self.unique_id = -1 self.rows = 0 - if not self.table is None: - self.table.destroy() - self.table = None + if not self.vbox is None: + self.vbox.destroy() + self.vbox = None self.peer_speed = [] self.eventboxes = [] self.progress = [] self.piece_info = [] self.tooltips = [] + self.first_indexes = [] + self.last_indexes = [] def prepare_pieces_store(self): - self.table = gtk.Table() - self.viewport.add(self.table) + self.vbox = gtk.VBox() + self.viewport.add(self.vbox) + all_files = self.manager.get_torrent_file_info(self.unique_id) + file_priorities = self.manager.get_priorities(self.unique_id) state = self.manager.get_torrent_state(self.unique_id) num_pieces = state["num_pieces"] - self.rows = int(math.ceil(num_pieces/self.columns)) - self.table.resize(self.rows, self.columns) - self.table.set_size_request((self.columns+1)*self.piece_width, (self.rows+1)*self.piece_height) - for index in xrange(num_pieces): - self.piece_info.append({'blocks_total':0, 'blocks_finished':0, 'blocks_requested':0}) - self.progress.append(gtk.ProgressBar()) - self.tooltips.append(gtk.Tooltips()) - self.eventboxes.append(gtk.EventBox()) - self.peer_speed.append("unknown") - self.progress[index].set_size_request(self.piece_width, self.piece_height) - row = index/self.columns - column = index%self.columns - self.table.attach(self.eventboxes[index], column, column+1, row, row+1, - xoptions=0, yoptions=0, xpadding=0, ypadding=0) - self.eventboxes[index].add(self.progress[index]) - if self.manager.has_piece(self.unique_id, index): - self.progress[index].set_fraction(1) - self.tooltips[index].set_tip(self.eventboxes[index], _("Piece finished")) + prev_file_index = -1 + file_index = 0 + next_file_index = 1 + for file, priority in izip(all_files, file_priorities): + if file_index == 0: + file_piece_range = self.manager.get_file_piece_range(self.unique_id,\ + file_index, file['size']) + self.first_indexes.append(file_piece_range['first_index']) + self.last_indexes.append(file_piece_range['last_index']) + if priority > 0: + #if file is being downloaded build the file pieces information + temp_prev_priority = 1 + label = gtk.Label() + label.set_alignment(0,0) + label.set_text(file['path']) + self.vbox.pack_start(label, expand=False) + table = gtk.Table() + self.rows = int(math.ceil((self.last_indexes[file_index]-self.first_indexes[file_index])/self.columns)+1) + self.vbox.pack_start(table, expand=False) + table.resize(self.rows, self.columns) + table.set_size_request((self.columns+1)*self.piece_width, (self.rows+1)*self.piece_height) + if self.last_indexes[file_index] != self.first_indexes[file_index]: + #if there is more than one piece + if self.first_indexes[file_index] == 0\ + or self.first_indexes[file_index] != self.last_indexes[prev_file_index]: + #if first piece is not a shared piece + temp_range = self.last_indexes[file_index]-self.first_indexes[file_index] + diff = 0 + else: + #if first piece is shared + temp_prev_priority = file_priorities[prev_file_index] + if temp_prev_priority > 0: + #if last file was not skipped, skip the first piece + diff = 1 + temp_range = self.last_indexes[file_index]-(self.first_indexes[file_index]+1) + #otherwise keep the first piece + else: + diff = 0 + temp_range = self.last_indexes[file_index]-self.first_indexes[file_index] + #last piece handled outside of loop, skip it from range + for index in xrange(temp_range): + main_index = diff+self.first_indexes[file_index]+index + if temp_prev_priority > 0: + #normal behavior + self.piece_info.append({'blocks_total':0, 'blocks_finished':0, 'blocks_requested':0}) + self.progress.append(gtk.ProgressBar()) + self.tooltips.append(gtk.Tooltips()) + self.eventboxes.append(gtk.EventBox()) + self.peer_speed.append("unknown") + else: + #if first piece is shared with a skipped file + self.piece_info[main_index] = {'blocks_total':0, 'blocks_finished':0, 'blocks_requested':0} + self.progress[main_index] = gtk.ProgressBar() + self.tooltips[main_index] = gtk.Tooltips() + self.eventboxes[main_index] = gtk.EventBox() + self.peer_speed[main_index] = "unknown" + temp_prev_priority = 1 + self.progress[main_index].set_size_request(self.piece_width, self.piece_height) + row = index/self.columns + column = index%self.columns + table.attach(self.eventboxes[main_index], column, column+1, row, row+1, + xoptions=0, yoptions=0, xpadding=0, ypadding=0) + self.eventboxes[main_index].add(self.progress[main_index]) + if self.manager.has_piece(self.unique_id, main_index): + #if piece is already finished + self.progress[main_index].set_fraction(1) + self.tooltips[main_index].set_tip(self.eventboxes[main_index], _("Piece finished")) + else: + #if piece is not already finished + self.tooltips[main_index].set_tip(self.eventboxes[main_index], _("Piece not started")) + self.piece_info.append({'blocks_total':0, 'blocks_finished':0, 'blocks_requested':0}) + self.progress.append(gtk.ProgressBar()) + self.tooltips.append(gtk.Tooltips()) + self.eventboxes.append(gtk.EventBox()) + self.peer_speed.append("unknown") + index = index+1 + only_one_piece = False + else: + #if file only has one piece + index = 0 + only_one_piece = True + main_index = self.last_indexes[file_index] + # do the following even if file has only one piece + # and the piece does not need created + if next_file_index < len(all_files): + #if there is another file + file_piece_range = self.manager.get_file_piece_range(self.unique_id,\ + next_file_index, all_files[next_file_index]['size']) + self.first_indexes.append(file_piece_range['first_index']) + self.last_indexes.append(file_piece_range['last_index']) + if file_index > 0 and not self.piece_info[main_index] is None and only_one_piece: + #if file has only one piece and it is shared destroy the table + table.destroy() + if file_index == 0 or self.piece_info[main_index] is None or not only_one_piece: + # piece could be shared if file has only one piece and it's not the first file + # only create it if it does not exist + if only_one_piece: + #if piece is shared with a skipped file + self.piece_info[main_index] = {'blocks_total':0, 'blocks_finished':0, 'blocks_requested':0} + self.progress[main_index] = gtk.ProgressBar() + self.tooltips[main_index] = gtk.Tooltips() + self.eventboxes[main_index] = gtk.EventBox() + self.peer_speed[main_index] = "unknown" + self.progress[main_index].set_size_request(self.piece_width, self.piece_height) + if next_file_index < len(all_files): + # if there is another file + if file_priorities[next_file_index]==0\ + or self.last_indexes[file_index] != self.first_indexes[next_file_index]: + #if next file is skipped or there is no shared piece, keep last piece + row=index/self.columns + column=index%self.columns + table.attach(self.eventboxes[main_index], column, column+1, row, row+1, + xoptions=0, yoptions=0, xpadding=0, ypadding=0) + self.eventboxes[main_index].add(self.progress[main_index]) + if file_priorities[next_file_index]>0\ + and self.last_indexes[file_index] == self.first_indexes[next_file_index]: + #if next file is not skipped and there is a shared piece, do not keep last piece + if only_one_piece: + #only piece in file is shared, destroy table for file + table.destroy() + label = gtk.Label() + label.set_alignment(0,0) + label.set_text(_("Piece shared with next file(s)")) + self.vbox.pack_start(label, expand=False) + temp_table = gtk.Table() + temp_table.resize(1,2) + temp_table.set_size_request(self.piece_width, 2*self.piece_height) + temp_table.attach(self.eventboxes[main_index], 0, 1, 0, 1, + xoptions=0, yoptions=0, xpadding=0, ypadding=0) + self.eventboxes[main_index].add(self.progress[main_index]) + self.vbox.pack_start(temp_table, expand=False) + else: + #if there is no other file + row=index/self.columns + column=index%self.columns + table.attach(self.eventboxes[main_index], column, column+1, row, row+1, + xoptions=0, yoptions=0, xpadding=0, ypadding=0) + self.eventboxes[main_index].add(self.progress[main_index]) + if self.manager.has_piece(self.unique_id, main_index): + #if the last piece is already finished + self.progress[main_index].set_fraction(1) + self.tooltips[main_index].set_tip(self.eventboxes[main_index], _("Piece finished")) + else: + #if the last piece is not already finished + self.tooltips[main_index].set_tip(self.eventboxes[main_index], _("Piece not started")) else: - self.tooltips[index].set_tip(self.eventboxes[index], _("Piece not started")) + #if file is not being downloaded skip the file pieces + if self.first_indexes[file_index] == 0 or self.first_indexes[file_index] != self.last_indexes[prev_file_index]: + #if first piece is not shared + temp_range = 1+self.last_indexes[file_index]-self.first_indexes[file_index] + else: + #if first piece is shared + temp_range = self.last_indexes[file_index]-self.first_indexes[file_index] + for index in xrange(temp_range): + self.piece_info.append(None) + self.progress.append(None) + self.eventboxes.append(None) + self.tooltips.append(None) + self.peer_speed.append(None) + if next_file_index < len(all_files): + #if there is another file + file_piece_range = self.manager.get_file_piece_range(self.unique_id,\ + next_file_index, all_files[next_file_index]['size']) + self.first_indexes.append(file_piece_range['first_index']) + self.last_indexes.append(file_piece_range['last_index']) + if self.last_indexes[next_file_index] >= num_pieces: + self.last_indexes[next_file_index] = num_pieces-1 + file_index += 1 + next_file_index += 1 + prev_file_index += 1 + + #get currently downloading piece information all_piece_info = self.manager.get_all_piece_info(self.unique_id) for piece_index in all_piece_info: - temp_piece_info = {'blocks_total':piece_index['blocks_total'], \ - 'blocks_finished':piece_index['blocks_finished']} - self.piece_info[piece_index['piece_index']] = temp_piece_info - blocks_total = str(temp_piece_info['blocks_total']) - info_string = str(temp_piece_info['blocks_finished']) + "/" + blocks_total + " " + _("blocks finished") + "\n" \ - + _("peer speed: unknown") - if self.progress[index].get_fraction() == 0: - self.progress[index].set_fraction(0.5) - self.tooltips[index].set_tip(self.eventboxes[index], info_string) - self.table.show_all() + index = piece_index['piece_index'] + if not self.piece_info[index] is None: + temp_piece_info = {'blocks_total':piece_index['blocks_total'], \ + 'blocks_finished':piece_index['blocks_finished']} + self.piece_info[index] = temp_piece_info + blocks_total = str(temp_piece_info['blocks_total']) + info_string = str(temp_piece_info['blocks_finished']) + "/" + blocks_total + " " + _("blocks finished") + "\n" \ + + _("peer speed: unknown") + if self.progress[index].get_fraction() == 0: + self.progress[index].set_fraction(0.5) + self.tooltips[index].set_tip(self.eventboxes[index], info_string) + self.vbox.show_all() def handle_event(self, event): - if event['event_type'] is self.manager.constants['EVENT_PIECE_FINISHED']: - if event['unique_ID'] == self.unique_id: + #protect against pieces trying to display after file priority changed + #or different torrent selected + if event['unique_ID'] == self.unique_id\ + and not self.piece_info[event['piece_index']] is None: + if event['event_type'] is self.manager.constants['EVENT_PIECE_FINISHED']: self.update_pieces_store(event['piece_index'], piece_finished=True) - elif event['event_type'] is self.manager.constants['EVENT_BLOCK_DOWNLOADING']: - if event['unique_ID'] == self.unique_id: + elif event['event_type'] is self.manager.constants['EVENT_BLOCK_DOWNLOADING']: index = event['piece_index'] if self.piece_info[index]['blocks_total'] == 0: self.piece_info[index] = self.manager.get_piece_info(self.unique_id, index) @@ -91,8 +253,7 @@ class PiecesManager(object): peer_speed_msg = _("unknown") self.peer_speed[index] = peer_speed_msg self.update_pieces_store(index) - else: - if event['unique_ID'] == self.unique_id: + else: index = event['piece_index'] if self.piece_info[index]['blocks_total'] == 0: self.piece_info[index] = self.manager.get_piece_info(self.unique_id, index) diff --git a/src/core.py b/src/core.py index 75ddb616d..3f21f5ab0 100644 --- a/src/core.py +++ b/src/core.py @@ -246,6 +246,9 @@ class Manager: # unique_ids removed by core self.removed_unique_ids = {} + # unique_ids with files just removed by user + self.update_files_removed = {} + PREF_FUNCTIONS["enable_dht"] = self.set_DHT # Unpickle the state, or create a new one @@ -318,6 +321,12 @@ class Manager: # Get the value from the preferences object return self.config.get(key) + # Get file piece range + def get_file_piece_range(self, unique_id,\ + file_index, file_size): + return deluge_core.get_file_piece_range(unique_id,\ + file_index, file_size) + # Check if piece is finished def has_piece(self, unique_id, piece_index): return deluge_core.has_piece(unique_id, piece_index) @@ -638,12 +647,17 @@ class Manager: return ret # Priorities functions - def prioritize_files(self, unique_ID, priorities): + def clear_update_files_removed(self): + self.update_files_removed = {} + + def prioritize_files(self, unique_ID, priorities, update_files_removed=False): assert(len(priorities) == \ self.get_core_torrent_state(unique_ID)['num_files']) self.unique_IDs[unique_ID].priorities = priorities[:] deluge_core.prioritize_files(unique_ID, priorities) + if update_files_removed: + self.update_files_removed[unique_ID] = 1 if self.get_pref('prioritize_first_last_pieces'): self.prioritize_first_last_pieces(unique_ID) @@ -866,12 +880,14 @@ class Manager: pass else: import random - ports = [random.randrange(49152, 65535), random.randrange(49152, 65535)] + randrange = lambda: random.randrange(49152, 65535) + ports = [randrange(), randrange()] ports.sort() deluge_core.set_listen_on(ports) else: import random - ports = [random.randrange(49152, 65535), random.randrange(49152, 65535)] + randrange = lambda: random.randrange(49152, 65535) + ports = [randrange(), randrange()] ports.sort() deluge_core.set_listen_on(ports) else: diff --git a/src/deluge_core.cpp b/src/deluge_core.cpp index 58b4e4736..80374435b 100644 --- a/src/deluge_core.cpp +++ b/src/deluge_core.cpp @@ -1261,6 +1261,26 @@ static PyObject *torrent_get_file_info(PyObject *self, PyObject *args) return ret; }; +static PyObject *torrent_get_file_piece_range(PyObject *self, PyObject *args) +{ + python_long unique_ID; + int file_index, file_size; + if (!PyArg_ParseTuple(args, "iii", &unique_ID, &file_index, &file_size)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + torrent_info const &info = M_torrents->at(index).handle.get_torrent_info(); + peer_request first_index = info.map_file(file_index, 0, 1); + peer_request last_index = info.map_file(file_index, file_size-1, 1); + return Py_BuildValue( + "{s:i,s:i}", + "first_index", first_index.piece, + "last_index", last_index.piece + ); +}; + /*static PyObject *torrent_get_unique_IDs(PyObject *self, PyObject *args) { PyObject *ret = PyTuple_New(M_torrents.size()); @@ -1878,6 +1898,7 @@ static PyMethodDef deluge_core_methods[] = {"has_piece", torrent_has_piece, METH_VARARGS, "."}, {"get_piece_info", torrent_get_piece_info, METH_VARARGS, "."}, {"get_all_piece_info", torrent_get_all_piece_info, METH_VARARGS, "."}, + {"get_file_piece_range", torrent_get_file_piece_range, METH_VARARGS, "."}, {NULL} }; diff --git a/src/interface.py b/src/interface.py index 873b5392e..0094a0732 100644 --- a/src/interface.py +++ b/src/interface.py @@ -864,6 +864,9 @@ class DelugeGTK: #Update any active plugins self.plugins.update_active_plugins() + + #Plugins have updated info, so clear it + self.manager.clear_update_files_removed() # only update gui if it's needed if self.update_interface: @@ -944,7 +947,6 @@ class DelugeGTK: # Update tool buttons below based on the first selected torrent's state path = torrent_selection.get_selected_rows()[1][0] unique_id = self.torrent_model.get_value(self.torrent_model.get_iter(path), 0) - return True def update_statusbar_and_tray(self): diff --git a/src/tab_files.py b/src/tab_files.py index 7c8732a7a..12c64e423 100644 --- a/src/tab_files.py +++ b/src/tab_files.py @@ -190,8 +190,16 @@ class FilesTabManager(FilesBaseManager): # From UI to core def update_priorities(self): - file_priorities = [x[2] for x in self.file_store] - self.manager.prioritize_files(self.file_unique_id, file_priorities) + prev_file_priorities = self.manager.get_priorities(self.file_unique_id) + file_priorities = [] + update = False + for x, priority in izip(self.file_store, prev_file_priorities): + file_priorities.append(x[2]) + if x[2] > 0 and priority == 0: + update = True + if x[2] == 0: + update = True + self.manager.prioritize_files(self.file_unique_id, file_priorities, update_files_removed=update) class FilesDialogManager(FilesBaseManager): def __init__(self, dumped_torrent):