def __init__(self, gtkui, logger, subscription_data, rssfeeds, email_messages, cookies): self.gtkUI = gtkui self.rssfeeds = rssfeeds self.email_messages = email_messages self.rssfeeds_dict = {} self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log) # This is to make testing of the GUI possible (unit tests) self.method_perform_rssfeed_selection = self.perform_rssfeed_selection super().__init__(editing=True if len(self.subscription_data.get("rssfeed_key", "")) != 0 else False, new_subscription="key" not in subscription_data)
def __init__(self, config, logger): self.yarss_config = config self.rssfeed_timers = {} self.run_queue = RSSFeedRunQueue() self.log = logger self.rssfeedhandler = RSSFeedHandler(logger) self.torrent_handler = TorrentHandler(logger) self.add_torrent_func = self.torrent_handler.add_torrents # To make it possible to disable adding torrents in testing
def __init__(self, gtkUI, logger, subscription_data, rssfeeds, move_completed_list, download_location_list, email_messages, cookies): self.gtkUI = gtkUI self.rssfeeds = rssfeeds self.move_completed_list = move_completed_list self.download_location_list = download_location_list self.email_messages = email_messages self.rssfeeds_dict = {} self.matching_store = None self.icon_matching = gtk.gdk.pixbuf_new_from_file( get_resource("match.png")) self.icon_nonmatching = gtk.gdk.pixbuf_new_from_file( get_resource("no_match.png")) self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log)
def __init__(self, config, logger): self.yarss_config = config self.rssfeed_timers = {} self.run_queue = RSSFeedRunQueue() self.log = logger self.rssfeedhandler = RSSFeedHandler(logger) self.torrent_handler = TorrentHandler(logger) # To make it possible to disable adding torrents in testing self.add_torrents_func = self.torrent_handler.add_torrents
def test_add_torrents(self): handler = TorrentHandler(self.log) from yarss2.rssfeed_handling import RSSFeedHandler self.rssfeedhandler = RSSFeedHandler(self.log) # Override method download_torrent_file handler.download_torrent_file = test_component.download_torrent_file filename = yarss2.util.common.get_resource( "FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") test_component.use_filedump = read_file(filename) config = get_test_config_dict() # 0 is the rssfeed key match_result = self.rssfeedhandler.fetch_feed_torrents(config, "0") matching_torrents = match_result["matching_torrents"] saved_subscriptions = [] def save_subscription_func(subscription_data): saved_subscriptions.append(subscription_data) handler.add_torrents(save_subscription_func, matching_torrents, self.config.get_config()) self.assertEquals(len(saved_subscriptions), 3) handler.use_filedump = None
def __init__(self, gtkUI, logger, subscription_data, rssfeeds, move_completed_list, download_location_list, email_messages, cookies): self.gtkUI = gtkUI self.rssfeeds = rssfeeds self.move_completed_list = move_completed_list self.download_location_list = download_location_list self.email_messages = email_messages self.rssfeeds_dict = {} self.matching_store = None self.icon_matching = gtk.gdk.pixbuf_new_from_file(get_resource("match.png")) self.icon_nonmatching = gtk.gdk.pixbuf_new_from_file(get_resource("no_match.png")) self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log)
def test_add_torrents(self): handler = TorrentHandler(self.log) from yarss2.rssfeed_handling import RSSFeedHandler self.rssfeedhandler = RSSFeedHandler(self.log) # Override method download_torrent_file handler.download_torrent_file = test_component.download_torrent_file filename = yarss2.util.common.get_resource("FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") test_component.use_filedump = read_file(filename) config = get_test_config_dict() # 0 is the rssfeed key match_result = self.rssfeedhandler.fetch_feed_torrents(config, "0") matching_torrents = match_result["matching_torrents"] saved_subscriptions = [] def save_subscription_func(subscription_data): saved_subscriptions.append(subscription_data) handler.add_torrents(save_subscription_func, matching_torrents, self.config.get_config()) self.assertEquals(len(saved_subscriptions), 1) handler.use_filedump = None
class DialogSubscription(DialogSubscriptionGUI): def __init__(self, gtkui, logger, subscription_data, rssfeeds, email_messages, cookies): self.gtkUI = gtkui self.rssfeeds = rssfeeds self.email_messages = email_messages self.rssfeeds_dict = {} self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log) # This is to make testing of the GUI possible (unit tests) self.method_perform_rssfeed_selection = self.perform_rssfeed_selection super().__init__(editing=True if len(self.subscription_data.get("rssfeed_key", "")) != 0 else False, new_subscription="key" not in subscription_data) def show(self): self.setup(show=True) def setup(self, show=False): """ Called by tests where show must be False """ self.setup_gui() if show: self.show_dialog() self.load_subscription_data() def button_add_torrent_clicked(self, torrent_link, use_settings): # Save current data to dict self.store_subscription_data() self.add_torrent(torrent_link, self.subscription_data if use_settings else None) def add_torrent(self, torrent_link, subscription_data): if torrent_link is None: return def add_torrent_callback(torrent_download): torrent_download = TorrentDownload(torrent_download) if torrent_download.success: return True if torrent_download.filedump is None: return readable_body = http.clean_html_body(torrent_download.filedump) textbuffer = self.get_object("textview_messages").get_buffer() textbuffer.set_text(readable_body) self.get_object("notebook_lower").set_current_page(1) # Quick hack to make sure the message is visible to the user. hpaned = self.get_object("hpaned_matching") max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.3)) return False d = self.gtkUI.add_torrent(torrent_link, subscription_data) d.addCallback(add_torrent_callback) return d ################## # RSS Matching ################## def perform_rssfeed_selection(self): rssfeed_key = self.get_current_rssfeed_key() deferred = self.get_and_update_rssfeed_results(rssfeed_key) deferred.addCallback(self.update_matching_view_with_rssfeed_results) return deferred def perform_search(self): match_option_dict = self.get_search_settings() self.perform_matching_and_update_liststore(match_option_dict) # Insert treeview self.set_matching_window_child(self.treeview) def perform_matching_and_update_liststore(self, match_option_dict): """ Updates the rssfeed_dict with matching according to options in match_option_dict Also updates the GUI """ if not self.rssfeeds_dict and not match_option_dict["custom_text_lines"]: return try: matchings, message = self.rssfeedhandler.update_rssfeeds_dict_matching(self.rssfeeds_dict, options=match_option_dict) self.update_matching_feeds_store(self.rssfeeds_dict, regex_matching=True) label_status = self.get_object("label_status") if message: label_status.set_text(str(message)) label_count = self.get_object("label_torrent_count") label_count.set_text("Matching: %d/%d" % (len(matchings.keys()), len(self.rssfeeds_dict.keys()))) except Exception: import traceback exc_str = traceback.format_exc() self.log.warn("Error when matching:" + exc_str, gtkui=True) def get_and_update_rssfeed_results(self, rssfeed_key): """ Returns: Deferred: """ site_cookies_dict = http.get_matching_cookies_dict(self.cookies, self.rssfeeds[rssfeed_key]["site"]) user_agent = get_user_agent(rssfeed_data=self.rssfeeds[rssfeed_key]) return self.get_rssfeed_parsed(self.rssfeeds[rssfeed_key], site_cookies_dict=site_cookies_dict, user_agent=user_agent) def get_rssfeed_parsed(self, rssfeed_data, site_cookies_dict=None, user_agent=None): return client.yarss2.get_rssfeed_parsed(rssfeed_data, site_cookies_dict=site_cookies_dict, user_agent=user_agent) def update_matching_view_with_rssfeed_results(self, rssfeeds_parsed): """Callback function, called when 'get_and_update_rssfeed_results' has finished. Replaces the content of the matching window. If valid items were retrieved, update the matching according to current settings. If no valid items, show the result as text instead. """ if rssfeeds_parsed is None: return # Window has been closed in the meantime if not self.dialog.get_visible(): return # Reset status to not display an older status label_status = self.get_object("label_status") label_text = "" # Bozo Exception (feedbparser error), still elements might have been successfully parsed if "bozo_exception" in rssfeeds_parsed: exception = rssfeeds_parsed["bozo_exception"] label_text = str(exception) else: message_text = "TTL value: %s" % (("%s min" % rssfeeds_parsed["ttl"]) if rssfeeds_parsed.get("ttl", None) is not None else "Not available") # This can sometimes be None for some effing reason if label_status: label_text = message_text try: # label_status is sometimes None, for som effin reason label_status.set_text(label_text) except AttributeError: self.log.warn("label_status is None", gtkui=False) pass # Failed to retrive items. Show content as text if "items" not in rssfeeds_parsed: self.show_result_as_text(rssfeeds_parsed["raw_result"]) return self.rssfeeds_dict = rssfeeds_parsed["items"] # Update the matching according to the current settings self.perform_search() def show_result_as_text(self, raw_rssfeed): """ When failing to parse the RSS Feed, this will show the result in a text window with HTML tags stripped away. """ result = get_viewable_result(raw_rssfeed) self.set_matching_result_in_textview(result) def save_subscription_data(self): if not self.store_subscription_data(): return False # Call save method in gtui. Saves to core self.gtkUI.save_subscription(self.subscription_data) return True def store_subscription_data(self): """ Get the subscription data from the GUI and store in self.subscription_data """ rssfeed_key = self.get_current_rssfeed_key() # RSS feed is mandatory if not rssfeed_key: self.show_rssfeed_mandatory_message() return False subscription_data = self.get_subscription_data() self.subscription_data.update(subscription_data) return True def load_subscription_data(self): self.load_basic_fields_data(self.subscription_data) self.load_rssfeed_combobox_data(self.subscription_data, self.rssfeeds) self.load_notifications_list_data(self.email_messages, self.subscription_data) self.load_path_choosers_data() self.load_timestamp(self.subscription_data) self.get_labels_d.addCallback(self.load_labels, self.subscription_data) def load_path_choosers_data(self): self.core_keys = [ "download_location", "move_completed_path", "move_completed_paths_list", "download_location_paths_list", ] client.core.get_config_values(self.core_keys).addCallback(self.set_path_chooses_data)
class DialogSubscription(): def __init__(self, gtkUI, logger, subscription_data, rssfeeds, move_completed_list, download_location_list, email_messages, cookies): self.gtkUI = gtkUI self.rssfeeds = rssfeeds self.move_completed_list = move_completed_list self.download_location_list = download_location_list self.email_messages = email_messages self.rssfeeds_dict = {} self.matching_store = None self.icon_matching = gtk.gdk.pixbuf_new_from_file( get_resource("match.png")) self.icon_nonmatching = gtk.gdk.pixbuf_new_from_file( get_resource("no_match.png")) self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log) def setup(self): self.glade = gtk.glade.XML(get_resource("dialog_subscription.glade")) self.glade.signal_autoconnect({ "on_txt_regex_include_changed": self.on_txt_regex_changed, "on_txt_regex_exclude_changed": self.on_txt_regex_changed, "on_button_cancel_clicked": self.on_button_cancel_clicked, "on_button_save_clicked": self.on_button_save_subscription_clicked, "on_button_add_notication_clicked": self.on_button_add_notication_clicked, "on_button_remove_notication_clicked": self.on_button_remove_notication_clicked, "on_rssfeed_selected": self.on_rssfeed_selected, "on_panel_matching_move_handle": self.on_panel_matching_move_handle, "on_button_fetch_clicked": self.on_rssfeed_selected, "on_button_last_matched_reset_clicked": self.on_button_last_matched_reset_clicked, "on_button_last_matched_now_clicked": self.on_button_last_matched_now_clicked, }) # This is to make testing of the GUI possible (tests/) self.method_perform_rssfeed_selection = self.perform_rssfeed_selection self.dialog = self.glade.get_widget("window_subscription") self.setup_rssfeed_combobox() self.setup_move_completed_combobox() self.setup_download_location_combobox() self.setup_messages_combobox() self.setup_messages_list() self.treeview = self.create_matching_tree() self.load_subscription_data() def show(self): self.setup() self.dialog.set_transient_for(component.get("Preferences").pref_dialog) self.dialog.show() ######################################## ## GUI creation ######################################## def setup_move_completed_combobox(self): # Create liststore model to replace default model self.move_completed_store = gtk.ListStore(str) combobox_move_completed = self.glade.get_widget( "combobox_move_completed") combobox_move_completed.set_model(self.move_completed_store) def setup_download_location_combobox(self): # Create liststore model to replace default model self.download_location_store = gtk.ListStore(str) combobox_download_location = self.glade.get_widget( "combobox_download_location") combobox_download_location.set_model(self.download_location_store) def setup_rssfeed_combobox(self): rssfeeds_combobox = self.glade.get_widget("combobox_rssfeeds") rendererName = gtk.CellRendererText() rendererSite = gtk.CellRendererText() rssfeeds_combobox.pack_start(rendererName, False) rssfeeds_combobox.pack_end(rendererSite, False) rssfeeds_combobox.add_attribute(rendererName, "text", 1) rssfeeds_combobox.add_attribute(rendererSite, "text", 2) # key, name, site url self.rssfeeds_store = gtk.ListStore(str, str, str) rssfeeds_combobox.set_model(self.rssfeeds_store) def setup_messages_combobox(self): messages_combobox = self.glade.get_widget("combobox_messages") rendererText = gtk.CellRendererText() messages_combobox.pack_start(rendererText, False) messages_combobox.add_attribute(rendererText, "text", 1) # key, name self.messages_combo_store = gtk.ListStore(str, str) messages_combobox.set_model(self.messages_combo_store) def create_matching_tree(self): # Matches, Title, Published, torrent link, CustomAttribute for PangoCellrenderer self.matching_store = gtk.ListStore(bool, str, str, str, CustomAttribute) self.matching_treeView = gtk.TreeView(self.matching_store) #self.matching_treeView.connect("cursor-changed", self.on_subscription_listitem_activated) #self.matching_treeView.connect("row-activated", self.on_button_edit_subscription_clicked) self.matching_treeView.set_rules_hint(True) self.matching_treeView.connect('button-press-event', self.on_matches_list_button_press_event) def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 0) == True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Matches", renderer) column.set_cell_data_func(renderer, cell_data_func) column.set_sort_column_id(0) self.matching_treeView.append_column(column) cellrenderer = CellrendererPango() column = gtk.TreeViewColumn("Title", cellrenderer, text=1) column.add_attribute(cellrenderer, "custom", 4) column.set_sort_column_id(1) column.set_resizable(True) column.set_expand(True) self.matching_treeView.append_column(column) cellrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Published", cellrenderer, text=2) column.set_sort_column_id(2) self.matching_treeView.append_column(column) col = gtk.TreeViewColumn() col.set_visible(False) self.matching_treeView.append_column(col) self.list_popup_menu = gtk.Menu() menuitem1 = gtk.MenuItem("Add this torrent now") menuitem1.connect("activate", self.on_button_add_torrent_clicked) menuitem2 = gtk.MenuItem("Copy text to clipboard") menuitem2.connect("activate", self.on_button_copy_to_clipboard) self.list_popup_menu.append(menuitem1) self.list_popup_menu.append(menuitem2) return self.matching_treeView def on_matches_list_button_press_event(self, treeview, event): """Shows popup on selected row when right clicking""" if event.button == 3: x = int(event.x) y = int(event.y) time = event.time pthinfo = treeview.get_path_at_pos(x, y) it = self.matching_store.get_iter(pthinfo[0]) link = self.matching_store.get_value(it, 3) if link is None: return False if pthinfo is not None: path, col, cellx, celly = pthinfo treeview.grab_focus() treeview.set_cursor(path, col, 0) self.list_popup_menu.popup(None, None, None, event.button, time, data=path) self.list_popup_menu.show_all() return True def on_button_add_torrent_clicked(self, menuitem): torrent_link = get_value_in_selected_row(self.matching_treeView, self.matching_store, column_index=3) if torrent_link is not None: self.gtkUI.add_torrent(torrent_link) def on_button_copy_to_clipboard(self, menuitem): torrent_title = get_value_in_selected_row(self.matching_treeView, self.matching_store, column_index=1) if torrent_title is not None: gtk.clipboard_get().set_text(torrent_title) def setup_messages_list(self): # message_key, message_title, active, torrent_added, torrent_completed, self.messages_list_store = gtk.ListStore(str, str, bool, bool, bool) self.messages_treeview = gtk.TreeView(self.messages_list_store) self.messages_treeview.connect("row-activated", self.on_notification_list_clicked) self.columns_dict = {} def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 2) == True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Message Active", renderer) column.set_cell_data_func(renderer, cell_data_func) self.messages_treeview.append_column(column) rendererText = gtk.CellRendererText() column = gtk.TreeViewColumn("Message Title", rendererText, text=1) self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent added", renderer, active=3) self.columns_dict["3"] = column self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent completed", renderer, active=4) self.columns_dict["4"] = column viewport = self.glade.get_widget("viewport_email_notifications") viewport.add(self.messages_treeview) viewport.show_all() def on_panel_matching_move_handle(self, paned, scrolltype): """Supposed to handle resizing of the splitpane when the Window size is changed Haven't found a good way to handle this""" textview = self.glade.get_widget("textview_custom_text") hpaned = self.glade.get_widget("hpaned_matching") #pos = hpaned.compute_position(400, -1, 100) #print "Computed pos:", pos #width = textview.get_style().get_font().width("w") #print "width:", width #print "position:", paned.get_position() #print "Scrolltype:", scrolltype ######################################## ## GUI Update / Callbacks ######################################## def get_selected_combobox_key(self, combobox): """Get the key of the currently selected item in the combobox""" # Get selected item active = combobox.get_active() model = combobox.get_model() iterator = combobox.get_active_iter() if iterator is None or model.get_value(iterator, 0) == -1: return None return model.get_value(iterator, 0) ### RSS Matching ################### ## Callbacks def on_rssfeed_selected(self, combobox): """Callback from glade when rss combobox is selected. Gets the results for the RSS Feed Runs the code that handles the parsing in a thread with Twisted, to avoid the dialog waiting on startup. """ self.method_perform_rssfeed_selection() def perform_rssfeed_selection(self): rssfeed_key = self.get_selected_combobox_key( self.glade.get_widget("combobox_rssfeeds")) defered = threads.deferToThread(self.get_and_update_rssfeed_results, rssfeed_key) defered.addCallback(self.update_matching_view_with_rssfeed_results) return defered def on_txt_regex_changed(self, text_field): """ Callback for when Enter is pressed in either of the regex fields """ self.perform_search() def get_custom_text_lines(self): textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() lines = [] text = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) text = string_to_unicode(text) for line in text.splitlines(): lines.append(line.strip()) return lines def get_search_settings(self): regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case = self.glade.get_widget( "regex_include_case").get_active() regex_exclude_case = self.glade.get_widget( "regex_exclude_case").get_active() match_option_dict = {} match_option_dict["regex_include"] = regex_include if ( len(regex_include) > 0) else None match_option_dict["regex_exclude"] = regex_exclude if ( len(regex_exclude) > 0) else None match_option_dict["regex_include_ignorecase"] = not regex_include_case match_option_dict["regex_exclude_ignorecase"] = not regex_exclude_case match_option_dict["custom_text_lines"] = self.get_custom_text_lines() return match_option_dict def perform_search(self): match_option_dict = self.get_search_settings() self.perform_matching_and_update_liststore(match_option_dict) # Insert treeview self.set_matching_window_child(self.treeview) ## Perform matching and update liststore (which updates GUI) def perform_matching_and_update_liststore(self, match_option_dict): """Updates the rssfeed_dict with matching according to options in match_option_dict Also updates the GUI """ if not self.rssfeeds_dict and not match_option_dict[ "custom_text_lines"]: return try: matchings, message = self.rssfeedhandler.update_rssfeeds_dict_matching( self.rssfeeds_dict, options=match_option_dict) self.update_matching_feeds_store(self.treeview, self.matching_store, self.rssfeeds_dict, regex_matching=True) label_status = self.glade.get_widget("label_status") if message: label_status.set_text(str(message)) label_count = self.glade.get_widget("label_torrent_count") label_count.set_text( "Torrent count: %d, Matches: %d" % (len(self.rssfeeds_dict.keys()), len(matchings.keys()))) except Exception as (v): import traceback exc_str = traceback.format_exc(v) self.log.warn("Error when matching:" + exc_str) def update_matching_feeds_store(self, treeview, store, rssfeeds_dict, regex_matching=False): """Updates the liststore of matching torrents. This updates the GUI""" store.clear() for key in sorted(rssfeeds_dict.keys()): customAttributes = CustomAttribute() if regex_matching: attr = {} if rssfeeds_dict[key].has_key("regex_include_match"): attr["regex_include_match"] = rssfeeds_dict[key][ "regex_include_match"] if rssfeeds_dict[key].has_key("regex_exclude_match"): attr["regex_exclude_match"] = rssfeeds_dict[key][ "regex_exclude_match"] customAttributes = CustomAttribute(attributes_dict=attr) updated = rssfeeds_dict[key]['updated'] store.append([ rssfeeds_dict[key]['matches'], rssfeeds_dict[key]['title'], updated if updated else "Not available", rssfeeds_dict[key]['link'], customAttributes ]) def get_and_update_rssfeed_results(self, rssfeed_key): rssfeeds_parsed = self.rssfeedhandler.get_rssfeed_parsed( self.rssfeeds[rssfeed_key], cookies=self.cookies) return rssfeeds_parsed def update_matching_view_with_rssfeed_results(self, rssfeeds_parsed): """Callback function, called when 'get_and_update_rssfeed_results' has finished. Replaces the content of the matching window. If valid items were retrieved, update the matching according to current settings. If no valid items, show the result as text instead. """ if rssfeeds_parsed is None: return # Reset status to not display an older status label_status = self.glade.get_widget("label_status") label_text = "" # Bozo Exception (feedbparser error), still elements might have been successfully parsed if rssfeeds_parsed.has_key("bozo_exception"): exception = rssfeeds_parsed["bozo_exception"] label_text = str(exception) else: message_text = "TTL value: %s" % (("%s min" % rssfeeds_parsed["ttl"]) \ if rssfeeds_parsed.has_key("ttl") \ else "Not available") # This can sometimes be None for some effing reason if label_status: label_text = message_text try: # label_status is sometimes None, for som effin reason label_status.set_text(label_text) except: self.log.warn("label_status is None", gtkui=False) pass # Failed to retrive items. Show content as text if not rssfeeds_parsed.has_key("items"): self.show_result_as_text(rssfeeds_parsed["raw_result"]) return self.rssfeeds_dict = rssfeeds_parsed["items"] # Update the matching according to the current settings self.perform_search() def show_result_as_text(self, rssfeeds_parsed): """When failing to parse the RSS Feed, this will show the result in a text window with HTML tags stripped away. """ result = self.get_viewable_result(rssfeeds_parsed) textview = gtk.TextView() textbuffer = textview.get_buffer() textview.show() textbuffer.set_text(result) # Insert widget self.set_matching_window_child(textview) def get_viewable_result(self, rssfeed_parsed): if not rssfeed_parsed["feed"].has_key("summary"): return "" cleaned = rssfeed_parsed["feed"]["summary"] s = HTMLStripper() s.feed(cleaned) return s.get_data() def set_matching_window_child(self, widget): """Insert the widget into the matching window""" matching_window = self.glade.get_widget("matching_window_upper") if matching_window.get_child(): matching_window.remove(matching_window.get_child()) matching_window.add(widget) # Quick hack to make sure the list of torrents are visible to the user. hpaned = self.glade.get_widget("hpaned_matching") if hpaned.get_position() == 0: max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.75)) matching_window.show_all() ### Notifications ################### def on_notification_list_clicked(self, Event=None, a=None, col=None): """Callback for when the checkboxes (or actually just the row) in notification list is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if not row_iter or not col: return for column in self.columns_dict.keys(): if self.columns_dict[column] == col: column = int(column) val = self.messages_list_store.get_value(row_iter, column) self.messages_list_store.set_value(row_iter, column, not val) return def on_button_add_notication_clicked(self, button): combobox = self.glade.get_widget("combobox_messages") key = self.get_selected_combobox_key(combobox) if key is None: return # Current notications message_dict = self.get_current_notifications() for c_key in message_dict.keys(): # This message is already in the notifications list if c_key == key: return self.messages_list_store.append([ key, self.email_messages[key]["name"], self.email_messages[key]["active"], True, False ]) def get_current_notifications(self): """ Retrieves the notifications from the notifications list""" notifications = {} row_iter = self.messages_list_store.get_iter_first() while row_iter is not None: key = self.messages_list_store.get_value(row_iter, 0) active = self.messages_list_store.get_value(row_iter, 2) on_added = self.messages_list_store.get_value(row_iter, 3) on_completed = self.messages_list_store.get_value(row_iter, 4) notifications[key] = { "on_torrent_added": on_added, "on_torrent_completed": on_completed } # Next row row_iter = self.messages_list_store.iter_next(row_iter) return notifications def message_checkbox_toggled_cb(self, cell, path, model): """Called when the checkboxes in the notications list are clicked""" for column in self.columns_dict.keys(): if self.columns_dict[column] == cell: column = int(column) row_iter = self.messages_list_store.get_iter(path) reversed_value = not self.messages_list_store.get_value( row_iter, column) self.messages_list_store.set_value(row_iter, column, reversed_value) def on_button_remove_notication_clicked(self, button): """Callback for when remove button for notifications is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if row_iter: self.messages_list_store.remove(row_iter) ### Options ################### def on_button_last_matched_reset_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text("") def on_button_last_matched_now_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text( get_current_date().isoformat()) ## Save / Close ################### def on_button_save_subscription_clicked(self, Event=None, a=None, col=None): if self.save_subsription_data(): self.dialog.destroy() def save_subsription_data(self): name = self.glade.get_widget("txt_name").get_text() regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case_sensitive = self.glade.get_widget( "regex_include_case").get_active() regex_exclude_case_sensitive = self.glade.get_widget( "regex_exclude_case").get_active() move_completed = "" active_string = self.glade.get_widget( "combobox_move_completed").get_active_text() if active_string is not None: move_completed = active_string.strip() download_location = "" active_string = self.glade.get_widget( "combobox_download_location").get_active_text() if active_string is not None: download_location = active_string.strip() add_torrents_paused = self.glade.get_widget( "checkbox_add_torrents_in_paused_state").get_active() last_match = self.glade.get_widget("txt_last_matched").get_text() textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() custom_text_lines = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) rss_key = self.get_selected_combobox_key( self.glade.get_widget("combobox_rssfeeds")) # RSS feed is mandatory if not rss_key: self.rssfeed_is_mandatory_message() return False self.subscription_data["name"] = name self.subscription_data["regex_include"] = regex_include self.subscription_data["regex_exclude"] = regex_exclude self.subscription_data[ "regex_include_ignorecase"] = not regex_include_case_sensitive self.subscription_data[ "regex_exclude_ignorecase"] = not regex_exclude_case_sensitive self.subscription_data["move_completed"] = move_completed self.subscription_data["download_location"] = download_location self.subscription_data["custom_text_lines"] = custom_text_lines self.subscription_data["rssfeed_key"] = rss_key self.subscription_data[ "add_torrents_in_paused_state"] = add_torrents_paused self.subscription_data["last_match"] = last_match #self.subscription_data["max_connections"] = #self.subscription_data["max_upload_slots"] = #self.subscription_data["max_upload_slots"] = #self.subscription_data["max_upload_speed"] = #self.subscription_data["max_download_speed"] = #self.subscription_data["auto_managed"] = # Get notifications from notifications list self.subscription_data[ "email_notifications"] = self.get_current_notifications() # Call save method in gtui. Saves to core self.gtkUI.save_subscription(self.subscription_data) return True def rssfeed_is_mandatory_message(self): md = gtk.MessageDialog(self.dialog, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "You must select a RSS Feed") md.run() md.destroy() def on_button_cancel_clicked(self, Event=None): self.dialog.destroy() ######################################## ## Load data on creation ######################################## def load_subscription_data(self): self.load_basic_fields_data() self.load_rssfeed_combobox_data() self.load_notifications_list_data() self.load_move_completed_combobox_data() self.load_download_location_combobox_data() self.load_last_matched_timestamp() def load_basic_fields_data(self): if self.subscription_data is None: return self.glade.get_widget("txt_name").set_text( self.subscription_data["name"]) self.glade.get_widget("txt_regex_include").set_text( self.subscription_data["regex_include"]) self.glade.get_widget("txt_regex_exclude").set_text( self.subscription_data["regex_exclude"]) self.glade.get_widget("regex_include_case").set_active( not self.subscription_data["regex_include_ignorecase"]) self.glade.get_widget("regex_exclude_case").set_active( not self.subscription_data["regex_exclude_ignorecase"]) # Add torrents paused self.glade.get_widget( "checkbox_add_torrents_in_paused_state").set_active( self.subscription_data["add_torrents_in_paused_state"]) textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() textbuffer.set_text(self.subscription_data["custom_text_lines"]) def load_rssfeed_combobox_data(self): rssfeed_key = "-1" active_index = -1 if self.subscription_data: # If editing a subscription, set the rssfeed_key if self.subscription_data.has_key("rssfeed_key"): rssfeed_key = self.subscription_data["rssfeed_key"] # Load rssfeeds into the combobox count = 0 for key in self.rssfeeds: self.rssfeeds_store.append([ self.rssfeeds[key]["key"], self.rssfeeds[key]["name"], "(%s)" % self.rssfeeds[key]["site"] ]) if key == rssfeed_key: active_index = count count += 1 # Set active index self.glade.get_widget("combobox_rssfeeds").set_active(active_index) # Update matching self.on_txt_regex_changed(None) def load_notifications_list_data(self): # Load notification messages into combo for key in self.email_messages.keys(): self.messages_combo_store.append( [key, self.email_messages[key]["name"]]) # Load notifications into notifications list # The dict keys in email_notifications are the email messages dict keys. for key in self.subscription_data["email_notifications"].keys(): on_added = self.subscription_data["email_notifications"][key][ "on_torrent_added"] on_completed = self.subscription_data["email_notifications"][key][ "on_torrent_completed"] self.messages_list_store.append([ key, self.email_messages[key]["name"], self.email_messages[key]["active"], on_added, on_completed ]) def load_move_completed_combobox_data(self): move_completed_value = None move_completed_index = -1 # Load the move completed values for i in range(len(self.move_completed_list)): if self.move_completed_list[i] == self.subscription_data[ "move_completed"]: move_completed_index = i self.move_completed_store.append([self.move_completed_list[i]]) # Set active value in combobox if move_completed_index != -1: self.glade.get_widget("combobox_move_completed").set_active( move_completed_index) def load_download_location_combobox_data(self): download_location_value = None download_location_index = -1 # Load the download location values for i in range(len(self.download_location_list)): if self.download_location_list[i] == self.subscription_data[ "download_location"]: download_location_index = i self.download_location_store.append( [self.download_location_list[i]]) # Set active value in combobox if download_location_index != -1: self.glade.get_widget("combobox_download_location").set_active( download_location_index) def load_last_matched_timestamp(self): self.glade.get_widget("txt_last_matched").set_text( self.subscription_data["last_match"])
def setUp(self): self.log = log self.rssfeedhandler = RSSFeedHandler(self.log)
class TorrentHandlingTestCase(unittest.TestCase): def setUp(self): # NOQA self.log = log self.config = test_common.get_test_config() # get_test_config will load a new core.conf with the default values. # Must save to save to file so that torrent.py.TorrentOptions loads the default values self.config.core_config.save() global test_component test_component = TestComponent(add_retval=True) def test_add_torrent(self): handler = TorrentHandler(self.log) filename = yarss2.util.common.get_resource( "FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") torrent_info = {"link": filename, "site_cookies_dict": {}} torrent_download = handler.add_torrent(torrent_info) torrent_added = test_component.added.pop() self.assertTrue(torrent_added.success) self.assertFalse(torrent_added.filedump is None, "Filedump is not None") self.assertEquals(torrent_added.filename, os.path.split(filename)[1]) self.assertFalse(torrent_added.filedump is None) self.assertEquals(torrent_download.url, filename) def test_add_torrent_raise_AddTorrentError(self): # noqa: N802 handler = TorrentHandler(self.log) filename = yarss2.util.common.get_resource( "FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") torrent_info = {"link": filename, "site_cookies_dict": {}} with mock.patch.object(TestComponent, 'add') as test_component_add: test_component_add.side_effect = AddTorrentError( 'Torrent already in session (%s).' % 1) torrent_added = handler.add_torrent(torrent_info) self.assertFalse(torrent_added.success) def test_add_torrent_magnet_link(self): handler = TorrentHandler(self.log) torrent_url = "magnet:blbalba/url.magnet.link" torrent_info = {"link": torrent_url, "site_cookies_dict": {}} download = handler.add_torrent(torrent_info) self.assertTrue(download.success) self.assertTrue(download.is_magnet) self.assertEquals(test_component.added.pop().magnet, torrent_url) def test_add_torrent_ret_false(self): handler = TorrentHandler(self.log) torrent_url = "http://url.com/file.torrent" cookies = {} global test_component test_component.download_success = False handler.download_torrent_file = test_component.download_torrent_file torrent_info = {"link": torrent_url, "site_cookies_dict": cookies} torrent_download = handler.add_torrent(torrent_info) self.assertFalse(torrent_download.success) # Set by download_torrent_file self.assertEquals(torrent_download.torrent_url, torrent_url) self.assertEquals(torrent_download.cookies, cookies) test_component.download_success = True def test_add_torrent_with_subscription_data(self): handler = TorrentHandler(self.log) subscription_data = yarss2.yarss_config.get_fresh_subscription_config() subscription_data["move_completed"] = "move/path" subscription_data["download_location"] = "download/path" subscription_data[ "add_torrents_in_paused_state"] = GeneralSubsConf.DEFAULT download = TorrentDownload() torrent_info = { "link": "http://url.com/file.torrent", "site_cookies_dict": {}, "subscription_data": subscription_data, "torrent_download": download } d = handler.add_torrent(torrent_info) self.assertTrue(d.success) added = test_component.added.pop() self.assertTrue(added.options["move_completed"]) self.assertEquals(added.options["move_completed_path"], subscription_data["move_completed"]) self.assertEquals(added.options["download_location"], subscription_data["download_location"]) # When using DEFAULT, the default value for add_paused on TorrentSettings is False self.assertEquals(added.options["add_paused"], False) def test_get_torrent(self): handler = TorrentHandler(self.log) handler.download_torrent_file = test_component.download_torrent_file filename = yarss2.util.common.get_resource( "FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") test_component.use_filedump = read_file(filename) torrent_info = { "link": "http://url.com/file.torrent", "site_cookies_dict": { "cookiekey": "cookievalue" }, "user_agent": "test" } download = handler.get_torrent(torrent_info) self.assertEquals(download.headers, {'User-Agent': 'test'}) self.assertEquals(download.cookies, {'cookiekey': 'cookievalue'}) self.assertFalse(download.is_magnet) def test_get_torrent_magnet(self): handler = TorrentHandler(self.log) torrent_info = {"link": "magnet:hash"} download = handler.get_torrent(torrent_info) self.assertTrue(download.is_magnet) def get_test_rssfeeds_match_dict(self): match_option_dict = {} match_option_dict["regex_include"] = "" match_option_dict["regex_exclude"] = "" match_option_dict["regex_include_ignorecase"] = True match_option_dict["regex_exclude_ignorecase"] = True match_option_dict["custom_text_lines"] = None rssfeed_matching = {} rssfeed_matching["0"] = { "matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-amd64-all" } rssfeed_matching["1"] = { "matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-i386-all" } rssfeed_matching["2"] = { "matches": False, "link": "", "title": "fREEbsd-9.0-RELEASE-i386-all" } return match_option_dict, rssfeed_matching def test_add_torrents(self): handler = TorrentHandler(self.log) from yarss2.rssfeed_handling import RSSFeedHandler self.rssfeedhandler = RSSFeedHandler(self.log) # Override method download_torrent_file handler.download_torrent_file = test_component.download_torrent_file filename = yarss2.util.common.get_resource( "FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") test_component.use_filedump = read_file(filename) config = get_test_config_dict() # 0 is the rssfeed key match_result = self.rssfeedhandler.fetch_feed_torrents(config, "0") matching_torrents = match_result["matching_torrents"] saved_subscriptions = [] def save_subscription_func(subscription_data): saved_subscriptions.append(subscription_data) handler.add_torrents(save_subscription_func, matching_torrents, self.config.get_config()) self.assertEquals(len(saved_subscriptions), 3) handler.use_filedump = None
class RSSFeedHandlingTestCase(unittest.TestCase): def setUp(self): self.log = log self.rssfeedhandler = RSSFeedHandler(self.log) def test_get_rssfeed_parsed(self): file_url = yarss2.common.get_resource(common.testdata_rssfeed_filename, path="tests") rssfeed_data = {"name": "Test", "url": file_url, "site:": "only used whith cookie arguments"} parsed_feed = self.rssfeedhandler.get_rssfeed_parsed(rssfeed_data) #common.json_dump(parsed_feed["items"], "freebsd_rss_items_dump2.json") self.assertTrue(parsed_feed.has_key("items")) items = parsed_feed["items"] stored_items = common.load_json_testdata() self.assertTrue(yarss2.common.dicts_equals(items, stored_items)) def get_default_rssfeeds_dict(self): match_option_dict = {} match_option_dict["regex_include"] = "" match_option_dict["regex_exclude"] = "" match_option_dict["regex_include_ignorecase"] = True match_option_dict["regex_exclude_ignorecase"] = True match_option_dict["custom_text_lines"] = None rssfeed_matching = {} rssfeed_matching["0"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-amd64-all"} rssfeed_matching["1"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-i386-all"} rssfeed_matching["2"] = {"matches": False, "link": "", "title": "fREEbsd-9.0-RELEASE-i386-all"} return match_option_dict, rssfeed_matching def test_update_rssfeeds_dict_matching(self): options, rssfeed_parsed = self.get_default_rssfeeds_dict() options["regex_include"] = "FreeBSD" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys())) # Also make sure the items in 'matching' correspond to the matching items in rssfeed_parsed count = 0 for key in rssfeed_parsed.keys(): if rssfeed_parsed[key]["matches"]: self.assertTrue(matching.has_key(key), "The matches dict does not contain the matching key '%s'" % key) count += 1 self.assertEquals(count, len(matching.keys()), "The number of items in matches dict (%d) does not match the number of matching items (%d)" % (count, len(matching.keys()))) options["regex_include_ignorecase"] = False matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 1) #options["regex_include_ignorecase"] = True options["regex_exclude"] = "i386" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 2) # Fresh options options, rssfeed_parsed = self.get_default_rssfeeds_dict() # Custom line with unicode characters, norwegian ø and å, as well as Latin Small Letter Lambda with stroke options["custom_text_lines"] = [u"Test line with æ and å, as well as ƛ"] options["regex_include"] = "æ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (15, 17)) options["regex_include"] = "with.*ƛ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (10, 39)) # Test exclude span options["regex_include"] = ".*" options["regex_exclude"] = "line.*å" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) for key in rssfeed_parsed.keys(): if not rssfeed_parsed[key]["matches"]: self.assertEquals(rssfeed_parsed[key]["title"], options["custom_text_lines"][0]) self.assertEquals(rssfeed_parsed[key]["regex_exclude_match"], (5, 24)) break def test_fetch_subscription_torrents(self): config = get_test_config() matche_result = self.rssfeedhandler.fetch_subscription_torrents(config, "0") matches = matche_result["matching_torrents"] self.assertTrue(len(matches) == 3)
class RSSFeedHandlingTestCase(unittest.TestCase): def setUp(self): self.log = log self.rssfeedhandler = RSSFeedHandler(self.log) def test_get_rssfeed_parsed(self): file_url = yarss2.common.get_resource(common.testdata_rssfeed_filename, path="tests") rssfeed_data = { "name": "Test", "url": file_url, "site:": "only used whith cookie arguments" } parsed_feed = self.rssfeedhandler.get_rssfeed_parsed(rssfeed_data) #common.json_dump(parsed_feed["items"], "freebsd_rss_items_dump2.json") self.assertTrue(parsed_feed.has_key("items")) items = parsed_feed["items"] stored_items = common.load_json_testdata() self.assertTrue(yarss2.common.dicts_equals(items, stored_items)) def get_default_rssfeeds_dict(self): match_option_dict = {} match_option_dict["regex_include"] = "" match_option_dict["regex_exclude"] = "" match_option_dict["regex_include_ignorecase"] = True match_option_dict["regex_exclude_ignorecase"] = True match_option_dict["custom_text_lines"] = None rssfeed_matching = {} rssfeed_matching["0"] = { "matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-amd64-all" } rssfeed_matching["1"] = { "matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-i386-all" } rssfeed_matching["2"] = { "matches": False, "link": "", "title": "fREEbsd-9.0-RELEASE-i386-all" } return match_option_dict, rssfeed_matching def test_update_rssfeeds_dict_matching(self): options, rssfeed_parsed = self.get_default_rssfeeds_dict() options["regex_include"] = "FreeBSD" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys())) # Also make sure the items in 'matching' correspond to the matching items in rssfeed_parsed count = 0 for key in rssfeed_parsed.keys(): if rssfeed_parsed[key]["matches"]: self.assertTrue( matching.has_key(key), "The matches dict does not contain the matching key '%s'" % key) count += 1 self.assertEquals( count, len(matching.keys()), "The number of items in matches dict (%d) does not match the number of matching items (%d)" % (count, len(matching.keys()))) options["regex_include_ignorecase"] = False matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 1) #options["regex_include_ignorecase"] = True options["regex_exclude"] = "i386" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 2) # Fresh options options, rssfeed_parsed = self.get_default_rssfeeds_dict() # Custom line with unicode characters, norwegian ø and å, as well as Latin Small Letter Lambda with stroke options["custom_text_lines"] = [ u"Test line with æ and å, as well as ƛ" ] options["regex_include"] = "æ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (15, 17)) options["regex_include"] = "with.*ƛ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (10, 39)) # Test exclude span options["regex_include"] = ".*" options["regex_exclude"] = "line.*å" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching( rssfeed_parsed, options) for key in rssfeed_parsed.keys(): if not rssfeed_parsed[key]["matches"]: self.assertEquals(rssfeed_parsed[key]["title"], options["custom_text_lines"][0]) self.assertEquals(rssfeed_parsed[key]["regex_exclude_match"], (5, 24)) break def test_fetch_subscription_torrents(self): config = get_test_config() matche_result = self.rssfeedhandler.fetch_subscription_torrents( config, "0") matches = matche_result["matching_torrents"] self.assertTrue(len(matches) == 3)
class DialogSubscription(): def __init__(self, gtkUI, logger, subscription_data, rssfeeds, move_completed_list, download_location_list, email_messages, cookies): self.gtkUI = gtkUI self.rssfeeds = rssfeeds self.move_completed_list = move_completed_list self.download_location_list = download_location_list self.email_messages = email_messages self.rssfeeds_dict = {} self.matching_store = None self.icon_matching = gtk.gdk.pixbuf_new_from_file(get_resource("match.png")) self.icon_nonmatching = gtk.gdk.pixbuf_new_from_file(get_resource("no_match.png")) self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log) self.new_subscription = not "key" in subscription_data def setup(self): self.glade = gtk.glade.XML(get_resource("dialog_subscription.glade")) self.glade.signal_autoconnect({ "on_txt_regex_include_changed": self.on_txt_regex_changed, "on_txt_regex_exclude_changed": self.on_txt_regex_changed, "on_button_cancel_clicked": self.on_button_cancel_clicked, "on_button_save_clicked": self.on_button_save_subscription_clicked, "on_button_add_notication_clicked": self.on_button_add_notication_clicked, "on_button_remove_notication_clicked": self.on_button_remove_notication_clicked, "on_rssfeed_selected": self.on_rssfeed_selected, "on_button_fetch_clicked": self.on_rssfeed_selected, "on_button_last_matched_reset_clicked": self.on_button_last_matched_reset_clicked, "on_button_last_matched_now_clicked": self.on_button_last_matched_now_clicked, "on_general_checkBox_toggled": self.on_general_checkBox_toggled, "on_key_pressed": self.on_key_pressed, }) # This is to make testing of the GUI possible (unit tests) self.method_perform_rssfeed_selection = self.perform_rssfeed_selection self.dialog = self.glade.get_widget("window_subscription") self.dialog.set_title("Edit Subscription" if "key" in self.subscription_data else "Add Subscription") self.setup_rssfeed_combobox() self.setup_move_completed_combobox() self.setup_download_location_combobox() self.setup_messages_combobox() self.setup_messages_list() self.treeview = self.create_matching_tree() self.set_custom_text_tooltip() self.load_subscription_data() def show(self): self.setup() self.dialog.set_transient_for(component.get("Preferences").pref_dialog) self.dialog.show() ######################################## ## GUI creation ######################################## def setup_move_completed_combobox(self): move_completed_box = self.glade.get_widget("move_completed_box") self.move_completed_path_chooser = PathChooser("move_completed_paths_list") self.move_completed_path_chooser.set_filechooser_visible(client.is_localhost()) self.move_completed_path_chooser.set_enable_properties(False) self.move_completed_path_chooser.set_enable_properties(True) move_completed_box.add(self.move_completed_path_chooser) move_completed_box.show_all() def setup_download_location_combobox(self): download_location_box = self.glade.get_widget("download_location_box") self.download_location_path_chooser = PathChooser("download_location_paths_list") self.download_location_path_chooser.set_filechooser_visible(client.is_localhost()) download_location_box.add(self.download_location_path_chooser) download_location_box.show_all() def setup_rssfeed_combobox(self): rssfeeds_combobox = self.glade.get_widget("combobox_rssfeeds") rendererName = gtk.CellRendererText() rendererSite = gtk.CellRendererText() rssfeeds_combobox.pack_start(rendererName, False) rssfeeds_combobox.pack_end(rendererSite, False) rssfeeds_combobox.add_attribute(rendererName, "text", 1) rssfeeds_combobox.add_attribute(rendererSite, "text", 2) # key, name, site url self.rssfeeds_store = gtk.ListStore(str, str, str) rssfeeds_combobox.set_model(self.rssfeeds_store) def setup_messages_combobox(self): messages_combobox = self.glade.get_widget("combobox_messages") rendererText = gtk.CellRendererText() messages_combobox.pack_start(rendererText, False) messages_combobox.add_attribute(rendererText, "text", 1) # key, name self.messages_combo_store = gtk.ListStore(str, str) messages_combobox.set_model(self.messages_combo_store) def create_matching_tree(self): # Matches, Title, Published, torrent link, CustomAttribute for PangoCellrenderer self.matching_store = gtk.ListStore(bool, str, str, str, CustomAttribute) self.matching_treeview = gtk.TreeView(self.matching_store) #self.matching_treeview.connect("cursor-changed", self.on_subscription_listitem_activated) #self.matching_treeview.connect("row-activated", self.on_button_edit_subscription_clicked) self.matching_treeview.set_rules_hint(True) self.matching_treeview.connect('button-press-event', self.on_matches_list_button_press_event) self.matching_treeview.connect('query-tooltip', self.on_tooltip_matches) self.matching_treeview.set_has_tooltip(True) def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 0) == True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Matches", renderer) column.set_cell_data_func(renderer, cell_data_func) column.set_sort_column_id(0) self.matching_treeview.append_column(column) cellrenderer = CellRendererPango() column = gtk.TreeViewColumn("Title", cellrenderer, text=1) column.add_attribute(cellrenderer, "custom", 4) column.set_sort_column_id(1) column.set_resizable(True) column.set_expand(True) self.matching_treeview.append_column(column) cellrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Published", cellrenderer, text=2) column.set_sort_column_id(2) self.matching_treeview.append_column(column) col = gtk.TreeViewColumn() col.set_visible(False) self.matching_treeview.append_column(col) self.list_popup_menu = gtk.Menu() menuitem1 = gtk.MenuItem("Add torrent") menuitem1.connect("activate", self.on_button_add_torrent_clicked) menuitem2 = gtk.MenuItem("Add torrent with current subscription options") menuitem2.connect("activate", self.on_button_add_torrent_clicked, True) menuitem3 = gtk.MenuItem("Copy title to clipboard") menuitem3.connect("activate", self.on_button_copy_to_clipboard, 1) menuitem4 = gtk.MenuItem("Copy link to clipboard") menuitem4.connect("activate", self.on_button_copy_to_clipboard, 3) self.list_popup_menu.append(menuitem1) self.list_popup_menu.append(menuitem2) self.list_popup_menu.append(menuitem3) self.list_popup_menu.append(menuitem4) return self.matching_treeview def set_custom_text_tooltip(self): textview = self.glade.get_widget("textview_custom_text") textview.set_tooltip_text("Each line is added to the list and tested for matching against the filters.") def on_tooltip_matches(self, widget, x, y, keyboard_tip, tooltip): x, y = self.treeview.convert_widget_to_bin_window_coords(x, y) if widget.get_path_at_pos(x, y) is None: return False path, treeColumn, t, r = widget.get_path_at_pos(x, y) if not path: return False # Matches, Title, Published, torrent link, CustomAttribute for PangoCellrenderer it = self.matching_store.get_iter(path) title = self.matching_store.get_value(it, 1) published = self.matching_store.get_value(it, 2) link = self.matching_store.get_value(it, 3) if link == None: return False text = "<b>Title:</b> %s\n<b>Link:</b> <u>%s</u>\n<b>Published:</b> %s" % (title.replace("&", "&"), link.replace("&", "&"), published) tooltip.set_markup(text) widget.set_tooltip_cell(tooltip, path, None, None) return True def on_matches_list_button_press_event(self, treeview, event): """Shows popup on selected row when right clicking""" if event.button == 3: x = int(event.x) y = int(event.y) time = event.time pthinfo = treeview.get_path_at_pos(x, y) it = self.matching_store.get_iter(pthinfo[0]) link = self.matching_store.get_value(it, 3) if link is None: return False if pthinfo is not None: path, col, cellx, celly = pthinfo treeview.grab_focus() treeview.set_cursor(path, col, 0) self.list_popup_menu.popup(None, None, None, event.button, time, data=path) self.list_popup_menu.show_all() return True def on_button_add_torrent_clicked(self, menuitem, use_settings=False): torrent_link = get_value_in_selected_row(self.matching_treeview, self.matching_store, column_index=3) # Save current data to dict self.store_subscription_data() if torrent_link is None: return def add_torrent_callback(torrent_download): torrent_download = TorrentDownload(torrent_download) if torrent_download.success: return textview = self.glade.get_widget("textview_messages") textbuffer = textview.get_buffer() readable_body = http.clean_html_body(torrent_download.filedump) textbuffer.set_text(readable_body) notebook = self.glade.get_widget("notebook_lower") notebook.set_current_page(1) # Quick hack to make sure the message is visible to the user. hpaned = self.glade.get_widget("hpaned_matching") #if hpaned.get_position() == 0: max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.3)) self.gtkUI.add_torrent(torrent_link, self.subscription_data if use_settings else None, add_torrent_callback) def on_button_copy_to_clipboard(self, menuitem, index): text = get_value_in_selected_row(self.matching_treeview, self.matching_store, column_index=index) if text is not None: gtk.clipboard_get().set_text(text) def setup_messages_list(self): # message_key, message_title, active, torrent_added, torrent_completed, self.messages_list_store = gtk.ListStore(str, str, bool, bool, bool) self.messages_treeview = gtk.TreeView(self.messages_list_store) self.messages_treeview.connect("row-activated", self.on_notification_list_clicked) self.columns_dict = {} def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 2) == True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Message Active", renderer) column.set_cell_data_func(renderer, cell_data_func) self.messages_treeview.append_column(column) rendererText = gtk.CellRendererText() column = gtk.TreeViewColumn("Message Title", rendererText, text=1) self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent added", renderer, active=3) self.columns_dict["3"] = column self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent completed", renderer, active=4) self.columns_dict["4"] = column viewport = self.glade.get_widget("viewport_email_notifications") viewport.add(self.messages_treeview) viewport.show_all() ######################################## ## GUI Update / Callbacks ######################################## def get_selected_combobox_key(self, combobox): """Get the key of the currently selected item in the combobox""" # Get selected item active = combobox.get_active() model = combobox.get_model() iterator = combobox.get_active_iter() if iterator is None or model.get_value(iterator, 0) == -1: return None return model.get_value(iterator, 0) ### RSS Matching ################### ## Callbacks def on_rssfeed_selected(self, combobox): """Callback from glade when rss combobox is selected. Gets the results for the RSS Feed Runs the code that handles the parsing in a thread with Twisted, to avoid the dialog waiting on startup. """ self.method_perform_rssfeed_selection() def perform_rssfeed_selection(self): rssfeed_key = self.get_selected_combobox_key(self.glade.get_widget("combobox_rssfeeds")) defered = threads.deferToThread(self.get_and_update_rssfeed_results, rssfeed_key) defered.addCallback(self.update_matching_view_with_rssfeed_results) return defered def on_txt_regex_changed(self, text_field): """ Callback for when Enter is pressed in either of the regex fields """ self.perform_search() def get_custom_text_lines(self): textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() lines = [] text = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) text = string_to_unicode(text) for line in text.splitlines(): lines.append(line.strip()) return lines def get_search_settings(self): regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case = self.glade.get_widget("regex_include_case").get_active() regex_exclude_case = self.glade.get_widget("regex_exclude_case").get_active() match_option_dict = {} match_option_dict["regex_include"] = regex_include if (len(regex_include) > 0) else None match_option_dict["regex_exclude"] = regex_exclude if (len(regex_exclude) > 0) else None match_option_dict["regex_include_ignorecase"] = not regex_include_case match_option_dict["regex_exclude_ignorecase"] = not regex_exclude_case match_option_dict["custom_text_lines"] = self.get_custom_text_lines() return match_option_dict def perform_search(self): match_option_dict = self.get_search_settings() self.perform_matching_and_update_liststore(match_option_dict) # Insert treeview self.set_matching_window_child(self.treeview) ## Perform matching and update liststore (which updates GUI) def perform_matching_and_update_liststore(self, match_option_dict): """Updates the rssfeed_dict with matching according to options in match_option_dict Also updates the GUI """ if not self.rssfeeds_dict and not match_option_dict["custom_text_lines"]: return try: matchings, message = self.rssfeedhandler.update_rssfeeds_dict_matching(self.rssfeeds_dict, options=match_option_dict) self.update_matching_feeds_store(self.treeview, self.matching_store, self.rssfeeds_dict, regex_matching=True) label_status = self.glade.get_widget("label_status") if message: label_status.set_text(str(message)) label_count = self.glade.get_widget("label_torrent_count") label_count.set_text("Torrent count: %d, Matches: %d" % (len(self.rssfeeds_dict.keys()), len(matchings.keys()))) except Exception as (v): import traceback exc_str = traceback.format_exc(v) self.log.warn("Error when matching:" + exc_str) def update_matching_feeds_store(self, treeview, store, rssfeeds_dict, regex_matching=False): """Updates the liststore of matching torrents. This updates the GUI""" store.clear() for key in sorted(rssfeeds_dict.keys()): customAttributes = CustomAttribute() if regex_matching: attr = {} if rssfeeds_dict[key].has_key("regex_include_match"): attr["regex_include_match"] = rssfeeds_dict[key]["regex_include_match"] if rssfeeds_dict[key].has_key("regex_exclude_match"): attr["regex_exclude_match"] = rssfeeds_dict[key]["regex_exclude_match"] customAttributes = CustomAttribute(attributes_dict=attr) updated = rssfeeds_dict[key]['updated'] store.append([rssfeeds_dict[key]['matches'], rssfeeds_dict[key]['title'], updated if updated else "Not available", rssfeeds_dict[key]['link'], customAttributes]) def get_and_update_rssfeed_results(self, rssfeed_key): site_cookies = http.get_matching_cookies_dict(self.cookies, self.rssfeeds[rssfeed_key]["site"]) rssfeeds_parsed = self.rssfeedhandler.get_rssfeed_parsed(self.rssfeeds[rssfeed_key], site_cookies_dict=site_cookies) return rssfeeds_parsed def update_matching_view_with_rssfeed_results(self, rssfeeds_parsed): """Callback function, called when 'get_and_update_rssfeed_results' has finished. Replaces the content of the matching window. If valid items were retrieved, update the matching according to current settings. If no valid items, show the result as text instead. """ if rssfeeds_parsed is None: return # Window has been closed in the meantime if not self.dialog.get_visible(): return # Reset status to not display an older status label_status = self.glade.get_widget("label_status") label_text = "" # Bozo Exception (feedbparser error), still elements might have been successfully parsed if rssfeeds_parsed.has_key("bozo_exception"): exception = rssfeeds_parsed["bozo_exception"] label_text = str(exception) else: message_text = "TTL value: %s" % (("%s min" % rssfeeds_parsed["ttl"]) \ if rssfeeds_parsed.has_key("ttl") \ else "Not available") # This can sometimes be None for some effing reason if label_status: label_text = message_text try: # label_status is sometimes None, for som effin reason label_status.set_text(label_text) except: self.log.warn("label_status is None", gtkui=False) pass # Failed to retrive items. Show content as text if not rssfeeds_parsed.has_key("items"): self.show_result_as_text(rssfeeds_parsed["raw_result"]) return self.rssfeeds_dict = rssfeeds_parsed["items"] # Update the matching according to the current settings self.perform_search() def show_result_as_text(self, raw_rssfeed): """When failing to parse the RSS Feed, this will show the result in a text window with HTML tags stripped away. """ result = self.get_viewable_result(raw_rssfeed) textview = gtk.TextView() textbuffer = textview.get_buffer() textview.show() textbuffer.set_text(result) # Insert widget self.set_matching_window_child(textview) def get_viewable_result(self, rssfeed_parsed): if not rssfeed_parsed["feed"].has_key("summary"): return "" cleaned = rssfeed_parsed["feed"]["summary"] s = http.HTMLStripper() s.feed(cleaned) return s.get_data() def set_matching_window_child(self, widget): """Insert the widget into the matching window""" matching_window = self.glade.get_widget("matching_window_upper") if matching_window.get_child(): matching_window.remove(matching_window.get_child()) matching_window.add(widget) # Quick hack to make sure the list of torrents are visible to the user. hpaned = self.glade.get_widget("hpaned_matching") if hpaned.get_position() == 0: max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.75)) matching_window.show_all() ### Notifications ################### def on_notification_list_clicked(self, Event=None, a=None, col=None): """Callback for when the checkboxes (or actually just the row) in notification list is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if not row_iter or not col: return for column in self.columns_dict.keys(): if self.columns_dict[column] == col: column = int(column) val = self.messages_list_store.get_value(row_iter, column) self.messages_list_store.set_value(row_iter, column, not val) return def on_button_add_notication_clicked(self, button): combobox = self.glade.get_widget("combobox_messages") key = self.get_selected_combobox_key(combobox) if key is None: return # Current notications message_dict = self.get_current_notifications() for c_key in message_dict.keys(): # This message is already in the notifications list if c_key == key: return self.messages_list_store.append([key, self.email_messages[key]["name"], self.email_messages[key]["active"], True, False]) def get_current_notifications(self): """ Retrieves the notifications from the notifications list""" notifications = {} row_iter = self.messages_list_store.get_iter_first() while row_iter is not None: key = self.messages_list_store.get_value(row_iter, 0) active = self.messages_list_store.get_value(row_iter, 2) on_added = self.messages_list_store.get_value(row_iter, 3) on_completed = self.messages_list_store.get_value(row_iter, 4) notifications[key] = {"on_torrent_added": on_added, "on_torrent_completed": on_completed} # Next row row_iter = self.messages_list_store.iter_next(row_iter) return notifications def message_checkbox_toggled_cb(self, cell, path, model): """Called when the checkboxes in the notications list are clicked""" for column in self.columns_dict.keys(): if self.columns_dict[column] == cell: column = int(column) row_iter = self.messages_list_store.get_iter(path) reversed_value = not self.messages_list_store.get_value(row_iter, column) self.messages_list_store.set_value(row_iter, column, reversed_value) def on_button_remove_notication_clicked(self, button): """Callback for when remove button for notifications is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if row_iter: self.messages_list_store.remove(row_iter) ### Options ################### def on_button_last_matched_reset_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text("") def on_button_last_matched_now_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text(get_current_date().isoformat()) ## Save / Close ################### def on_button_save_subscription_clicked(self, Event=None, a=None, col=None): if self.save_subscription_data(): self.dialog.destroy() def save_subscription_data(self): if not self.store_subscription_data(): return False # Call save method in gtui. Saves to core self.gtkUI.save_subscription(self.subscription_data) return True def store_subscription_data(self): name = self.glade.get_widget("txt_name").get_text() regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case_sensitive = self.glade.get_widget("regex_include_case").get_active() regex_exclude_case_sensitive = self.glade.get_widget("regex_exclude_case").get_active() move_completed = "" active_string = self.move_completed_path_chooser.get_text() if active_string is not None: move_completed = active_string.strip() download_location = "" #active_string = self.glade.get_widget("combobox_download_location").get_active_text() active_string = self.download_location_path_chooser.get_text() if active_string is not None: download_location = active_string.strip() last_match = self.glade.get_widget("txt_last_matched").get_text() max_download_speed = self.glade.get_widget("spinbutton_max_download_speed").get_value() max_upload_speed = self.glade.get_widget("spinbutton_max_upload_speed").get_value() max_connections = self.glade.get_widget("spinbutton_max_connections").get_value() max_upload_slots = self.glade.get_widget("spinbutton_max_upload_slots").get_value() add_torrents_paused = self.glade.get_widget("checkbox_add_torrents_in_paused_state").get_active() auto_managed = self.glade.get_widget("checkbutton_auto_managed").get_active() sequential_download = self.glade.get_widget("checkbutton_sequential_download").get_active() prioritize_first_last = self.glade.get_widget("checkbutton_prioritize_first_last").get_active() add_torrents_paused_default = self.glade.get_widget("checkbox_add_torrents_in_paused_state_default").get_active() auto_managed_default = self.glade.get_widget("checkbutton_auto_managed_default").get_active() sequential_download_default = self.glade.get_widget("checkbutton_sequential_download_default").get_active() prioritize_first_last_default = self.glade.get_widget("checkbutton_prioritize_first_last_default").get_active() add_torrents_paused = GeneralSubsConf().bool_to_value(add_torrents_paused, add_torrents_paused_default) auto_managed = GeneralSubsConf().bool_to_value(auto_managed, auto_managed_default) sequential_download = GeneralSubsConf().bool_to_value(sequential_download, sequential_download_default) prioritize_first_last = GeneralSubsConf().bool_to_value(prioritize_first_last, prioritize_first_last_default) textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() custom_text_lines = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) rss_key = self.get_selected_combobox_key(self.glade.get_widget("combobox_rssfeeds")) # RSS feed is mandatory if not rss_key: self.rssfeed_is_mandatory_message() return False self.subscription_data["name"] = name self.subscription_data["regex_include"] = regex_include self.subscription_data["regex_exclude"] = regex_exclude self.subscription_data["regex_include_ignorecase"] = not regex_include_case_sensitive self.subscription_data["regex_exclude_ignorecase"] = not regex_exclude_case_sensitive self.subscription_data["move_completed"] = move_completed self.subscription_data["download_location"] = download_location self.subscription_data["custom_text_lines"] = custom_text_lines self.subscription_data["rssfeed_key"] = rss_key self.subscription_data["last_match"] = last_match self.subscription_data["max_download_speed"] = int(max_download_speed) self.subscription_data["max_upload_speed"] = int(max_upload_speed) self.subscription_data["max_connections"] = int(max_connections) self.subscription_data["max_upload_slots"] = int(max_upload_slots) self.subscription_data["add_torrents_in_paused_state"] = add_torrents_paused self.subscription_data["auto_managed"] = auto_managed self.subscription_data["sequential_download"] = sequential_download self.subscription_data["prioritize_first_last_pieces"] = prioritize_first_last # Get notifications from notifications list self.subscription_data["email_notifications"] = self.get_current_notifications() return True def rssfeed_is_mandatory_message(self): md = gtk.MessageDialog(self.dialog, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "You must select a RSS Feed") md.run() md.destroy() def on_button_cancel_clicked(self, Event=None): self.dialog.destroy() def on_key_pressed(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.dialog.destroy() ######################################## ## Load data on creation ######################################## def load_subscription_data(self): self.load_basic_fields_data() self.load_rssfeed_combobox_data() self.load_notifications_list_data() self.load_path_choosers_data() self.load_last_matched_timestamp() def load_basic_fields_data(self): if self.subscription_data is None: return self.glade.get_widget("txt_name").set_text(self.subscription_data["name"]) self.glade.get_widget("txt_regex_include").set_text(self.subscription_data["regex_include"]) self.glade.get_widget("txt_regex_exclude").set_text(self.subscription_data["regex_exclude"]) self.glade.get_widget("regex_include_case").set_active( not self.subscription_data["regex_include_ignorecase"]) self.glade.get_widget("regex_exclude_case").set_active( not self.subscription_data["regex_exclude_ignorecase"]) textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() textbuffer.set_text(self.subscription_data["custom_text_lines"]) self.glade.get_widget("spinbutton_max_download_speed").set_value(self.subscription_data["max_download_speed"]) self.glade.get_widget("spinbutton_max_upload_speed").set_value(self.subscription_data["max_upload_speed"]) self.glade.get_widget("spinbutton_max_connections").set_value(self.subscription_data["max_connections"]) self.glade.get_widget("spinbutton_max_upload_slots").set_value(self.subscription_data["max_upload_slots"]) add_paused = self.subscription_data["add_torrents_in_paused_state"] auto_managed = self.subscription_data["auto_managed"] sequential_download = self.subscription_data["sequential_download"] prioritize_first_last_pieces = self.subscription_data["prioritize_first_last_pieces"] self.glade.get_widget("checkbox_add_torrents_in_paused_state").set_active(add_paused == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_auto_managed").set_active(auto_managed == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_sequential_download").set_active(sequential_download == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_prioritize_first_last").set_active(prioritize_first_last_pieces == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbox_add_torrents_in_paused_state_default").set_active(add_paused == GeneralSubsConf.DEFAULT) self.glade.get_widget("checkbutton_auto_managed_default").set_active(auto_managed == GeneralSubsConf.DEFAULT) self.glade.get_widget("checkbutton_sequential_download_default").set_active(sequential_download == GeneralSubsConf.DEFAULT) self.glade.get_widget("checkbutton_prioritize_first_last_default").set_active(prioritize_first_last_pieces == GeneralSubsConf.DEFAULT) self.on_general_checkBox_toggled(None) def on_general_checkBox_toggled(self, button): self.glade.get_widget("checkbox_add_torrents_in_paused_state").set_sensitive(\ not self.glade.get_widget("checkbox_add_torrents_in_paused_state_default").get_active()) self.glade.get_widget("checkbutton_auto_managed").set_sensitive(\ not self.glade.get_widget("checkbutton_auto_managed_default").get_active()) self.glade.get_widget("checkbutton_sequential_download").set_sensitive(\ not self.glade.get_widget("checkbutton_sequential_download_default").get_active()) self.glade.get_widget("checkbutton_prioritize_first_last").set_sensitive(\ not self.glade.get_widget("checkbutton_prioritize_first_last_default").get_active()) def load_rssfeed_combobox_data(self): rssfeed_key = "-1" active_index = -1 if self.subscription_data: # If editing a subscription, set the rssfeed_key if self.subscription_data.has_key("rssfeed_key"): rssfeed_key = self.subscription_data["rssfeed_key"] # Load rssfeeds into the combobox count = 0 for key in self.rssfeeds: self.rssfeeds_store.append([self.rssfeeds[key]["key"], self.rssfeeds[key]["name"], "(%s)" % self.rssfeeds[key]["site"]]) if key == rssfeed_key: active_index = count count += 1 # Set active index self.glade.get_widget("combobox_rssfeeds").set_active(active_index) # Update matching self.on_txt_regex_changed(None) def load_notifications_list_data(self): # Load notification messages into combo for key in self.email_messages.keys(): self.messages_combo_store.append([key, self.email_messages[key]["name"]]) # Load notifications into notifications list # The dict keys in email_notifications are the email messages dict keys. for key in self.subscription_data["email_notifications"].keys(): on_added = self.subscription_data["email_notifications"][key]["on_torrent_added"] on_completed = self.subscription_data["email_notifications"][key]["on_torrent_completed"] self.messages_list_store.append([key, self.email_messages[key]["name"], self.email_messages[key]["active"], on_added, on_completed]) def load_path_choosers_data(self): self.core_keys = [ "download_location", "move_completed_path", "move_completed_paths_list", "download_location_paths_list", ] def _on_config_values(config): if not config.get("move_completed_paths_list", None) is None: self.move_completed_path_chooser.add_values(config["move_completed_paths_list"]) if not config.get("download_location_paths_list", None) is None: self.download_location_path_chooser.add_values(config["download_location_paths_list"]) if self.new_subscription: self.move_completed_path_chooser.set_text(config["move_completed_path"]) self.download_location_path_chooser.set_text(config["download_location"]) else: self.move_completed_path_chooser.set_text(self.subscription_data["move_completed"]) self.download_location_path_chooser.set_text(self.subscription_data["download_location"]) client.core.get_config_values(self.core_keys).addCallback(_on_config_values) def load_last_matched_timestamp(self): self.glade.get_widget("txt_last_matched").set_text(self.subscription_data["last_match"])
class RSSFeedHandlingTestCase(unittest.TestCase): def setUp(self): self.log = log self.rssfeedhandler = RSSFeedHandler(self.log) def test_get_rssfeed_parsed(self): file_url = yarss2.util.common.get_resource(common.testdata_rssfeed_filename, path="tests/") rssfeed_data = {"name": "Test", "url": file_url, "site:": "only used whith cookie arguments"} site_cookies = {"uid": "18463", "passkey": "b830f87d023037f9393749s932"} parsed_feed = self.rssfeedhandler.get_rssfeed_parsed(rssfeed_data, site_cookies_dict=site_cookies) # When needing to dump the result in json format #common.json_dump(parsed_feed["items"], "freebsd_rss_items_dump2.json") self.assertTrue(parsed_feed.has_key("items")) items = parsed_feed["items"] stored_items = common.load_json_testdata() self.assertTrue(yarss2.util.common.dicts_equals(items, stored_items, debug=False)) self.assertEquals(parsed_feed["cookie_header"], {'Cookie': 'uid=18463; passkey=b830f87d023037f9393749s932'}) def test_get_link(self): file_url = yarss2.util.common.get_resource(common.testdata_rssfeed_filename, path="tests/") from yarss2.lib.feedparser import feedparser parsed_feed = feedparser.parse(file_url) item = None for e in parsed_feed["items"]: item = e break # Item has enclosure, so it should use that link self.assertEquals(self.rssfeedhandler.get_link(item), item.enclosures[0]["href"]) del item["links"][:] # Item no longer has enclosures, so it should return the regular link self.assertEquals(self.rssfeedhandler.get_link(item), item["link"]) def test_get_size(self): file_url = yarss2.util.common.get_resource("t1.rss", path="tests/data/feeds/") from yarss2.lib.feedparser import feedparser parsed_feed = feedparser.parse(file_url) size = self.rssfeedhandler.get_size(parsed_feed["items"][0]) self.assertEquals(len(size), 1) self.assertEquals(size[0], (4541927915.52, u'4.23 GB')) size = self.rssfeedhandler.get_size(parsed_feed["items"][1]) self.assertEquals(len(size), 1) self.assertEquals(size[0], (402349096.96, u'383.71 MB')) size = self.rssfeedhandler.get_size(parsed_feed["items"][2]) self.assertEquals(len(size), 1) self.assertEquals(size[0], (857007476)) size = self.rssfeedhandler.get_size(parsed_feed["items"][3]) self.assertEquals(len(size), 2) self.assertEquals(size[0], (14353107637)) self.assertEquals(size[1], (13529146982.4, u'12.6 GB')) def get_default_rssfeeds_dict(self): match_option_dict = {} match_option_dict["regex_include"] = "" match_option_dict["regex_exclude"] = "" match_option_dict["regex_include_ignorecase"] = True match_option_dict["regex_exclude_ignorecase"] = True match_option_dict["custom_text_lines"] = None rssfeed_matching = {} rssfeed_matching["0"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-amd64-all"} rssfeed_matching["1"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-i386-all"} rssfeed_matching["2"] = {"matches": False, "link": "", "title": "fREEbsd-9.0-RELEASE-i386-all"} return match_option_dict, rssfeed_matching def test_update_rssfeeds_dict_matching(self): options, rssfeed_parsed = self.get_default_rssfeeds_dict() options["regex_include"] = "FreeBSD" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys())) # Also make sure the items in 'matching' correspond to the matching items in rssfeed_parsed count = 0 for key in rssfeed_parsed.keys(): if rssfeed_parsed[key]["matches"]: self.assertTrue(matching.has_key(key), "The matches dict does not contain the matching key '%s'" % key) count += 1 self.assertEquals(count, len(matching.keys()), "The number of items in matches dict (%d) does not match the number of matching items (%d)" % (count, len(matching.keys()))) options["regex_include_ignorecase"] = False matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 1) #options["regex_include_ignorecase"] = True options["regex_exclude"] = "i386" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), len(rssfeed_parsed.keys()) - 2) # Fresh options options, rssfeed_parsed = self.get_default_rssfeeds_dict() # Custom line with unicode characters, norwegian ø and å, as well as Latin Small Letter Lambda with stroke options["custom_text_lines"] = [u"Test line with æ and å, as well as ƛ"] options["regex_include"] = "æ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (15, 17)) options["regex_include"] = "with.*ƛ" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) self.assertEquals(len(matching.keys()), 1) for key in matching.keys(): self.assertEquals(matching[key]["title"], options["custom_text_lines"][0]) self.assertEquals(matching[key]["regex_include_match"], (10, 39)) # Test exclude span options["regex_include"] = ".*" options["regex_exclude"] = "line.*å" matching, msg = self.rssfeedhandler.update_rssfeeds_dict_matching(rssfeed_parsed, options) for key in rssfeed_parsed.keys(): if not rssfeed_parsed[key]["matches"]: self.assertEquals(rssfeed_parsed[key]["title"], options["custom_text_lines"][0]) self.assertEquals(rssfeed_parsed[key]["regex_exclude_match"], (5, 24)) break def test_fetch_feed_torrents(self): config = common.get_test_config_dict() # 0 is the rssfeed key matche_result = self.rssfeedhandler.fetch_feed_torrents(config, "0") matches = matche_result["matching_torrents"] self.assertTrue(len(matches) == 3)
class TorrentHandlingTestCase(unittest.TestCase): def setUp(self): self.log = log self.config = common.get_test_config() # get_test_config will load a new core.conf with the default values. # Must save to save to file so that torrent.py.TorrentOptions loads the default values self.config.core_config.save() global test_component test_component = TestComponent(True) def test_add_torrent(self): handler = TorrentHandler(self.log) filename = yarss2.util.common.get_resource("FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") torrent_info = {"link": filename, "site_cookies_dict": {}} torrent_download = handler.add_torrent(torrent_info) torrent_added = test_component.added.pop() self.assertTrue(torrent_added.success) self.assertFalse(torrent_added.filedump is None, "Filedump is not None") self.assertEquals(torrent_added.filename, os.path.split(filename)[1]) self.assertFalse(torrent_added.filedump is None) self.assertEquals(torrent_download.url, filename) def test_add_torrent_magnet_link(self): handler = TorrentHandler(self.log) torrent_url = "magnet:blbalba/url.magnet.link" torrent_info = {"link": torrent_url, "site_cookies_dict": {}} download = handler.add_torrent(torrent_info) self.assertTrue(download.success) self.assertTrue(download.is_magnet) self.assertEquals(test_component.added.pop().magnet, torrent_url) def test_add_torrent_ret_false(self): handler = TorrentHandler(self.log) torrent_url = "http://url.com/file.torrent" cookies_dict = {} global test_component test_component.download_success = False handler.download_torrent_file = test_component.download_torrent_file torrent_info = {"link": torrent_url, "site_cookies_dict": cookies_dict} torrent_download = handler.add_torrent(torrent_info) self.assertFalse(torrent_download.success) # Set by download_torrent_file self.assertEquals(torrent_download.torrent_url, torrent_url) self.assertEquals(torrent_download.cookies_dict, cookies_dict) test_component.download_success = True def test_add_torrent_with_subscription_data(self): handler = TorrentHandler(self.log) subscription_data = yarss2.yarss_config.get_fresh_subscription_config() subscription_data["move_completed"] = "move/path" subscription_data["download_location"] = "download/path" subscription_data["add_torrents_in_paused_state"] = GeneralSubsConf.DEFAULT download = TorrentDownload() torrent_info = { "link": "http://url.com/file.torrent", "site_cookies_dict": {}, "subscription_data": subscription_data, "torrent_download": download, } d = handler.add_torrent(torrent_info) self.assertTrue(d.success) added = test_component.added.pop() self.assertTrue(added.options["move_completed"]) self.assertEquals(added.options["move_completed_path"], subscription_data["move_completed"]) self.assertEquals(added.options["download_location"], subscription_data["download_location"]) # When using DEFAULT, the default value for add_paused on TorrentSettings is False self.assertEquals(added.options["add_paused"], False) def get_test_rssfeeds_match_dict(self): match_option_dict = {} match_option_dict["regex_include"] = "" match_option_dict["regex_exclude"] = "" match_option_dict["regex_include_ignorecase"] = True match_option_dict["regex_exclude_ignorecase"] = True match_option_dict["custom_text_lines"] = None rssfeed_matching = {} rssfeed_matching["0"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-amd64-all"} rssfeed_matching["1"] = {"matches": False, "link": "", "title": "FreeBSD-9.0-RELEASE-i386-all"} rssfeed_matching["2"] = {"matches": False, "link": "", "title": "fREEbsd-9.0-RELEASE-i386-all"} return match_option_dict, rssfeed_matching def test_add_torrents(self): handler = TorrentHandler(self.log) from yarss2.rssfeed_handling import RSSFeedHandler self.rssfeedhandler = RSSFeedHandler(self.log) # Override method download_torrent_file handler.download_torrent_file = test_component.download_torrent_file filename = yarss2.util.common.get_resource("FreeBSD-9.0-RELEASE-amd64-dvd1.torrent", path="tests/data/") test_component.use_filedump = read_file(filename) config = get_test_config_dict() # 0 is the rssfeed key match_result = self.rssfeedhandler.fetch_feed_torrents(config, "0") matching_torrents = match_result["matching_torrents"] saved_subscriptions = [] def save_subscription_func(subscription_data): saved_subscriptions.append(subscription_data) handler.add_torrents(save_subscription_func, matching_torrents, self.config.get_config()) self.assertEquals(len(saved_subscriptions), 1) handler.use_filedump = None
class DialogSubscription(object): def __init__(self, gtkui, logger, subscription_data, rssfeeds, move_completed_list, download_location_list, email_messages, cookies): self.gtkUI = gtkui self.rssfeeds = rssfeeds self.move_completed_list = move_completed_list self.download_location_list = download_location_list self.email_messages = email_messages self.rssfeeds_dict = {} self.matching_store = None self.icon_matching = gtk.gdk.pixbuf_new_from_file( get_resource("match.png")) self.icon_nonmatching = gtk.gdk.pixbuf_new_from_file( get_resource("no_match.png")) self.subscription_data = subscription_data self.cookies = cookies self.log = logger self.rssfeedhandler = RSSFeedHandler(self.log) self.new_subscription = "key" not in subscription_data self.labels = None self.editing = True if len( self.subscription_data.get("rssfeed_key", "")) != 0 else False def setup(self): self.glade = gtk.glade.XML(get_resource("dialog_subscription.glade")) self.glade.signal_autoconnect({ "on_txt_regex_include_changed": self.on_txt_regex_changed, "on_txt_regex_exclude_changed": self.on_txt_regex_changed, "on_button_cancel_clicked": self.on_button_cancel_clicked, "on_button_save_clicked": self.on_button_save_subscription_clicked, "on_button_add_notication_clicked": self.on_button_add_notication_clicked, "on_button_remove_notication_clicked": self.on_button_remove_notication_clicked, "on_rssfeed_selected": self.on_rssfeed_selected, "on_button_fetch_clicked": self.on_rssfeed_selected, "on_button_last_matched_reset_clicked": self.on_button_last_matched_reset_clicked, "on_button_last_matched_now_clicked": self.on_button_last_matched_now_clicked, "on_general_checkbox_toggled": self.on_general_checkbox_toggled, "on_key_pressed": self.on_key_pressed, }) # This is to make testing of the GUI possible (unit tests) self.method_perform_rssfeed_selection = self.perform_rssfeed_selection self.dialog = self.glade.get_widget("window_subscription") self.dialog.set_title( "Edit Subscription" if self.editing else "Add Subscription") self.setup_rssfeed_combobox() self.setup_move_completed_combobox() self.setup_download_location_combobox() self.setup_messages_combobox() self.setup_messages_list() self.treeview = self.create_matching_tree() self.setup_labels() self.set_custom_text_tooltip() self.load_subscription_data() def show(self): self.setup() self.dialog.set_transient_for(component.get("Preferences").pref_dialog) self.dialog.show() ######################################## # GUI creation ######################################## def setup_labels(self): combobox_labels = self.glade.get_widget("combobox_labels") label_labels = self.glade.get_widget("labels_label") def on_labels(labels): self.labels = labels # Disable labels in GUI if self.labels is None: label_labels.set_sensitive(False) combobox_labels.set_sensitive(False) tooltips = gtk.Tooltips() tooltips.set_tip(combobox_labels, 'Label plugin is not enabled') else: label_labels.set_sensitive(True) renderer_label = gtk.CellRendererText() combobox_labels.pack_end(renderer_label, False) combobox_labels.add_attribute(renderer_label, "text", 0) self.labels_liststore = gtk.ListStore(str) combobox_labels.set_model(self.labels_liststore) self.get_labels_d = self.gtkUI.get_labels().addCallback(on_labels) def setup_move_completed_combobox(self): move_completed_box = self.glade.get_widget("move_completed_box") self.move_completed_path_chooser = PathChooser( "move_completed_paths_list") self.move_completed_path_chooser.set_filechooser_button_visible( client.is_localhost()) self.move_completed_path_chooser.set_enable_properties(False) self.move_completed_path_chooser.set_enable_properties(True) move_completed_box.add(self.move_completed_path_chooser) move_completed_box.show_all() def setup_download_location_combobox(self): download_location_box = self.glade.get_widget("download_location_box") self.download_location_path_chooser = PathChooser( "download_location_paths_list") self.download_location_path_chooser.set_filechooser_button_visible( client.is_localhost()) download_location_box.add(self.download_location_path_chooser) download_location_box.show_all() def setup_rssfeed_combobox(self): rssfeeds_combobox = self.glade.get_widget("combobox_rssfeeds") renderer_name = gtk.CellRendererText() renderer_site = gtk.CellRendererText() rssfeeds_combobox.pack_start(renderer_name, False) rssfeeds_combobox.pack_end(renderer_site, False) rssfeeds_combobox.add_attribute(renderer_name, "text", 1) rssfeeds_combobox.add_attribute(renderer_site, "text", 2) # key, name, site url self.rssfeeds_store = gtk.ListStore(str, str, str) rssfeeds_combobox.set_model(self.rssfeeds_store) def setup_messages_combobox(self): messages_combobox = self.glade.get_widget("combobox_messages") renderer_text = gtk.CellRendererText() messages_combobox.pack_start(renderer_text, False) messages_combobox.add_attribute(renderer_text, "text", 1) # key, name self.messages_combo_store = gtk.ListStore(str, str) messages_combobox.set_model(self.messages_combo_store) def create_matching_tree(self): # Matches, Title, Published, link, CustomAttribute for PangoCellrenderer, torrent link, magnet link self.matching_store = gtk.ListStore(bool, str, str, str, CustomAttribute, str, str) self.matching_treeview = gtk.TreeView(self.matching_store) self.matching_treeview.set_rules_hint(True) self.matching_treeview.connect('button-press-event', self.on_matches_list_button_press_event) self.matching_treeview.connect('query-tooltip', self.on_tooltip_matches) self.matching_treeview.set_has_tooltip(True) def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 0) is True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Matches", renderer) column.set_cell_data_func(renderer, cell_data_func) column.set_sort_column_id(0) self.matching_treeview.append_column(column) cellrenderer = CellRendererPango() column = gtk.TreeViewColumn("Title", cellrenderer, text=1) column.add_attribute(cellrenderer, "custom", 4) column.set_sort_column_id(1) column.set_resizable(True) column.set_expand(True) self.matching_treeview.append_column(column) cellrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Published", cellrenderer, text=2) column.set_sort_column_id(2) self.matching_treeview.append_column(column) col = gtk.TreeViewColumn() col.set_visible(False) self.matching_treeview.append_column(col) self.list_popup_menu = gtk.Menu() menuitem1 = gtk.MenuItem("Add torrent") menuitem1.connect("activate", self.on_button_add_torrent_clicked) menuitem2 = gtk.MenuItem( "Add torrent with current subscription options") menuitem2.connect("activate", self.on_button_add_torrent_clicked, True) menuitem3 = gtk.MenuItem("Copy title to clipboard") menuitem3.connect("activate", self.on_button_copy_to_clipboard, 1) menuitem4 = gtk.MenuItem("Copy link to clipboard") menuitem4.connect("activate", self.on_button_copy_to_clipboard, 3) self.list_popup_menu.append(menuitem1) self.list_popup_menu.append(menuitem2) self.list_popup_menu.append(menuitem3) self.list_popup_menu.append(menuitem4) return self.matching_treeview def set_custom_text_tooltip(self): textview = self.glade.get_widget("textview_custom_text") textview.set_tooltip_text( "Each line is added to the list and tested for matching against the filters." ) def on_tooltip_matches(self, widget, x, y, keyboard_tip, tooltip): x, y = self.treeview.convert_widget_to_bin_window_coords(x, y) if widget.get_path_at_pos(x, y) is None: return False path, treeColumn, t, r = widget.get_path_at_pos(x, y) if not path: return False # Matches, Title, Published, torrent link, CustomAttribute for PangoCellrenderer it = self.matching_store.get_iter(path) title = self.matching_store.get_value(it, 1) published = self.matching_store.get_value(it, 2) link = self.matching_store.get_value(it, 3) if link is None: return False text = "<b>Title:</b> %s\n<b>Link:</b> <u>%s</u>\n<b>Published:</b> %s" % ( title.replace("&", "&"), link.replace("&", "&"), published) tooltip.set_markup(text) widget.set_tooltip_cell(tooltip, path, None, None) return True def on_matches_list_button_press_event(self, treeview, event): """Shows popup on selected row when right clicking""" if event.button == 3: x = int(event.x) y = int(event.y) time = event.time pthinfo = treeview.get_path_at_pos(x, y) it = self.matching_store.get_iter(pthinfo[0]) link = self.matching_store.get_value(it, 3) if link is None: return False if pthinfo is not None: path, col, cellx, celly = pthinfo treeview.grab_focus() treeview.set_cursor(path, col, 0) self.list_popup_menu.popup(None, None, None, event.button, time, data=path) self.list_popup_menu.show_all() return True def on_button_add_torrent_clicked(self, menuitem, use_settings=False): torrent_link = get_value_in_selected_row(self.matching_treeview, self.matching_store, column_index=3) # Save current data to dict self.store_subscription_data() if torrent_link is None: return def add_torrent_callback(torrent_download): torrent_download = TorrentDownload(torrent_download) if torrent_download.success: return textview = self.glade.get_widget("textview_messages") textbuffer = textview.get_buffer() if torrent_download.filedump is None: return readable_body = http.clean_html_body(torrent_download.filedump) textbuffer.set_text(readable_body) notebook = self.glade.get_widget("notebook_lower") notebook.set_current_page(1) # Quick hack to make sure the message is visible to the user. hpaned = self.glade.get_widget("hpaned_matching") max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.3)) self.gtkUI.add_torrent( torrent_link, self.subscription_data if use_settings else None, add_torrent_callback) def on_button_copy_to_clipboard(self, menuitem, index): text = get_value_in_selected_row(self.matching_treeview, self.matching_store, column_index=index) if text is not None: gtk.clipboard_get().set_text(text) def setup_messages_list(self): # message_key, message_title, active, torrent_added, torrent_completed, self.messages_list_store = gtk.ListStore(str, str, bool, bool, bool) self.messages_treeview = gtk.TreeView(self.messages_list_store) self.messages_treeview.connect("row-activated", self.on_notification_list_clicked) self.columns_dict = {} def cell_data_func(tree_column, cell, model, tree_iter): if model.get_value(tree_iter, 2) is True: pixbuf = self.icon_matching else: pixbuf = self.icon_nonmatching cell.set_property("pixbuf", pixbuf) renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn("Message Active", renderer) column.set_cell_data_func(renderer, cell_data_func) self.messages_treeview.append_column(column) renderer_text = gtk.CellRendererText() column = gtk.TreeViewColumn("Message Title", renderer_text, text=1) self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent added", renderer, active=3) self.columns_dict["3"] = column self.messages_treeview.append_column(column) renderer = gtk.CellRendererToggle() renderer.connect("toggled", self.message_checkbox_toggled_cb, self.messages_list_store) column = gtk.TreeViewColumn("On torrent completed", renderer, active=4) self.columns_dict["4"] = column viewport = self.glade.get_widget("viewport_email_notifications") viewport.add(self.messages_treeview) viewport.show_all() ######################################## # GUI Update / Callbacks ######################################## def get_selected_combobox_key(self, combobox): """Get the key of the currently selected item in the combobox""" # Get selected item model = combobox.get_model() iterator = combobox.get_active_iter() if iterator is None or model.get_value(iterator, 0) == -1: return None return model.get_value(iterator, 0) # RSS Matching ################### # Callbacks def on_rssfeed_selected(self, combobox): """Callback from glade when rss combobox is selected. Gets the results for the RSS Feed Runs the code that handles the parsing in a thread with Twisted, to avoid the dialog waiting on startup. """ self.method_perform_rssfeed_selection() def perform_rssfeed_selection(self): rssfeed_key = self.get_selected_combobox_key( self.glade.get_widget("combobox_rssfeeds")) deferred = threads.deferToThread(self.get_and_update_rssfeed_results, rssfeed_key) deferred.addCallback(self.update_matching_view_with_rssfeed_results) return deferred def on_txt_regex_changed(self, text_field): """ Callback for when Enter is pressed in either of the regex fields """ self.perform_search() def get_custom_text_lines(self): textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() lines = [] text = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) text = string_to_unicode(text) for line in text.splitlines(): lines.append(line.strip()) return lines def get_search_settings(self): regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case = self.glade.get_widget( "regex_include_case").get_active() regex_exclude_case = self.glade.get_widget( "regex_exclude_case").get_active() match_option_dict = {} match_option_dict["regex_include"] = regex_include if ( len(regex_include) > 0) else None match_option_dict["regex_exclude"] = regex_exclude if ( len(regex_exclude) > 0) else None match_option_dict["regex_include_ignorecase"] = not regex_include_case match_option_dict["regex_exclude_ignorecase"] = not regex_exclude_case match_option_dict["custom_text_lines"] = self.get_custom_text_lines() return match_option_dict def perform_search(self): match_option_dict = self.get_search_settings() self.perform_matching_and_update_liststore(match_option_dict) # Insert treeview self.set_matching_window_child(self.treeview) # Perform matching and update liststore (which updates GUI) def perform_matching_and_update_liststore(self, match_option_dict): """Updates the rssfeed_dict with matching according to options in match_option_dict Also updates the GUI """ if not self.rssfeeds_dict and not match_option_dict[ "custom_text_lines"]: return try: matchings, message = self.rssfeedhandler.update_rssfeeds_dict_matching( self.rssfeeds_dict, options=match_option_dict) self.update_matching_feeds_store(self.treeview, self.matching_store, self.rssfeeds_dict, regex_matching=True) label_status = self.glade.get_widget("label_status") if message: label_status.set_text(str(message)) label_count = self.glade.get_widget("label_torrent_count") label_count.set_text( "Torrent count: %d, Matches: %d" % (len(self.rssfeeds_dict.keys()), len(matchings.keys()))) except Exception as (v): import traceback exc_str = traceback.format_exc(v) self.log.warn("Error when matching:" + exc_str) def update_matching_feeds_store(self, treeview, store, rssfeeds_dict, regex_matching=False): """Updates the liststore of matching torrents. This updates the GUI""" store.clear() for key in sorted(rssfeeds_dict.keys()): custom_attributes = CustomAttribute() if regex_matching: attr = {} if "regex_include_match" in rssfeeds_dict[key]: attr["regex_include_match"] = rssfeeds_dict[key][ "regex_include_match"] if "regex_exclude_match" in rssfeeds_dict[key]: attr["regex_exclude_match"] = rssfeeds_dict[key][ "regex_exclude_match"] custom_attributes = CustomAttribute(attributes_dict=attr) updated = rssfeeds_dict[key]['updated'] store.append([ rssfeeds_dict[key]['matches'], rssfeeds_dict[key]['title'], updated if updated else "Not available", rssfeeds_dict[key]['link'], custom_attributes, rssfeeds_dict[key]["torrent"], rssfeeds_dict[key]["magnet"] ]) def get_and_update_rssfeed_results(self, rssfeed_key): site_cookies = http.get_matching_cookies_dict( self.cookies, self.rssfeeds[rssfeed_key]["site"]) user_agent = get_user_agent(rssfeed_data=self.rssfeeds[rssfeed_key]) rssfeeds_parsed = self.rssfeedhandler.get_rssfeed_parsed( self.rssfeeds[rssfeed_key], site_cookies_dict=site_cookies, user_agent=user_agent) return rssfeeds_parsed def update_matching_view_with_rssfeed_results(self, rssfeeds_parsed): """Callback function, called when 'get_and_update_rssfeed_results' has finished. Replaces the content of the matching window. If valid items were retrieved, update the matching according to current settings. If no valid items, show the result as text instead. """ if rssfeeds_parsed is None: return # Window has been closed in the meantime if not self.dialog.get_visible(): return # Reset status to not display an older status label_status = self.glade.get_widget("label_status") label_text = "" # Bozo Exception (feedbparser error), still elements might have been successfully parsed if "bozo_exception" in rssfeeds_parsed: exception = rssfeeds_parsed["bozo_exception"] label_text = str(exception) else: message_text = "TTL value: %s" % ( ("%s min" % rssfeeds_parsed["ttl"]) if "ttl" in rssfeeds_parsed else "Not available") # This can sometimes be None for some effing reason if label_status: label_text = message_text try: # label_status is sometimes None, for som effin reason label_status.set_text(label_text) except: self.log.warn("label_status is None", gtkui=False) pass # Failed to retrive items. Show content as text if "items" not in rssfeeds_parsed: self.show_result_as_text(rssfeeds_parsed["raw_result"]) return self.rssfeeds_dict = rssfeeds_parsed["items"] # Update the matching according to the current settings self.perform_search() def show_result_as_text(self, raw_rssfeed): """When failing to parse the RSS Feed, this will show the result in a text window with HTML tags stripped away. """ result = self.get_viewable_result(raw_rssfeed) textview = gtk.TextView() textbuffer = textview.get_buffer() textview.show() textbuffer.set_text(result) # Insert widget self.set_matching_window_child(textview) def get_viewable_result(self, rssfeed_parsed): if "summary" not in rssfeed_parsed["feed"]: return "" cleaned = rssfeed_parsed["feed"]["summary"] s = http.HTMLStripper() s.feed(cleaned) return s.get_data() def set_matching_window_child(self, widget): """Insert the widget into the matching window""" matching_window = self.glade.get_widget("matching_window_upper") if matching_window.get_child(): matching_window.remove(matching_window.get_child()) matching_window.add(widget) # Quick hack to make sure the list of torrents are visible to the user. hpaned = self.glade.get_widget("hpaned_matching") if hpaned.get_position() == 0: max_pos = hpaned.get_property("max-position") hpaned.set_position(int(max_pos * 0.75)) matching_window.show_all() # Notifications ################### def on_notification_list_clicked(self, event=None, a=None, col=None): """Callback for when the checkboxes (or actually just the row) in notification list is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if not row_iter or not col: return for column in self.columns_dict.keys(): if self.columns_dict[column] == col: column = int(column) val = self.messages_list_store.get_value(row_iter, column) self.messages_list_store.set_value(row_iter, column, not val) return def on_button_add_notication_clicked(self, button): combobox = self.glade.get_widget("combobox_messages") key = self.get_selected_combobox_key(combobox) if key is None: return # Current notications message_dict = self.get_current_notifications() for c_key in message_dict.keys(): # This message is already in the notifications list if c_key == key: return self.messages_list_store.append([ key, self.email_messages[key]["name"], self.email_messages[key]["active"], True, False ]) def get_current_notifications(self): """ Retrieves the notifications from the notifications list""" notifications = {} row_iter = self.messages_list_store.get_iter_first() while row_iter is not None: key = self.messages_list_store.get_value(row_iter, 0) # active = self.messages_list_store.get_value(row_iter, 2) on_added = self.messages_list_store.get_value(row_iter, 3) on_completed = self.messages_list_store.get_value(row_iter, 4) notifications[key] = { "on_torrent_added": on_added, "on_torrent_completed": on_completed } # Next row row_iter = self.messages_list_store.iter_next(row_iter) return notifications def message_checkbox_toggled_cb(self, cell, path, model): """Called when the checkboxes in the notications list are clicked""" for column in self.columns_dict.keys(): if self.columns_dict[column] == cell: column = int(column) row_iter = self.messages_list_store.get_iter(path) reversed_value = not self.messages_list_store.get_value( row_iter, column) self.messages_list_store.set_value(row_iter, column, reversed_value) def on_button_remove_notication_clicked(self, button): """Callback for when remove button for notifications is clicked""" tree, row_iter = self.messages_treeview.get_selection().get_selected() if row_iter: self.messages_list_store.remove(row_iter) # Options ################### def on_button_last_matched_reset_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text("") def on_button_last_matched_now_clicked(self, button): self.glade.get_widget("txt_last_matched").set_text( get_current_date_in_isoformat()) # Save / Close ################### def on_button_save_subscription_clicked(self, event=None, a=None, col=None): if self.save_subscription_data(): self.dialog.destroy() def save_subscription_data(self): if not self.store_subscription_data(): return False # Call save method in gtui. Saves to core self.gtkUI.save_subscription(self.subscription_data) return True def store_subscription_data(self): name = self.glade.get_widget("txt_name").get_text() regex_include = self.glade.get_widget("txt_regex_include").get_text() regex_exclude = self.glade.get_widget("txt_regex_exclude").get_text() regex_include_case_sensitive = self.glade.get_widget( "regex_include_case").get_active() regex_exclude_case_sensitive = self.glade.get_widget( "regex_exclude_case").get_active() move_completed = "" active_string = self.move_completed_path_chooser.get_text() if active_string is not None: move_completed = active_string.strip() download_location = "" active_string = self.download_location_path_chooser.get_text() if active_string is not None: download_location = active_string.strip() last_match = self.glade.get_widget("txt_last_matched").get_text() ignore_timestamp = self.glade.get_widget( "checkbutton_ignore_timestamp").get_active() max_download_speed = self.glade.get_widget( "spinbutton_max_download_speed").get_value() max_upload_speed = self.glade.get_widget( "spinbutton_max_upload_speed").get_value() max_connections = self.glade.get_widget( "spinbutton_max_connections").get_value() max_upload_slots = self.glade.get_widget( "spinbutton_max_upload_slots").get_value() add_torrents_paused = self.glade.get_widget( "checkbox_add_torrents_in_paused_state").get_active() auto_managed = self.glade.get_widget( "checkbutton_auto_managed").get_active() sequential_download = self.glade.get_widget( "checkbutton_sequential_download").get_active() prioritize_first_last = self.glade.get_widget( "checkbutton_prioritize_first_last").get_active() add_torrents_paused_default = self.glade.get_widget( "checkbox_add_torrents_in_paused_state_default").get_active() auto_managed_default = self.glade.get_widget( "checkbutton_auto_managed_default").get_active() sequential_download_default = self.glade.get_widget( "checkbutton_sequential_download_default").get_active() prioritize_first_last_default = self.glade.get_widget( "checkbutton_prioritize_first_last_default").get_active() add_torrents_paused = GeneralSubsConf().bool_to_value( add_torrents_paused, add_torrents_paused_default) auto_managed = GeneralSubsConf().bool_to_value(auto_managed, auto_managed_default) sequential_download = GeneralSubsConf().bool_to_value( sequential_download, sequential_download_default) prioritize_first_last = GeneralSubsConf().bool_to_value( prioritize_first_last, prioritize_first_last_default) textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() custom_text_lines = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) combobox_labels = self.glade.get_widget("combobox_labels") label = None active_label = combobox_labels.get_active_iter() if active_label is not None: label = combobox_labels.get_model().get_value(active_label, 0) rss_key = self.get_selected_combobox_key( self.glade.get_widget("combobox_rssfeeds")) # RSS feed is mandatory if not rss_key: self.rssfeed_is_mandatory_message() return False self.subscription_data["name"] = name self.subscription_data["regex_include"] = regex_include self.subscription_data["regex_exclude"] = regex_exclude self.subscription_data[ "regex_include_ignorecase"] = not regex_include_case_sensitive self.subscription_data[ "regex_exclude_ignorecase"] = not regex_exclude_case_sensitive self.subscription_data["move_completed"] = move_completed self.subscription_data["download_location"] = download_location self.subscription_data["custom_text_lines"] = custom_text_lines self.subscription_data["rssfeed_key"] = rss_key self.subscription_data["last_match"] = last_match self.subscription_data["ignore_timestamp"] = ignore_timestamp self.subscription_data["max_download_speed"] = int(max_download_speed) self.subscription_data["max_upload_speed"] = int(max_upload_speed) self.subscription_data["max_connections"] = int(max_connections) self.subscription_data["max_upload_slots"] = int(max_upload_slots) self.subscription_data[ "add_torrents_in_paused_state"] = add_torrents_paused self.subscription_data["auto_managed"] = auto_managed self.subscription_data["sequential_download"] = sequential_download self.subscription_data[ "prioritize_first_last_pieces"] = prioritize_first_last self.subscription_data["label"] = label # Get notifications from notifications list self.subscription_data[ "email_notifications"] = self.get_current_notifications() return True def rssfeed_is_mandatory_message(self): md = gtk.MessageDialog(self.dialog, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "You must select a RSS Feed") md.run() md.destroy() def on_button_cancel_clicked(self, event=None): self.dialog.destroy() def on_key_pressed(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.dialog.destroy() ######################################## # Load data on creation ######################################## def load_subscription_data(self): self.load_basic_fields_data() self.load_rssfeed_combobox_data() self.load_notifications_list_data() self.load_path_choosers_data() self.load_timestamp() self.get_labels_d.addCallback(self.load_labels) def load_basic_fields_data(self): if self.subscription_data is None: return self.glade.get_widget("txt_name").set_text( self.subscription_data["name"]) self.glade.get_widget("txt_regex_include").set_text( self.subscription_data["regex_include"]) self.glade.get_widget("txt_regex_exclude").set_text( self.subscription_data["regex_exclude"]) self.glade.get_widget("regex_include_case").set_active( not self.subscription_data["regex_include_ignorecase"]) self.glade.get_widget("regex_exclude_case").set_active( not self.subscription_data["regex_exclude_ignorecase"]) textbuffer = self.glade.get_widget("textview_custom_text").get_buffer() textbuffer.set_text(self.subscription_data["custom_text_lines"]) self.glade.get_widget("spinbutton_max_download_speed").set_value( self.subscription_data["max_download_speed"]) self.glade.get_widget("spinbutton_max_upload_speed").set_value( self.subscription_data["max_upload_speed"]) self.glade.get_widget("spinbutton_max_connections").set_value( self.subscription_data["max_connections"]) self.glade.get_widget("spinbutton_max_upload_slots").set_value( self.subscription_data["max_upload_slots"]) add_paused = self.subscription_data["add_torrents_in_paused_state"] auto_managed = self.subscription_data["auto_managed"] sequential_download = self.subscription_data["sequential_download"] prioritize_first_last_pieces = self.subscription_data[ "prioritize_first_last_pieces"] self.glade.get_widget( "checkbox_add_torrents_in_paused_state").set_active( add_paused == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_auto_managed").set_active( auto_managed == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_sequential_download").set_active( sequential_download == GeneralSubsConf.ENABLED) self.glade.get_widget("checkbutton_prioritize_first_last").set_active( prioritize_first_last_pieces == GeneralSubsConf.ENABLED) self.glade.get_widget( "checkbox_add_torrents_in_paused_state_default").set_active( add_paused == GeneralSubsConf.DEFAULT) self.glade.get_widget("checkbutton_auto_managed_default").set_active( auto_managed == GeneralSubsConf.DEFAULT) self.glade.get_widget( "checkbutton_sequential_download_default").set_active( sequential_download == GeneralSubsConf.DEFAULT) self.glade.get_widget( "checkbutton_prioritize_first_last_default").set_active( prioritize_first_last_pieces == GeneralSubsConf.DEFAULT) self.on_general_checkbox_toggled(None) def on_general_checkbox_toggled(self, button): self.glade.get_widget( "checkbox_add_torrents_in_paused_state" ).set_sensitive(not self.glade.get_widget( "checkbox_add_torrents_in_paused_state_default").get_active()) self.glade.get_widget("checkbutton_auto_managed").set_sensitive( not self.glade.get_widget( "checkbutton_auto_managed_default").get_active()) self.glade.get_widget("checkbutton_sequential_download").set_sensitive( not self.glade.get_widget( "checkbutton_sequential_download_default").get_active()) self.glade.get_widget( "checkbutton_prioritize_first_last").set_sensitive( not self.glade.get_widget( "checkbutton_prioritize_first_last_default").get_active()) def load_rssfeed_combobox_data(self): rssfeed_key = "-1" active_index = -1 if self.subscription_data: # If editing a subscription, set the rssfeed_key if self.editing: rssfeed_key = self.subscription_data["rssfeed_key"] # Load rssfeeds into the combobox count = 0 for key in sorted(self.rssfeeds): self.rssfeeds_store.append([ self.rssfeeds[key]["key"], self.rssfeeds[key]["name"], "(%s)" % self.rssfeeds[key]["site"] ]) if key == rssfeed_key: active_index = count count += 1 # Set active index self.glade.get_widget("combobox_rssfeeds").set_active(active_index) # Update matching self.on_txt_regex_changed(None) def load_notifications_list_data(self): # Load notification messages into combo for key in self.email_messages.keys(): self.messages_combo_store.append( [key, self.email_messages[key]["name"]]) # Load notifications into notifications list # The dict keys in email_notifications are the email messages dict keys. for key in self.subscription_data["email_notifications"].keys(): on_added = self.subscription_data["email_notifications"][key][ "on_torrent_added"] on_completed = self.subscription_data["email_notifications"][key][ "on_torrent_completed"] self.messages_list_store.append([ key, self.email_messages[key]["name"], self.email_messages[key]["active"], on_added, on_completed ]) def load_path_choosers_data(self): self.core_keys = [ "download_location", "move_completed_path", "move_completed_paths_list", "download_location_paths_list", ] def _on_config_values(config): if not config.get("move_completed_paths_list", None) is None: self.move_completed_path_chooser.add_values( config["move_completed_paths_list"]) if not config.get("download_location_paths_list", None) is None: self.download_location_path_chooser.add_values( config["download_location_paths_list"]) if self.new_subscription: self.move_completed_path_chooser.set_text( config["move_completed_path"]) self.download_location_path_chooser.set_text( config["download_location"]) else: self.move_completed_path_chooser.set_text( self.subscription_data["move_completed"]) self.download_location_path_chooser.set_text( self.subscription_data["download_location"]) client.core.get_config_values( self.core_keys).addCallback(_on_config_values) def load_timestamp(self): self.glade.get_widget("txt_last_matched").set_text( self.subscription_data["last_match"]) self.glade.get_widget("checkbutton_ignore_timestamp").set_active( self.subscription_data["ignore_timestamp"]) def load_labels(self, labels): if self.labels is None: return current = self.subscription_data["label"] active_index = 0 for i, label in enumerate(self.labels): if label == current: active_index = i self.labels_liststore.append([label]) combobox_labels = self.glade.get_widget("combobox_labels") combobox_labels.set_active(active_index)
class RSSFeedScheduler(object): """Handles scheduling the RSS Feed fetches.""" def __init__(self, config, logger): self.yarss_config = config self.rssfeed_timers = {} self.run_queue = RSSFeedRunQueue() self.log = logger self.rssfeedhandler = RSSFeedHandler(logger) self.torrent_handler = TorrentHandler(logger) self.add_torrent_func = self.torrent_handler.add_torrents # To make it possible to disable adding torrents in testing def enable_timers(self): """Creates the LoopingCall timers, one for each RSS Feed""" config = self.yarss_config.get_config() for key in config["rssfeeds"]: self.set_timer(config["rssfeeds"][key]["key"], config["rssfeeds"][key]['update_interval']) self.log.info("Scheduled RSS Feed '%s' with interval %s" % (config["rssfeeds"][key]["name"], config["rssfeeds"][key]["update_interval"])) def disable_timers(self): for key in self.rssfeed_timers.keys(): self.rssfeed_timers[key]["timer"].stop() del self.rssfeed_timers[key] def set_timer(self, key, interval): """Schedule a timer for the specified interval.""" try: interval = int(interval) except: self.log.error("Failed to convert interval '%s' to int!" % str(interval)) # Already exists, so reschedule if interval has changed if self.rssfeed_timers.has_key(key): # Interval is the same, so return if self.rssfeed_timers[key]["update_interval"] == interval: return False self.rssfeed_timers[key]["timer"].stop() self.rssfeed_timers[key]["update_interval"] = interval else: # New timer # Second argument, the rssfeedkey is passed as argument in the callback method #timer = LoopingCall(self.rssfeed_update_handler, (key)) timer = LoopingCall(self.queue_rssfeed_update, key) self.rssfeed_timers[key] = {"timer": timer, "update_interval": interval} self.rssfeed_timers[key]["timer"].start(interval * 60, now=False) # Multiply to get seconds return True def delete_timer(self, key): """Delete timer with the specified key.""" if not self.rssfeed_timers.has_key(key): self.log.warn("Cannot delete timer. No timer with key %s" % key) return False self.rssfeed_timers[key]["timer"].stop() del self.rssfeed_timers[key] return True def rssfeed_update_handler(self, rssfeed_key=None, subscription_key=None): """Goes through all the feeds and runs the active ones. Multiple subscriptions on one RSS Feed will download the RSS feed page only once """ if subscription_key: self.log.info("Manually running Subscription '%s'" % (self.yarss_config.get_config()["subscriptions"][subscription_key]["name"])) elif rssfeed_key: if self.yarss_config.get_config()["rssfeeds"][rssfeed_key]["active"] is False: return #self.log.info("Running RSS Feed '%s'" % (self.yarss_config.get_config()["rssfeeds"][rssfeed_key]["name"])) fetch_result = self.rssfeedhandler.fetch_feed_torrents(self.yarss_config.get_config(), rssfeed_key, subscription_key=subscription_key) matching_torrents = fetch_result["matching_torrents"] # Fetching the torrent files. Do this slow task in non-main thread. for torrent in matching_torrents: torrent["torrent_download"] = self.torrent_handler.get_torrent(torrent) # Update TTL value? if fetch_result.has_key("ttl"): # Subscription is run directly. Get RSS Feed key if not rssfeed_key: rssfeed_key = self.yarss_config.get_config()["subscriptions"][subscription_key]["rssfeed_key"] self.log.info("Rescheduling RSS Feed '%s' with interval '%s' according to TTL." % (self.yarss_config.get_config()["rssfeeds"][rssfeed_key]["name"], fetch_result["ttl"])) self.set_timer(rssfeed_key, fetch_result["ttl"]) # Set new interval in config self.yarss_config.get_config()["rssfeeds"][rssfeed_key]["update_interval"] = fetch_result["ttl"] # Send YARSSConfigChangedEvent to GUI with updated config. try: # Tests throws KeyError for EventManager when running this method, so wrap this in try/except component.get("EventManager").emit(YARSSConfigChangedEvent(self.yarss_config.get_config())) except KeyError: pass def save_subscription_func(subscription_data): self.yarss_config.generic_save_config("subscriptions", data_dict=subscription_data) return (self.add_torrent_func, save_subscription_func, fetch_result["matching_torrents"], self.yarss_config.get_config()) def add_torrents_callback(self, args): """This i called with the results from rssfeed_update_handler add_torrent_func must be called on the main thread """ if args is None: return add_torrent_func, save_subscription_func, matching_torrents, config = args add_torrent_func(save_subscription_func, matching_torrents, config) def queue_rssfeed_update(self, *args, **kwargs): d = self.run_queue.push(self.rssfeed_update_handler, *args, **kwargs) d.addCallback(self.add_torrents_callback) return d