diff --git a/src/core.py b/src/core.py index 637536e48..9d428b791 100644 --- a/src/core.py +++ b/src/core.py @@ -47,14 +47,13 @@ # time to calculate, so we do if efficiently # 3. supp_torrent_state - supplementary torrent data, from Deluge -import deluge_core +import pickle import os -import os.path import shutil import statvfs -import pickle import time -import gettext + +import deluge_core import pref # Constants @@ -167,8 +166,8 @@ class persistent_state: self.torrents = [] # Prepare queue (queue is pickled, just like everything else) - self.queue = [] # queue[x] is the unique_ID of the x-th queue position. Simple. - + # queue[x] is the unique_ID of the x-th queue position. Simple. + self.queue = [] # The manager for the torrent system @@ -236,15 +235,18 @@ class Manager: # Unpickle the state, or create a new one if not blank_slate: try: - pkl_file = open(os.path.join(self.base_dir, STATE_FILENAME), 'rb') + pkl_file = open(os.path.join(self.base_dir, STATE_FILENAME), + 'rb') self.state = pickle.load(pkl_file) pkl_file.close() - # Sync with the core: tell core about torrents, and get unique_IDs + # Sync with the core: tell core about torrents, and get + # unique_IDs self.sync() - # Apply all the file filters, right after adding the torrents - self.apply_all_file_filters() + # Apply all the file priorities, right after adding the + # torrents + self.apply_all_file_priorities() # Apply the queue at this time, after all is loaded and ready self.apply_queue() @@ -262,10 +264,7 @@ class Manager: self.config.save() # Pickle the state - print "Pickling state..." - output = open(os.path.join(self.base_dir, STATE_FILENAME), 'wb') - pickle.dump(self.state, output) - output.close() + self.pickle_state() # Stop DHT, if needed self.set_DHT(False) @@ -278,6 +277,18 @@ class Manager: print "Quitting the core..." deluge_core.quit() + def pickle_state(self): + # Pickle the state so if we experience a crash, the latest state is + # available + print "Pickling state..." + + #print self.state.torrents + #print self.state.queue + + output = open(os.path.join(self.base_dir, STATE_FILENAME), 'wb') + pickle.dump(self.state, output) + output.close() + def pre_quitting(self): # Save the uploaded data from this session to the existing upload memory for unique_ID in self.unique_IDs.keys(): @@ -306,6 +317,10 @@ class Manager: if PREF_FUNCTIONS[key] is not None: PREF_FUNCTIONS[key](value) + # Dump torrent info without adding + def dump_torrent_file_info(self, torrent): + return deluge_core.dump_file_info(torrent) + # Torrent addition and removal functions def add_torrent(self, filename, save_dir, compact): @@ -393,14 +408,13 @@ class Manager: # This is the EXTERNAL function, for the GUI. It returns the core_state + supp_state def get_torrent_state(self, unique_ID): # Check to see if unique_ID exists: - if self.state.queue.count(unique_ID) == 0: + if unique_ID not in self.state.queue: raise InvalidUniqueIDError(_("Asked for a torrent that doesn't exist")) ret = self.get_core_torrent_state(unique_ID, True).copy() # Add the deluge-level things to the deluge_core data - if self.get_supp_torrent_state(unique_ID) is not None: - ret.update(self.get_supp_torrent_state(unique_ID)) + ret.update(self.get_supp_torrent_state(unique_ID)) # Get queue position ret['queue_pos'] = self.state.queue.index(unique_ID) @@ -604,30 +618,30 @@ class Manager: return ret - # Filtering functions - - def set_file_filter(self, unique_ID, file_filter): - assert(len(file_filter) == self.get_core_torrent_state(unique_ID, True)['num_files']) - - self.unique_IDs[unique_ID].file_filter = file_filter[:] - - deluge_core.set_filter_out(unique_ID, file_filter) - - def get_file_filter(self, unique_ID): - try: - return self.unique_IDs[unique_ID].file_filter[:] - except AttributeError: - return None - # Priorities functions def prioritize_files(self, unique_ID, priorities): + assert(len(priorities) == self.get_core_torrent_state(unique_ID, + True)['num_files']) + + self.unique_IDs[unique_ID].priorities = priorities[:] deluge_core.prioritize_files(unique_ID, priorities) - # Called when a session starts, to apply existing filters - def apply_all_file_filters(self): + def get_priorities(self, unique_ID): + try: + return self.unique_IDs[unique_ID].priorities[:] + except AttributeError: + # return normal priority for all files by default + + num_files = self.get_core_torrent_state(unique_ID, + True)['num_files'] + return [1] * num_files + + # Called when a session starts, to apply existing priorities + def apply_all_file_priorities(self): for unique_ID in self.unique_IDs.keys(): try: - self.set_file_filter(unique_ID, self.unique_IDs[unique_ID].file_filter) + self.prioritize_files(unique_ID, + self.get_priorities(unique_ID)) except AttributeError: pass @@ -673,8 +687,9 @@ class Manager: # Efficient: use a saved state, if it hasn't expired yet def get_core_torrent_state(self, unique_ID, efficiently=True): - if unique_ID not in self.saved_core_torrent_states.keys(): - self.saved_core_torrent_states[unique_ID] = cached_data(deluge_core.get_torrent_state, unique_ID) + if unique_ID not in self.saved_core_torrent_states: + self.saved_core_torrent_states[unique_ID] = \ + cached_data(deluge_core.get_torrent_state, unique_ID) return self.saved_core_torrent_states[unique_ID].get(efficiently) @@ -682,7 +697,7 @@ class Manager: try: return self.supp_torrent_states[unique_ID] except KeyError: - return None + return {} def set_supp_torrent_state_val(self, unique_ID, key, val): try: @@ -754,19 +769,17 @@ class Manager: no_space = False # Add torrents to core and unique_IDs - torrents_with_unique_ID = self.unique_IDs.values() - for torrent in self.state.torrents: if not os.path.exists(torrent.filename): print "Missing file: %s" % torrent.filename self.state.torrents.remove(torrent) continue - if torrent not in torrents_with_unique_ID: + if torrent not in self.unique_IDs.values(): # print "Adding torrent to core:", torrent.filename, torrent.save_dir, torrent.compact try: unique_ID = deluge_core.add_torrent(torrent.filename, - torrent.save_dir, - torrent.compact) + torrent.save_dir, + torrent.compact) except DelugeError, e: print "Error:", e self.state.torrents.remove(torrent) @@ -775,9 +788,7 @@ class Manager: ret = unique_ID self.unique_IDs[unique_ID] = torrent - -# print torrents_with_unique_ID # Remove torrents from core, unique_IDs and queue to_delete = [] for unique_ID in self.unique_IDs.keys(): @@ -800,8 +811,9 @@ class Manager: # Add torrents to queue - at the end, of course for unique_ID in self.unique_IDs.keys(): if unique_ID not in self.state.queue: - if (self.get_pref('queue_above_completed')) and len(self.state.queue) > 0: - for index in range(len(self.state.queue)): + if self.get_pref('queue_above_completed') and \ + len(self.state.queue) > 0: + for index in xrange(len(self.state.queue)): torrent_state = self.get_core_torrent_state(self.state.queue[index]) if torrent_state['progress'] == 1.0: break @@ -827,11 +839,8 @@ class Manager: #if no_space: #self.apply_queue() - # Pickle the state so if we experience a crash, the latest state is available - print "Pickling state..." - output = open(os.path.join(self.base_dir, STATE_FILENAME), 'wb') - pickle.dump(self.state, output) - output.close() + + self.pickle_state() return ret diff --git a/src/deluge_core.cpp b/src/deluge_core.cpp index a6fcbe7ae..b75e71c26 100644 --- a/src/deluge_core.cpp +++ b/src/deluge_core.cpp @@ -97,7 +97,6 @@ using namespace libtorrent; //----------------- typedef long unique_ID_t; -typedef std::vector filter_out_t; typedef std::string torrent_name_t; struct torrent_t @@ -179,6 +178,17 @@ long get_index_from_unique_ID(long unique_ID) RAISE_INT(DelugeError, "No such unique_ID."); } +torrent_info internal_dump_file_info(std::string const& torrent_name) +{ + std::ifstream in(torrent_name.c_str(), std::ios_base::binary); + in.unsetf(std::ios_base::skipws); + entry e; + e = bdecode(std::istream_iterator(in), std::istream_iterator()); + + torrent_info t(e); + + return t; +} long internal_add_torrent(std::string const& torrent_name, float preferred_ratio, @@ -512,6 +522,34 @@ static PyObject *torrent_set_max_connections(PyObject *self, PyObject *args) Py_INCREF(Py_None); return Py_None; } +static PyObject *torrent_dump_file_info(PyObject *self, PyObject *args) +{ + const char *name; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + torrent_info t = internal_dump_file_info(name); + + PyObject *file_info; + long file_index = 0; + PyObject *ret = PyTuple_New(t.num_files()); + + for(torrent_info::file_iterator i = t.begin_files(); i != t.end_files(); ++i) + { + file_entry const &currFile = (*i); + + file_info = Py_BuildValue( + "{s:s,s:L}", + "path", currFile.path.string().c_str(), + "size", currFile.size + ); + + PyTuple_SetItem(ret, file_index, file_info); + file_index++; + }; + + return ret; +} static PyObject *torrent_add_torrent(PyObject *self, PyObject *args) { @@ -1011,35 +1049,6 @@ static PyObject *torrent_get_file_info(PyObject *self, PyObject *args) return ret; }; -static PyObject *torrent_set_filter_out(PyObject *self, PyObject *args) -{ - python_long unique_ID; - PyObject *filter_out_object; - if (!PyArg_ParseTuple(args, "iO", &unique_ID, &filter_out_object)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_t &t = M_torrents->at(index); - long num_files = t.handle.get_torrent_info().num_files(); - assert(PyList_Size(filter_out_object) == num_files); - - filter_out_t filter_out(num_files); - - for (long i = 0; i < num_files; i++) - { - filter_out.at(i) = - PyInt_AsLong(PyList_GetItem(filter_out_object, i)); - }; - - t.handle.filter_files(filter_out); - - Py_INCREF(Py_None); return Py_None; -} - - /*static PyObject *torrent_get_unique_IDs(PyObject *self, PyObject *args) { PyObject *ret = PyTuple_New(M_torrents.size()); @@ -1521,7 +1530,7 @@ static PyMethodDef deluge_core_methods[] = {"get_session_info", torrent_get_session_info, METH_VARARGS, "."}, {"get_peer_info", torrent_get_peer_info, METH_VARARGS, "."}, {"get_file_info", torrent_get_file_info, METH_VARARGS, "."}, - {"set_filter_out", torrent_set_filter_out, METH_VARARGS, "."}, + {"dump_file_info", torrent_dump_file_info, METH_VARARGS, "."}, {"constants", torrent_constants, METH_VARARGS, "."}, {"start_DHT", torrent_start_DHT, METH_VARARGS, "."}, {"stop_DHT", torrent_stop_DHT, METH_VARARGS, "."}, diff --git a/src/dialogs.py b/src/dialogs.py index 36c039273..39729e65c 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -202,22 +202,18 @@ class PreferencesDlg: self.glade.get_widget('txt_tray_passwd').set_sensitive(value) class FilesDlg: - def __init__(self, manager, unique_id): + def __init__(self, dumped_torrent): self.glade = gtk.glade.XML(common.get_glade_file("files_dialog.glade"), domain='deluge') self.dialog = self.glade.get_widget("file_dialog") self.dialog.set_icon_from_file(common.get_pixmap("deluge32.png")) self.file_view = self.glade.get_widget("file_view") - self.manager = manager - self.unique_id = unique_id - self.files_manager = files.FilesManager(manager, False) + self.files_manager = files.FilesDialogManager(dumped_torrent) self.files_manager.build_file_view(self.file_view) + self.files_manager.prepare_file_store() def show(self): - self.files_manager.use_unique_id(self.unique_id) - self.files_manager.prepare_store() - #clear private setting self.glade.get_widget("chk_setpriv").set_active(False) @@ -229,6 +225,9 @@ class FilesDlg: self.manager.set_priv(self.unique_id, True) return r + + def get_priorities(self): + return self.files_manager.get_priorities() class PluginDlg: def __init__(self, plugins): diff --git a/src/files.py b/src/files.py index b133cc60e..777cc363f 100644 --- a/src/files.py +++ b/src/files.py @@ -36,10 +36,10 @@ import common from itertools import izip import gobject -class FilesManager: - def __init__(self, manager, is_file_tab): - self.manager = manager - self.file_glade = gtk.glade.XML(common.get_glade_file("file_tab_menu.glade"), domain='deluge') +class FilesBaseManager(object): + def __init__(self, file_store): + self.file_glade = gtk.glade.XML(common.get_glade_file("file_tab_menu.glade"), + domain='deluge') self.file_menu = self.file_glade.get_widget("file_tab_menu") self.file_glade.signal_autoconnect({ "select_all": self.file_select_all, @@ -47,33 +47,25 @@ class FilesManager: "check_selected": self.file_check_selected, "uncheck_selected": self.file_uncheck_selected, }) - self.file_unique_id = -1 - self.is_file_tab = is_file_tab - # Stores file path -> gtk.TreeIter's iter mapping for quick look up - # in self.update_torrent_info_widget - self.file_store_dict = {} - if self.is_file_tab: - self.file_store = gtk.ListStore(bool, str, gobject.TYPE_UINT64, float) - else: - self.file_store = gtk.ListStore(bool, str, gobject.TYPE_UINT64) + + self.file_store = file_store + # We need file_store_sorted so original file_store keeps unchanged + # when file_view is sorted. And from file_store we have to pass + # files priorities to manager.prioritize_files() in the exact same + # order as we get files from manager.get_torrent_file_info() self.file_store_sorted = gtk.TreeModelSort(self.file_store) - def use_unique_id(self, unique_id): - self.file_unique_id = unique_id - def build_file_view(self, file_view): self.file_view = file_view - def percent(column, cell, model, iter, data): - percent = float(model.get_value(iter, data)) - percent_str = "%.2f%%"%percent - cell.set_property("text", percent_str) self.file_selected = [] - self.toggle_column = dgtk.add_toggle_column(self.file_view, _("Download"), 0, toggled_signal=self.file_toggled) + + self.toggle_column = dgtk.add_toggle_column(self.file_view, + _("Priority"), 0, + toggled_signal=self.file_toggled) self.filename_column = dgtk.add_text_column(self.file_view, _("Filename"), 1) self.filename_column.set_expand(True) self.size_column = dgtk.add_func_column(self.file_view, _("Size"), dgtk.cell_data_size, 2) - if self.is_file_tab: - dgtk.add_func_column(self.file_view, _("Progress"), percent, 3) + self.file_view.set_model(self.file_store_sorted) self.file_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.file_view.get_selection().set_select_function(self.file_clicked) @@ -82,33 +74,10 @@ class FilesManager: def clear_file_store(self): self.file_store.clear() self.file_store_dict = {} - - def prepare_store(self): - if not self.file_store_dict: - all_files = self.manager.get_torrent_file_info(self.file_unique_id) - file_filter = self.manager.get_file_filter(self.file_unique_id) - if file_filter is None: - file_filter = [False] * len(all_files) - if self.is_file_tab: - for file, filt in izip(all_files, file_filter): - iter = self.file_store.append([not filt, file['path'], - file['size'], - round(file['progress'], 2)]) - self.file_store_dict[file['path']] = iter - else: - for file, filt in izip(all_files, file_filter): - iter = self.file_store.append([not filt, file['path'], - file['size']]) - self.file_store_dict[file['path']] = iter - - - def update_store(self): - new_file_info = self.manager.get_torrent_file_info(self.file_unique_id) - for file in new_file_info: - iter = self.file_store_dict[file['path']] - if self.file_store.get_value(iter, 3) != round(file['progress'], 2): - self.file_store.set(iter, 3, file['progress']) - + + def prepare_file_store(self): + pass + def file_select_all(self, widget): self.file_view.get_selection().select_all() @@ -117,11 +86,11 @@ class FilesManager: def file_check_selected(self, widget): self.file_view.get_selection().selected_foreach(self.file_toggle_selected, True) - self.file_toggled_update_filter() + self.file_toggled_update_priorities() def file_uncheck_selected(self, widget): self.file_view.get_selection().selected_foreach(self.file_toggle_selected, False) - self.file_toggled_update_filter() + self.file_toggled_update_priorities() def file_clicked(self, path): return not self.file_selected @@ -137,22 +106,97 @@ class FilesManager: def file_toggle_selected(self, treemodel, path, selected_iter, value): child_iter = self.file_store_sorted.convert_iter_to_child_iter(None, selected_iter) - self.file_store_sorted.get_model().set_value(child_iter, 0, value) - + if value: + new_value = 1 + else: + new_value = 0 + self.file_store_sorted.get_model().set_value(child_iter, 0, new_value) + def file_toggled(self, renderer, path): self.file_selected = True - file_iter = self.file_store_sorted.get_iter_from_string(path) + value = not renderer.get_active() + if value: + new_value = 1 + else: + new_value = 0 + + file_iter = self.file_store_sorted.get_iter_from_string(path) selection = self.file_view.get_selection() if selection.iter_is_selected(file_iter): selection.selected_foreach(self.file_toggle_selected, value) else: child_iter = self.file_store_sorted.convert_iter_to_child_iter( None, file_iter) - self.file_store_sorted.get_model().set_value(child_iter, 0, value) + self.file_store_sorted.get_model().set_value(child_iter, 0, + new_value) + self.file_toggled_update_priorities() + + def file_toggled_update_priorities(self): + pass + +class FilesTabManager(FilesBaseManager): + def __init__(self, manager): + file_store = gtk.ListStore(int, str, gobject.TYPE_UINT64, float) - self.file_toggled_update_filter() + super(FilesTabManager, self).__init__(file_store) + + self.manager = manager + self.file_unique_id = None - def file_toggled_update_filter(self): - file_filter = [not x[0] for x in self.file_store] - self.manager.set_file_filter(self.file_unique_id, file_filter) + # Stores file path -> gtk.TreeIter's iter mapping for quick look up + # in self.update_file_store() + self.file_store_dict = {} + + def build_file_view(self, file_view): + super(FilesTabManager, self).build_file_view(file_view) + + def percent(column, cell, model, iter, data): + percent = float(model.get_value(iter, data)) + percent_str = "%.2f%%"%percent + cell.set_property("text", percent_str) + dgtk.add_func_column(file_view, _("Progress"), percent, 3) + + def set_unique_id(self, unique_id): + self.file_unique_id = unique_id + + def prepare_file_store(self): + if not self.file_store_dict: + all_files = self.manager.get_torrent_file_info(self.file_unique_id) + file_priorities = self.manager.get_priorities(self.file_unique_id) + for file, priority in izip(all_files, file_priorities): + iter = self.file_store.append([priority, file['path'], + file['size'], + round(file['progress'], 2)]) + self.file_store_dict[file['path']] = iter + + def update_file_store(self): + new_file_info = self.manager.get_torrent_file_info(self.file_unique_id) + for file in new_file_info: + iter = self.file_store_dict[file['path']] + if self.file_store.get_value(iter, 3) != round(file['progress'], 2): + self.file_store.set(iter, 3, file['progress']) + + def file_toggled_update_priorities(self): + file_priorities = [] + for x in self.file_store: + file_priorities.append(x[0]) + self.manager.prioritize_files(self.file_unique_id, file_priorities) + +class FilesDialogManager(FilesBaseManager): + def __init__(self, dumped_torrent): + file_store = gtk.ListStore(int, str, gobject.TYPE_UINT64) + super(FilesDialogManager, self).__init__(file_store) + + self.dumped_torrent = dumped_torrent + + def prepare_file_store(self): + for file in self.dumped_torrent: + self.file_store.append([1, file['path'], file['size']]) + + def get_priorities(self): + file_priorities = [] + for x in self.file_store: + file_priorities.append(x[0]) + + return file_priorities diff --git a/src/interface.py b/src/interface.py index 6f95b7e9e..f163f6f39 100644 --- a/src/interface.py +++ b/src/interface.py @@ -63,7 +63,7 @@ class DelugeGTK: #Start the Deluge Manager: self.manager = core.Manager(common.CLIENT_CODE, common.CLIENT_VERSION, '%s %s'%(common.PROGRAM_NAME, common.PROGRAM_VERSION), common.CONFIG_DIR) - self.files_for_tab = files.FilesManager(self.manager, True) + self.files_tab = files.FilesTabManager(self.manager) self.plugins = plugins.PluginManager(self.manager, self) self.plugins.add_plugin_dir(common.PLUGIN_DIR) if os.path.isdir(os.path.join(common.CONFIG_DIR , 'plugins')): @@ -636,10 +636,10 @@ class DelugeGTK: self.peer_store_dict = {} def build_file_tab(self): - self.files_for_tab.build_file_view(self.wtree.get_widget("file_view")) + self.files_tab.build_file_view(self.wtree.get_widget("file_view")) def clear_file_store(self): - self.files_for_tab.clear_file_store() + self.files_tab.clear_file_store() def show_about_dialog(self, arg=None): dialogs.show_about_dialog() @@ -1019,9 +1019,9 @@ class DelugeGTK: elif page_num == 2: # Files # Fill self.file_store with files only once and only when we click to # Files tab or it's already open - self.files_for_tab.use_unique_id(unique_id) - self.files_for_tab.prepare_store() - self.files_for_tab.update_store() + self.files_tab.set_unique_id(unique_id) + self.files_tab.prepare_file_store() + self.files_tab.update_file_store() def calc_share_ratio(self, unique_id, torrent_state): @@ -1083,7 +1083,20 @@ class DelugeGTK: return try: - unique_id = self.manager.add_torrent(torrent, path, self.config.get('use_compact_storage')) + dumped_torrent = self.manager.dump_torrent_file_info(torrent) + if self.config.get('enable_files_dialog') and \ + len(dumped_torrent) > 1: + files_dialog = dialogs.FilesDlg(dumped_torrent) + if files_dialog.show() == 1: + unique_id = self.manager.add_torrent(torrent, path, + self.config.get('use_compact_storage')) + self.manager.prioritize_files(unique_id, + files_dialog.get_priorities()) + else: + return + else: + unique_id = self.manager.add_torrent(torrent, path, + self.config.get('use_compact_storage')) except core.InvalidEncodingError, e: print "InvalidEncodingError", e dialogs.show_popup_warning(self.window, _("An error occured while trying to add the torrent. It's possible your .torrent file is corrupted.")) @@ -1095,19 +1108,8 @@ class DelugeGTK: dialogs.show_popup_warning(self.window, _("There is not enough free disk space to complete your download.") + "\n" + \ _("Space Needed:") + " " + nice_need + "\n" + \ _("Available Space:") + " " + nice_free) - else: - num_files = len(self.manager.get_torrent_file_info(unique_id)) - if self.config.get('enable_files_dialog') and num_files > 1: - self.manager.set_user_pause(unique_id, True) - - files_dialog = dialogs.FilesDlg(self.manager, unique_id) - if files_dialog.show() == 1: - self.manager.set_user_pause(unique_id, False) - self.torrent_model_append(unique_id) - else: - self.manager.remove_torrent(unique_id, True, True) - else: - self.torrent_model_append(unique_id) + + self.torrent_model_append(unique_id) def launchpad(self, obj=None): common.open_url_in_browser('https://translations.launchpad.net/deluge/trunk/+pots/deluge')