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
Exemple #3
0
 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)
Exemple #9
0
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)
Exemple #13
0
 def setUp(self):
     self.log = log
     self.rssfeedhandler = RSSFeedHandler(self.log)
Exemple #14
0
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("&", "&amp;"), link.replace("&", "&amp;"), 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
Exemple #18
0
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("&", "&amp;"), link.replace("&", "&amp;"), 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