diff --git a/plugins/FlexRSS/CalendarButton.py b/plugins/FlexRSS/CalendarButton.py new file mode 100644 index 000000000..2dea91f98 --- /dev/null +++ b/plugins/FlexRSS/CalendarButton.py @@ -0,0 +1,67 @@ +# Obviously needs work, but a decent start. +# Public domain + +import gobject, gtk, time + +class CalendarButton(gtk.Button): + format = None + calendar = None + window = None + + __gsignals__ = { + 'date-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), + 'date-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + } + + def __lost_focus(self, win, e): + print e.type + + def __day_selected(self, calendar): + self.window.hide() + newdate = self.get_date() + self.set_date(newdate[0], newdate[1], newdate[2]) + self.emit("date-selected", newdate) + + def __show_calendar(self, source): + if self.window != None: + self.window.show() + return + + self.window = gtk.Window(gtk.WINDOW_POPUP) + self.window.set_position(gtk.WIN_POS_MOUSE) + self.window.set_destroy_with_parent(True) + self.window.set_transient_for(source.get_ancestor(gtk.Window)) + self.window.add(self.calendar) + self.window.show_all() + + def __set_date_on_button(self, year, month, day): + self.set_label(time.strftime('%x', time.strptime('%d/%d/%d' % (day, month, year), '%d/%m/%Y'))) + + def set_date(self, year, month, day): + self.__set_date_on_button(year, month, day) + self.calendar.select_month(month - 1, year) + self.calendar.select_day(day) + self.emit("date-changed", (year, month, day)) + + def get_date(self): + ret = self.calendar.get_date() + return (ret[0], ret[1] + 1, ret[2]) + + def __init__(self, year=None, month=None, day=None): + value = time.localtime() + + if year == None: + year = value[0] + if month == None: + month = value[1] + if day == None: + day = value[2] + + self.calendar = gtk.Calendar() + self.calendar.select_month(month - 1, year) + self.calendar.select_day(day) + self.calendar.connect("day-selected-double-click", self.__day_selected) + + gtk.Button.__init__(self, time.strftime('%x', time.strptime('%d/%d/%d' % (day, month, year), '%d/%m/%Y'))) + + self.connect("pressed", self.__show_calendar) diff --git a/plugins/FlexRSS/FlexRSS.glade b/plugins/FlexRSS/FlexRSS.glade new file mode 100644 index 000000000..264fd08c3 --- /dev/null +++ b/plugins/FlexRSS/FlexRSS.glade @@ -0,0 +1,1593 @@ + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + FlexRSS + FlexRSS.svg + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-new + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-save + True + 0 + + + + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-delete + True + 0 + + + + 2 + + + + + False + 1 + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Feed will be retrieved automatically, based on the update interval. + Enabled + 0 + True + True + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Feed must be refreshed manually. + Disabled + 0 + True + True + FlexRSS_EditFeed_Status_Enabled + + + 1 + + + + + 1 + 2 + 3 + 4 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Status + + + 3 + 4 + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Update Interval + + + 2 + 3 + GTK_FILL + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + URL + + + 1 + 2 + GTK_FILL + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Name + + + GTK_FILL + GTK_FILL + 5 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 900 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + GTK_FILL + + + + + False + 2 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Feeds + + + tab + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 0 + + + + False + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-delete + True + 0 + + + + False + False + 1 + + + + + False + 1 + + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Name + + + GTK_FILL + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Type + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Feed + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + True + False + + + + + 1 + 2 + 2 + 3 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Information</b> + True + + + label_item + + + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 0 + + + + False + 1 + + + + + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Patterns</b> + True + + + label_item + + + + + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From + 0 + True + + + + GTK_FILL + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Season + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Episode + + + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + + 4 + 5 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Thru + 0 + True + + + + 1 + 2 + GTK_FILL + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Season + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + + 2 + 3 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Episode + + + 3 + 4 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + + 4 + 5 + 1 + 2 + GTK_FILL + + + + + False + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + + + + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From + 0 + True + + + + GTK_FILL + GTK_FILL + 5 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Thru + 0 + True + + + + 1 + 2 + GTK_FILL + GTK_FILL + 5 + + + + + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>History Restriction</b> + True + + + label_item + + + + + False + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Doesn't Match + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Season + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Episode + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + 1 + 2 + 1 + 2 + + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Year + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Month + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Day + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + 1 + 2 + 2 + 3 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Test</b> + True + + + label_item + + + + + False + 3 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Insert torrent at top of queue. + 0 + True + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Set state to paused. + 0 + True + + + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete filter when matched. + 0 + True + + + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Download</b> + True + + + label_item + + + + + False + 4 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Replacement + + + 1 + 2 + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Pattern + + + GTK_FILL + 5 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Link Rewriting</b> + True + + + label_item + + + + + False + 5 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Content will be saved to Deluge's default directory, or a prompt will appear if none is set. + Deluge default + 0 + True + True + FlexRSS_Filter_Output_Type_Custom + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Content will be automatically downloaded to the specified directory. + Choose: + 0 + True + True + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + + False + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Output</b> + True + + + label_item + + + + + False + 6 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-save + True + 0 + + + + False + 1 + + + + + False + GTK_PACK_END + 6 + + + + + 5 + 1 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Filters + + + tab + 1 + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Show a FlexRSS icon in Deluge's toolbar. + Show button on toolbar. + 0 + True + + + + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Interface</b> + True + + + label_item + + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Feeds will be retrieved and parsed in their own threads. The application will not be blocked, but it can be unreliable. + Threaded (experimental) + 0 + True + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Feed Retrieval</b> + True + + + label_item + + + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-new + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-save + True + 0 + + + + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-delete + True + 0 + + + + 2 + + + + + False + 1 + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Domain + + + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Path + + + 1 + 2 + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Name + + + 2 + 3 + GTK_FILL + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Value + + + 3 + 4 + GTK_FILL + 5 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 2 + 3 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 3 + 4 + + + + + False + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Cookies</b> + True + + + label_item + + + + + 2 + + + + + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Configuration + + + tab + 2 + False + + + + + + diff --git a/plugins/FlexRSS/FlexRSS.png b/plugins/FlexRSS/FlexRSS.png new file mode 100644 index 000000000..e27f1a464 Binary files /dev/null and b/plugins/FlexRSS/FlexRSS.png differ diff --git a/plugins/FlexRSS/FlexRSS.svg b/plugins/FlexRSS/FlexRSS.svg new file mode 100644 index 000000000..c996707df --- /dev/null +++ b/plugins/FlexRSS/FlexRSS.svg @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/FlexRSS/__init__.py b/plugins/FlexRSS/__init__.py new file mode 100644 index 000000000..89ab7b865 --- /dev/null +++ b/plugins/FlexRSS/__init__.py @@ -0,0 +1,25 @@ +FlexRSS_DEFAULTS = { + 'VERSION': (0, 2, 3) +} + +plugin_name = "FlexRSS" +plugin_author = "Daddy" +plugin_version = "%d.%d.%d" % (FlexRSS_DEFAULTS['VERSION'][0], FlexRSS_DEFAULTS['VERSION'][1], FlexRSS_DEFAULTS['VERSION'][2]) +plugin_description = _(""" +Advanced RSS scraper + +This plugin is similar in purpose to Mark Adamson's excellent RSS Broadcatcher, but targets a more advanced audience. It is extremely powerful, configurable, unforgiving, and difficult. + +Filters use regular expressions with named patterns and strptime(3)-like format filters to match items. + +For more information and documentation, visit http://dev.deluge-torrent.org/wiki/Plugins/FlexRSS""") + +from FlexRSS.plugin import plugin_FlexRSS + +def deluge_init(deluge_path): + global path + path = deluge_path + +def enable(core, interface): + global path + return plugin_FlexRSS(path, core, interface, FlexRSS_DEFAULTS) diff --git a/plugins/SimpleRSS/feedparser.py b/plugins/FlexRSS/feedparser.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/SimpleRSS/feedparser.py rename to plugins/FlexRSS/feedparser.py diff --git a/plugins/FlexRSS/plugin.py b/plugins/FlexRSS/plugin.py new file mode 100644 index 000000000..b2f738929 --- /dev/null +++ b/plugins/FlexRSS/plugin.py @@ -0,0 +1,1732 @@ +def gtk_treeview_search_cb_stristr(model, column, key, iter, data=None): + value = model.get_value(iter, column) + if value.lower().find(key.lower()) > -1: + return False + else: + return True + +class plugin_FlexRSS_Config: + constants = { + 'Generic' : 0, + 'TV Show' : 1, + 'TV Show (dated)' : 2 } + feeds_counter = 0 + filters_counter = 0 + feeds = None + filters = None + version = 0 + show_toolbar_button = False + threaded_retrieval = False + + def getFilter(self, id): + if not self.filters: + return None + + for filter in self.filters: + if filter['id'] == id: + return filter + + return None + + def setFilter(self, id, key, value): + filter = self.getFilter(id) + + if not filter: + return False + else: + filter[key] = value + return True + + def addFilter(self, name, type, patterns, feeds): + id = self.feeds_counter + 1 + self.feeds_counter = id + + if not self.filters: + self.filters = [] + + filter = { "id" : id, + "name" : name, + "type" : type, + "patterns" : patterns, + "feeds" : feeds, + "history" : {}, + "path" : None, + "enabled" : True, + "queue_top": False, + "pause" : False, + "delete" : False} + + self.filters.append(filter) + + return id + + def deleteFilter(self, id): + if not self.filters: + return False + + i = 0 + while i < len(self.filters): + if self.filters[i]['id'] == id: + del self.filters[i] + return True + i = i + 1 + + def checkHistory(self, id, type, data): + filter = self.getFilter(id) + + if not filter: + return False + + try: + if type == self.constants['TV Show']: + filter['history'][type][data['series']].index(data['episode']) + return True + elif type == self.constants['TV Show (dated)']: + filter['history'][type][data['year']][data['month']].index(data['day']) + return True + else: + filter['history'][type].index(data['url']) + return True + except: + return False + + def checkRange(self, id, type, data): + filter = self.getFilter(id) + + if not filter: + return False + + if type == self.constants['TV Show']: + if filter['history'][type].has_key('from'): + if data['series'] < filter['history'][type]['from']['season']: + return False + elif data['series'] == filter['history'][type]['from']['season']: + if data['episode'] < filter['history'][type]['from']['episode']: + return False + + if filter['history'][type].has_key('thru'): + if data['series'] > filter['history'][type]['thru']['season']: + return False + elif data['series'] == filter['history'][type]['thru']['season']: + if data['episode'] > filter['history'][type]['thru']['episode']: + return False + + return True + + elif type == self.constants['TV Show (dated)']: + if filter['history'][type].has_key('from'): + if data['year'] < filter['history'][type]['from']['year']: + return False + elif data['year'] == filter['history'][type]['from']['year']: + if data['month'] < filter['history'][type]['from']['month']: + return False + elif data['month'] == filter['history'][type]['from']['month']: + if data['day'] < filter['history'][type]['from']['day']: + return False + + if filter['history'][type].has_key('thru'): + if data['year'] > filter['history'][type]['thru']['year']: + return False + elif data['year'] == filter['history'][type]['thru']['year']: + if data['month'] > filter['history'][type]['thru']['month']: + return False + elif data['month'] == filter['history'][type]['thru']['month']: + if data['day'] > filter['history'][type]['thru']['day']: + return False + + return True + + else: + return True + + def addHistory(self, id, type, data): + filter = self.getFilter(id) + + if not filter: + return False + + if type == self.constants['TV Show']: + if not filter['history'].has_key(type): + filter['history'][type] = {} + if not filter['history'][type].has_key(data['series']): + filter['history'][type][data['series']] = [] + filter['history'][type][data['series']].append(data['episode']) + elif type == self.constants['TV Show (dated)']: + if not filter['history'].has_key(type): + filter['history'][type] = {} + if not filter['history'][type].has_key(data['year']): + filter['history'][type][data['year']] = {} + if not filter['history'][type][data['year']].has_key(data['month']): + filter['history'][type][data['year']][data['month']] = [] + filter['history'][type][data['year']][data['month']].append(data['day']) + else: + if not filter['history'].has_key(type): + filter['history'][type] = [] + try: + filter['history'][type].append(data['url']) + except AttributeError, e: + filter['history'][type] = [data['url']] + + def getFeed(self, id): + if not self.feeds: + return None + + for feed in self.feeds: + if feed['id'] == id: + return feed + + return None + + def setFeed(self, id, key, value): + feed = self.getFeed(id) + + if not feed: + return False + else: + feed[key] = value + return True + + def addFeed(self, name, url, interval): + # Uhhh... no ++? WTF is the increment operator? + id = self.feeds_counter + 1 + self.feeds_counter = id + + if not self.feeds: + self.feeds = [] + + self.feeds.append({ "id" : id, + "name" : name, + "url" : url, + "interval" : interval, + "enabled" : True }) + + return id + + def deleteFeed(self, id): + if not self.feeds: + return False + + i = 0 + while i < len(self.feeds): + if self.feeds[i]['id'] == id: + del self.feeds[i] + return True + i = i + 1 + +class plugin_FlexRSS: + config = None + glade = None + feeds = None + toolbar_button = None + history_calendarbuttons = None + + def update_config(self): + if self.config.version >= 5: + return + + if self.config.version < 1: + print 'Updating config to v1' + i = 0 + while i < len(self.config.filters): + self.config.filters[i]['enabled'] = True + i = i + 1 + + if self.config.version < 2: + print 'Updating config to v2' + i = 0 + while i < len(self.config.feeds): + self.config.feeds[i]['enabled'] = True + i = i + 1 + + if self.config.version < 3: + print 'Updating config to v3' + i = 0 + while i < len(self.config.filters): + type_s = self.config.filters[i]['type'] + type_i = self.config.constants[type_s] + if self.config.filters[i]['history'].has_key(type_s): + history = { type_i: self.config.filters[i]['history'][type_s] } + self.config.filters[i]['history'] = history + self.config.filters[i]['type'] = self.config.constants[self.config.filters[i]['type']]; + i = i + 1 + + if self.config.version < 4: + print 'Updating config to v4' + i = 0 + while i < len(self.config.filters): + self.config.filters[i]['queue_top'] = False + self.config.filters[i]['pause'] = False + i = i + 1 + + if self.config.version < 5: + print 'Updating config to v5' + i = 0 + while i < len(self.config.filters): + self.config.filters[i]['delete'] = False + i = i + 1 + + self.config.version = 5 + self.write_config() + + def load_config(self): + import deluge.common, os, cookielib + + file = deluge.common.CONFIG_DIR + "/flexrss.dat" + if os.path.isfile(file): + import pickle + + fd = open(file, 'r') + self.config = pickle.load(fd) + fd.close() + else: + self.config = plugin_FlexRSS_Config() + self.config.version = 5 + + self.update_config() + + cookie_file = deluge.common.CONFIG_DIR + "/flexrss-cookies.txt" + self.cookies = cookielib.MozillaCookieJar() + if os.path.isfile(cookie_file): + self.cookies.load(cookie_file) + + def write_config(self, write_cookies=False): + import deluge.common, os, pickle + + file = deluge.common.CONFIG_DIR + "/flexrss.dat" + fd = open(file, 'w') + pickle.dump(self.config, fd) + fd.close() + + if write_cookies: + print "Saving cookies." + self.cookies.save(deluge.common.CONFIG_DIR + "/flexrss-cookies.txt") + + def configure_cb_closed(self, args): + self.glade = None + self.history_calendarbuttons = None + + def configure_cb_feed_new(self, args): + feed = { "name" : 'Untitled', + "url" : 'http://', + "interval" : 900, + "id" : 0 } + + feed["id"] = self.config.addFeed(feed["name"], feed["url"], feed["interval"]) + + view = self.glade.get_widget("FlexRSS_Feeds") + model = view.get_model() + view.get_selection().select_iter(model.append(None, (feed["id"], feed["name"], feed["url"]))) + + def configure_cb_feed_selected(self, selection): + model, iter = selection.get_selected() + + if not iter: + return + + parent = model.iter_parent(iter) + if parent: # Selected a filter + self.glade.get_widget('FlexRSS_Filters_Test_Pattern').set_text(model.get_value(iter, 1)) + self.configure_cb_test_filter(None) + + iter = parent + + # We want to edit a feed. + feed = self.config.getFeed(model.get_value(iter, 0)) + if not feed: + print 'Error: could not find feed #' + str(model.get_value(iter, 0)) + return + + self.glade.get_widget("FlexRSS_EditFeed_Name").set_text(feed["name"]) + self.glade.get_widget("FlexRSS_EditFeed_URL").set_text(feed["url"]) + self.glade.get_widget("FlexRSS_EditFeed_Interval").set_text(str(feed["interval"])) + if feed["enabled"] == True: + self.glade.get_widget("FlexRSS_EditFeed_Status_Enabled").set_active(True) + else: + self.glade.get_widget("FlexRSS_EditFeed_Status_Disabled").set_active(True) + self.glade.get_widget('FlexRSS_Feeds_Editor').show() + + def configure_cb_feed_delete(self, arg, id=None): + if not id: + # Which feed is selected? + selection = self.glade.get_widget("FlexRSS_Feeds").get_selection() + model, iter = selection.get_selected() + + if not iter: + return + + id = model.get_value(iter, 0) + if id == 0: + id = model.get_value(model.iter_parent(iter), 0) + else: + model = self.glade.get_widget("FlexRSS_Feeds").get_model() + iter = model.get_iter_first() + while iter: + if model.get_value(iter, 0) == id: + break + iter = model.iter_next(iter) + + if not iter: + print 'Couldn\'t find feed.' + return + + # Remove from config + if not self.config.deleteFeed(id): + print 'Unable to delete feed #' + str(id) + return + + # Remove from UI + model.remove(iter) + + self.write_config() + + # We are no longer editing a feed, so hide the editing widgets. + self.glade.get_widget('FlexRSS_Feeds_Editor').hide() + + def configure_cb_feed_save(self, arg, id=None): + if not id: + # Which feed is selected? + selection = self.glade.get_widget("FlexRSS_Feeds").get_selection() + model, iter = selection.get_selected() + + if not iter: + return + + id = model.get_value(iter, 0) + if id == 0: + iter = model.iter_parent(iter) + id = model.get_value(iter, 0) + else: + model = self.glade.get_widget("FlexRSS_Feeds").get_model() + iter = model.iter_first() + while iter: + if model.get_value(iter, 0) == id: + break + iter = iter.iter_next(iter) + + if not iter: + print 'Couldn\'t find feed.' + return + + # Update configuration + self.config.setFeed(id, "name", self.glade.get_widget("FlexRSS_EditFeed_Name").get_text()) + self.config.setFeed(id, "url", self.glade.get_widget("FlexRSS_EditFeed_URL").get_text()) + interval = self.configure_ui_get_text_as_int("FlexRSS_EditFeed_Interval", 900) + if interval < 300: + # It's just not polite to hit a server that often. + self.glade.get_widget("FlexRSS_EditFeed_Interval").set_text('300') + interval = 300 + self.config.setFeed(id, "interval", interval) + self.config.setFeed(id, "enabled", self.glade.get_widget("FlexRSS_EditFeed_Status_Enabled").get_active()) + self.write_config() + + # Update UI + feed = self.config.getFeed(id) + model.set_value(iter, 0, feed["id"]) + model.set_value(iter, 1, feed["name"]) + model.set_value(iter, 2, feed["url"]) + + if not self.feeds.has_key(id): + self.feeds[id] = { 'cfg' : feed, + 'updated' : 0, + 'data' : [] } + + def escape_regex_special_chars(self, pattern): + escape_chars = '[]()^$\\.?*+|' + out = [] + for c in pattern: + try: + escape_chars.index(c) + out.append('\\' + c) + except: + out.append(c) + return ''.join(out) + + def configure_cb_filter_new(self, arg, test_pattern=None): + filter = { "name" : "Untitled", + "type" : "Generic", + "patterns" : [('', 'Title')], + "feeds" : [0], + "id" : 0 } + + if test_pattern: + # Try to guess a good pattern + import re, string + + trans_table = string.maketrans(' ', '.') + + # TV Show + exp = re.compile(r'(.*?)S([0-9]+)E([0-9]+)', re.IGNORECASE) + match = exp.match(test_pattern) + if match: + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + 's%se%e' + filter['patterns'][0] = (pattern, 'Title') + filter['name'] = match.group(1) + filter['type'] = self.config.constants['TV Show'] + + if not match: + exp = re.compile(r'(.*?)([0-9]{4}).([0-9]{1,2}).([0-9]{1,2})', re.IGNORECASE) + match = exp.match(test_pattern) + if match: + pattern = None + if ((int(match.group(3)) <= 12) and (int(match.group(4)) <= 31)): + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%Y.%m.%d' + elif ((int(match.group(3)) <= 31) and (int(match.group(4)) <= 12)): + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%Y.%d.%m' + + if pattern: + filter['patterns'][0] = (pattern, 'Title') + filter['name'] = match.group(1) + filter['type'] = self.config.constants['TV Show (dated)'] + else: + match = None + + if not match: + exp = re.compile(r'(.*?)([0-9]{2}).([0-9]{2}).([0-9]{2})', re.IGNORECASE) + match = exp.match(test_pattern) + if match: + pattern = None + if ((int(match.group(2)) <= 12) and (int(match.group(3)) <= 31)): + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%m.%d.%y' + elif ((int(match.group(3)) <= 31) and (int(match.group(2)) <= 12)): + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%d.%m.%y' + + if pattern: + filter['patterns'][0] = (pattern, 'Title') + filter['name'] = match.group(1) + filter['type'] = self.config.constants['TV Show (dated)'] + else: + match = None + + if not match: + exp = re.compile(r'(.*?)([0-9]+)([x\.\-_]{1})([0-9]+)', re.IGNORECASE) + match = exp.match(test_pattern) + if match: + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%s' + self.escape_regex_special_chars(match.group(3)) + '%e' + filter['patterns'][0] = (pattern, 'Title') + filter['name'] = match.group(1) + filter['type'] = self.config.constants['TV Show'] + + if not match: + exp = re.compile(r'(.*?)([0-9]+)$', re.IGNORECASE) + match = exp.match(test_pattern) + if match: + pattern = self.escape_regex_special_chars(match.group(1)).lower().translate(trans_table) + '%e' + filter['patterns'][0] = (pattern, 'Title') + filter['name'] = match.group(1) + filter['type'] = self.config.constants['TV Show'] + + # Add to config + filter['id'] = self.config.addFilter(filter['name'], filter['type'], filter['patterns'], [0]) + + # Add to UI + self.configure_ui_add_filter(self.config.getFilter(filter['id']), test_pattern) + + def configure_cb_filter_selected(self, selection): + model, iter = selection.get_selected() + + if not iter: + return + + # We want to edit a filter. + filter = self.config.getFilter(model.get_value(iter, 0)) + if not filter: + print 'Error: could not find filter #' + str(model.get_value(iter, 0)) + return + + self.configure_ui_reset_filter() + + self.glade.get_widget('FlexRSS_Filters_Name').set_text(filter['name']) + + if filter['type'] == self.config.constants['TV Show']: + type_i = 1 + elif filter['type'] == self.config.constants['TV Show (dated)']: + type_i = 2 + else: + type_i = 0 + + self.glade.get_widget('FlexRSS_Filters_Type').set_active(type_i) + + selection = self.glade.get_widget('FlexRSS_Filters_Feed').get_selection() + selection.unselect_all() + feed_model = self.glade.get_widget('FlexRSS_Filters_Feed').get_model() + for i in filter['feeds']: + if i == 0: + selection.select_all() + else: + iter = feed_model.get_iter_first() + while iter: + if feed_model.get_value(iter, 0) == i: + selection.select_iter(iter) + iter = feed_model.iter_next(iter) + + for pattern in filter['patterns']: + self.configure_ui_add_pattern(pattern) + + if filter['type'] == self.config.constants['TV Show']: + if filter['history'].has_key(filter['type']) and filter['history'][filter['type']].has_key('from'): + self.glade.get_widget('FlexRSS_History_TVShow_From_Enabled').set_active(True) + self.glade.get_widget('FlexRSS_History_TVShow_From_Season').set_sensitive(True) + self.glade.get_widget('FlexRSS_History_TVShow_From_Episode').set_sensitive(True) + self.glade.get_widget('FlexRSS_History_TVShow_From_Season').set_text(str(filter['history'][filter['type']]['from']['season'])) + self.glade.get_widget('FlexRSS_History_TVShow_From_Episode').set_text(str(filter['history'][filter['type']]['from']['episode'])) + else: + self.glade.get_widget('FlexRSS_History_TVShow_From_Enabled').set_active(False) + self.glade.get_widget('FlexRSS_History_TVShow_From_Season').set_sensitive(False) + self.glade.get_widget('FlexRSS_History_TVShow_From_Episode').set_sensitive(False) + self.glade.get_widget('FlexRSS_History_TVShow_From_Season').set_text('0') + self.glade.get_widget('FlexRSS_History_TVShow_From_Episode').set_text('0') + + if filter['history'].has_key(filter['type']) and filter['history'][filter['type']].has_key('thru'): + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Enabled').set_active(True) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Season').set_sensitive(True) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Episode').set_sensitive(True) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Season').set_text(str(filter['history'][filter['type']]['thru']['season'])) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Episode').set_text(str(filter['history'][filter['type']]['thru']['episode'])) + else: + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Enabled').set_active(False) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Season').set_sensitive(False) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Episode').set_sensitive(False) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Season').set_text('0') + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Episode').set_text('0') + + self.glade.get_widget("FlexRSS_History_TVShow_Dated").hide() + self.glade.get_widget("FlexRSS_History_TVShow").show() + self.glade.get_widget("FlexRSS_Filter_History_Range").show() + elif filter['type'] == self.config.constants['TV Show (dated)']: + import time + + cal_from, cal_thru = self.history_calendarbuttons + today = time.localtime() + + if filter['history'].has_key(filter['type']) and filter['history'][filter['type']].has_key('from'): + self.glade.get_widget('FlexRSS_History_TVShow_Dated_From_Enabled').set_active(True) + cal_from.set_sensitive(True) + cal_from.set_date(filter['history'][filter['type']]['from']['year'], + filter['history'][filter['type']]['from']['month'], + filter['history'][filter['type']]['from']['day']) + else: + self.glade.get_widget('FlexRSS_History_TVShow_Dated_From_Enabled').set_active(False) + cal_from.set_sensitive(False) + cal_from.set_date(today[0], today[1], today[2]) + + if filter['history'].has_key(filter['type']) and filter['history'][filter['type']].has_key('thru'): + self.glade.get_widget('FlexRSS_History_TVShow_Dated_Thru_Enabled').set_active(True) + cal_thru.set_sensitive(True) + cal_thru.set_date(filter['history'][filter['type']]['thru']['year'], + filter['history'][filter['type']]['thru']['month'], + filter['history'][filter['type']]['thru']['day']) + else: + self.glade.get_widget('FlexRSS_History_TVShow_Dated_Thru_Enabled').set_active(False) + cal_thru.set_sensitive(False) + cal_thru.set_date(today[0], today[1], today[2]) + + self.glade.get_widget("FlexRSS_History_TVShow").hide() + self.glade.get_widget("FlexRSS_History_TVShow_Dated").show() + self.glade.get_widget("FlexRSS_Filter_History_Range").show() + else: + self.glade.get_widget("FlexRSS_History_TVShow").hide() + self.glade.get_widget("FlexRSS_History_TVShow_Dated").hide() + self.glade.get_widget("FlexRSS_Filter_History_Range").hide() + + self.glade.get_widget('FlexRSS_Filter_Download_QueueTop').set_active(filter.get('queue_top', False)) + self.glade.get_widget('FlexRSS_Filter_Download_Pause').set_active(filter.get('pause', False)) + self.glade.get_widget('FlexRSS_Filter_Download_Delete').set_active(filter.get('delete', False)) + + self.configure_cb_test_filter(None) + + if filter['path'] != None: + self.glade.get_widget('FlexRSS_Filter_Output_Location').set_current_folder(filter['path']) + self.glade.get_widget('FlexRSS_Filter_Output_Type_Custom').set_active(True) + else: + self.glade.get_widget('FlexRSS_Filter_Output_Type_Default').set_active(True) + + replace = filter.get('replace', {'pattern': '', 'with': ''}) + self.glade.get_widget('FlexRSS_Filter_Rewrite_Pattern').set_text(replace['pattern']) + self.glade.get_widget('FlexRSS_Filter_Rewrite_Replacement').set_text(replace['with']) + + def configure_cb_filter_history_toggle(self, box): + name = box.get_name() + active = box.get_active() + + if name == 'FlexRSS_History_TVShow_From_Enabled': + self.glade.get_widget('FlexRSS_History_TVShow_From_Season').set_sensitive(active) + self.glade.get_widget('FlexRSS_History_TVShow_From_Episode').set_sensitive(active) + elif name == 'FlexRSS_History_TVShow_Thru_Enabled': + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Season').set_sensitive(active) + self.glade.get_widget('FlexRSS_History_TVShow_Thru_Episode').set_sensitive(active) + elif name == 'FlexRSS_History_TVShow_Dated_From_Enabled': + self.history_calendarbuttons[0].set_sensitive(active) + elif name == 'FlexRSS_History_TVShow_Dated_Thru_Enabled': + self.history_calendarbuttons[1].set_sensitive(active) + + self.configure_cb_test_filter(None) + + def configure_ui_get_text_as_int(self, widget_name, default=0): + try: + return int(self.glade.get_widget(widget_name).get_text()) + except: + return default + + def configure_ui_get_history_restriction(self): + h_from = None + h_thru = None + + type = self.glade.get_widget('FlexRSS_Filters_Type').get_active() + if type == 1: + type_s = self.config.constants['TV Show'] + elif type == 2: + type_s = self.config.constants['TV Show (dated)'] + else: + type_s = self.config.constants['Generic'] + + if type_s == self.config.constants['TV Show']: + if self.glade.get_widget('FlexRSS_History_TVShow_From_Enabled').get_active(): + from_season = self.configure_ui_get_text_as_int('FlexRSS_History_TVShow_From_Season') + from_episode = self.configure_ui_get_text_as_int('FlexRSS_History_TVShow_From_Episode') + h_from = {'season': from_season, 'episode': from_episode} + elif type_s == self.config.constants['TV Show (dated)']: + if self.glade.get_widget('FlexRSS_History_TVShow_Dated_From_Enabled').get_active(): + from_y, from_m, from_d = self.history_calendarbuttons[0].get_date() + h_from = {'year': from_y, 'month': from_m, 'day': from_d} + + if type_s == self.config.constants['TV Show']: + if self.glade.get_widget('FlexRSS_History_TVShow_Thru_Enabled').get_active(): + thru_season = self.configure_ui_get_text_as_int('FlexRSS_History_TVShow_Thru_Season') + thru_episode = self.configure_ui_get_text_as_int('FlexRSS_History_TVShow_Thru_Episode') + h_thru = {'season': thru_season, 'episode': thru_episode} + elif type_s == self.config.constants['TV Show (dated)']: + if self.glade.get_widget('FlexRSS_History_TVShow_Dated_Thru_Enabled').get_active(): + thru_y, thru_m, thru_d = self.history_calendarbuttons[1].get_date() + h_thru = {'year': thru_y, 'month': thru_m, 'day': thru_d} + + return (h_from, h_thru) + + def configure_cb_filter_save(self, arg): + # Which feed is selected? + selection = self.glade.get_widget("FlexRSS_Filters_List").get_selection() + model, iter = selection.get_selected() + + if not iter: + return + + id = model.get_value(iter, 0) + name = self.glade.get_widget('FlexRSS_Filters_Name').get_text() + self.config.setFilter(id, 'name', name) + iter = model.get_iter_first() + while iter: + if model.get_value(iter, 0) == id: + model.set_value(iter, 1, name) + iter = model.iter_next(iter) + + type = self.glade.get_widget('FlexRSS_Filters_Type').get_active() + if type == 1: + type_s = self.config.constants['TV Show'] + elif type == 2: + type_s = self.config.constants['TV Show (dated)'] + else: + type_s = self.config.constants['Generic'] + self.config.setFilter(id, 'type', type_s) + + model, paths = self.glade.get_widget('FlexRSS_Filters_Feed').get_selection().get_selected_rows() + feeds = [] + for path in paths: + feeds.append(model.get_value(model.get_iter(path), 0)) + if len(feeds) == 0: + feeds.append(0) + self.config.setFilter(id, 'feeds', feeds) + + patterns = [] + filter_patterns = self.glade.get_widget('FlexRSS_Filter_Patterns_List') + for i in filter_patterns.get_children(): + data = i.get_children() + pattern = data[0].get_text() + if data[2].get_active() == 1: + type = 'Link' + else: + type = 'Title' + patterns.append((pattern, type)) + self.config.setFilter(id, 'patterns', patterns) + + if self.glade.get_widget('FlexRSS_Filter_Output_Type_Custom').get_active(): + path = self.glade.get_widget('FlexRSS_Filter_Output_Location').get_current_folder() + else: + path = None + + self.config.setFilter(id, 'path', path) + + self.config.setFilter(id, 'queue_top', self.glade.get_widget('FlexRSS_Filter_Download_QueueTop').get_active()) + self.config.setFilter(id, 'pause', self.glade.get_widget('FlexRSS_Filter_Download_Pause').get_active()) + self.config.setFilter(id, 'delete', self.glade.get_widget('FlexRSS_Filter_Download_Delete').get_active()) + + filter = self.config.getFilter(id) + + h_from, h_thru = self.configure_ui_get_history_restriction() + if filter['type'] != self.config.constants['Generic']: + if not filter['history'].has_key(filter['type']): + filter['history'][filter['type']] = {} + + if h_from != None: + filter['history'][filter['type']]['from'] = h_from + else: + if filter['history'][filter['type']].has_key('from'): + del filter['history'][filter['type']]['from'] + + if h_thru != None: + filter['history'][filter['type']]['thru'] = h_thru + else: + if filter['history'][filter['type']].has_key('thru'): + del filter['history'][filter['type']]['thru'] + + filter['replace'] = { 'pattern': self.glade.get_widget('FlexRSS_Filter_Rewrite_Pattern').get_text(), + 'with' : self.glade.get_widget('FlexRSS_Filter_Rewrite_Replacement').get_text() } + + self.write_config() + + def configure_cb_remove_pattern(self, arg): + arg.get_parent().destroy() + + def configure_ui_add_pattern(self, pattern): + import gtk, gobject + + filter_patterns = self.glade.get_widget('FlexRSS_Filter_Patterns_List') + + hbox = gtk.HBox() + filter_patterns.pack_start(hbox) + + input = gtk.Entry() + input.set_text(pattern[0]) + input.connect("changed", self.configure_cb_test_filter) + + on = gtk.Label() + on.set_text('On') + on.show() + + combo = gtk.combo_box_new_text() + combo.append_text('Title') + combo.append_text('Link') + if pattern[1] == 'Link': + combo.set_active(1) + else: + combo.set_active(0) + + remove = gtk.Button(stock=gtk.STOCK_REMOVE) + remove.connect("pressed", self.configure_cb_remove_pattern) + + hbox.pack_start(input) + hbox.pack_start(on, expand=False) + hbox.pack_start(combo, expand=False) + hbox.pack_start(remove, expand=False) + + hbox.show_all() + + def configure_cb_add_pattern(self, args): + self.configure_ui_add_pattern(('', 'Title')) + + def configure_ui_add_filter(self, filter, test_pattern=None): + if not filter: + print 'No filter to add' + return None + + self.configure_ui_reset_filter() + + model = self.glade.get_widget("FlexRSS_Filters_List").get_model() + iter = model.append((filter['id'], filter['name'], filter['enabled'])) + + if not iter: + return + + view = self.glade.get_widget("FlexRSS_Filters_List") + model = view.get_model() + view.get_selection().select_iter(iter) + + if test_pattern: + self.glade.get_widget('FlexRSS_Filters_Test_Pattern').set_text(test_pattern) + self.configure_cb_test_filter(None) + + self.glade.get_widget('FlexRSS_MainNotebook').set_current_page(1) + + def configure_ui_test_result(self, result, h_match=False): + if result and h_match: + self.glade.get_widget('FlexRSS_Filters_Test_Result').set_text('Match') + else: + self.glade.get_widget('FlexRSS_Filters_Test_Result').set_text('Doesn\'t match') + + type = self.glade.get_widget('FlexRSS_Filters_Type').get_active() + + if type == 0: # Generic + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow').hide() + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated').hide() + self.glade.get_widget('FlexRSS_History_TVShow').hide() + self.glade.get_widget('FlexRSS_History_TVShow_Dated').hide() + elif type == 1: # TV Show + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow').show() + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated').hide() + self.glade.get_widget('FlexRSS_History_TVShow').show() + self.glade.get_widget('FlexRSS_History_TVShow_Dated').hide() + if result: + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Series').set_text(str(result['series'])) + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Episode').set_text(str(result['episode'])) + else: + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Series').set_text('0') + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Episode').set_text('0') + else: # TV Show (dated) + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow').hide() + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated').show() + self.glade.get_widget('FlexRSS_History_TVShow').hide() + self.glade.get_widget('FlexRSS_History_TVShow_Dated').show() + if result: + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Year').set_text(str(result['year'])) + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Month').set_text(str(result['month'])) + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Day').set_text(str(result['day'])) + else: + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Year').set_text('0') + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Month').set_text('0') + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated_Day').set_text('0') + + def configure_cb_test_filter(self, source=None, date=None): + subject = self.glade.get_widget('FlexRSS_Filters_Test_Pattern').get_text() + + type = self.glade.get_widget('FlexRSS_Filters_Type').get_active() + if type == 1: + type_s = self.config.constants['TV Show'] + elif type == 2: + type_s = self.config.constants['TV Show (dated)'] + else: + type_s = self.config.constants['Generic'] + + filter_patterns = self.glade.get_widget('FlexRSS_Filter_Patterns_List') + for i in filter_patterns.get_children(): + data = i.get_children() + pattern = data[0].get_text() + result = self.test_pattern(type_s, subject, pattern) + if result: + # Pattern matched, let's try history restriction. + h_from, h_thru = self.configure_ui_get_history_restriction() + h_match = self.test_range(type_s, result, h_from, h_thru) + self.configure_ui_test_result(result, h_match) + return + + self.configure_ui_test_result(False) + + def configure_cb_delete_filter(self, arg): + # Which filter is selected? + selection = self.glade.get_widget("FlexRSS_Filters_List").get_selection() + model, iter = selection.get_selected() + + if not iter: + return + + id = model.get_value(iter, 0) + + # Remove from config + if not self.config.deleteFilter(id): + print 'Unable to delete filter #' + str(id) + return + self.write_config() + + # Remove from UI + model.remove(iter) + + self.configure_ui_reset_filter() + + def configure_cb_feed_refresh(self, caller, id=None): + if not id: + selection = self.glade.get_widget("FlexRSS_Feeds").get_selection() + model, iter = selection.get_selected() + + if not iter: + return + + id = model.get_value(iter, 0) + + if id: + if ( self.config.threaded_retrieval ): + import threading + threading.Thread(target=self.parse_feed, args=(id,)).start() + else: + self.parse_feed(id) + + def configure_cb_download_torrent(self, caller, url): + self.download_torrent(url) + + def configure_cb_feed_popup(self, view, event): + if event.button != 3: + return + + model = view.get_model() + coords = event.get_coords() + path = view.get_path_at_pos(int(coords[0]), int(coords[1])) + if path: + iter = model.get_iter(path[0]) + else: + iter = None + + import gtk + + popup = gtk.Menu() + + if iter: + id = model.get_value(iter, 0) + if id: # Feed + item_refresh = gtk.MenuItem(_("Refresh feed")) + item_refresh.connect("activate", self.configure_cb_feed_refresh, id) + popup.append(item_refresh) + + item_delete = gtk.MenuItem(_("Delete feed")) + item_delete.connect("activate", self.configure_cb_feed_delete, id) + popup.append(item_delete) + + else: # Filter + item_filter = gtk.MenuItem(_("Create filter")) + item_filter.connect("activate", self.configure_cb_filter_new, model.get_value(iter, 1)) + popup.append(item_filter) + + item_download = gtk.MenuItem(_("Download torrent")) + item_download.connect("activate", self.configure_cb_download_torrent, model.get_value(iter, 2)) + popup.append(item_download) + + else: # Neither + item_new = gtk.MenuItem("New feed") + item_new.connect("activate", self.configure_cb_feed_new) + popup.append(item_new) + + popup.popup(None, None, None, event.button, event.time) + popup.show_all() + + def configure_cb_output_set(self, chooser): + self.glade.get_widget('FlexRSS_Filter_Output_Type_Custom').set_active(True) + + def configure_ui_reset_filter(self): + # Just resets the crap in the filter tab to defaults. + self.glade.get_widget('FlexRSS_Filters_Name').set_text('') + self.glade.get_widget('FlexRSS_Filters_Type').set_active(0) + self.glade.get_widget('FlexRSS_Filters_Feed').get_selection().unselect_all() + for filter in self.glade.get_widget('FlexRSS_Filter_Patterns_List').get_children(): + filter.destroy() + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow').hide() + self.glade.get_widget('FlexRSS_Filters_Test_Results_TVShow_Dated').hide() + file_chooser = self.glade.get_widget('FlexRSS_Filter_Output_Location') + file_chooser.set_current_folder(self.interface.config.get('default_download_path')) + self.glade.get_widget('FlexRSS_Filter_Output_Type_Default').set_active(True) + + def strcasecmp(self, s1, s2): + try: + t1 = s1.lower() + t2 = s2.lower() + + if s1 < s2: + return -1 + elif s1 > s2: + return 1 + except: + pass + + return 0 + + def configure_ui_sort_cmp(self, model, iter1, iter2, user_data=None): + if (model.get_value(iter1, 0) == 0) or (model.get_value(iter2, 0) == 0): + return 0 + + return self.strcasecmp(model.get_value(iter1, 1), model.get_value(iter2, 1)) + + def configure_cb_filter_toggled(self, renderer, path): + old_val = renderer.get_active() + model = self.glade.get_widget('FlexRSS_Filters_List').get_model() + iter = model.get_iter(path) + if old_val: + model.set_value(iter, 2, False) + self.config.setFilter(model.get_value(iter, 0), 'enabled', False) + else: + model.set_value(iter, 2, True) + self.config.setFilter(model.get_value(iter, 0), 'enabled', True) + self.write_config() + + def configure_cb_cookie_new(self, src): + self.configure_ui_cookie_reset() + self.glade.get_widget("FlexRSS_Cookies_Editor").show() + + def configure_cb_cookie_save(self, src): + import cookielib, time + + domain = self.glade.get_widget("FlexRSS_Cookie_Domain").get_text() + path = self.glade.get_widget("FlexRSS_Cookie_Path").get_text() + name = self.glade.get_widget("FlexRSS_Cookie_Name").get_text() + value = self.glade.get_widget("FlexRSS_Cookie_Value").get_text() + + if (domain == ""): + return + + if path == '': + path = '/' + + # Fragile. + self.cookies._policy._now = self.cookies._now = int(time.time()) + cookie = self.cookies._cookie_from_cookie_tuple((name, value, {"domain": domain, "path":path, "expires":2147483647}, {}), None) + + self.cookies.set_cookie(cookie) + self.configure_ui_add_cookie(None, cookie) + self.configure_ui_cookie_reset() + self.glade.get_widget("FlexRSS_Cookies_Editor").hide() + self.write_config(True) + + def configure_ui_add_cookie(self, model, cookie): + if cookie.domain[0] == '.': + domain = cookie.domain[1:] + else: + domain = cookie.domain + + if model == None: + model = self.glade.get_widget("FlexRSS_Cookies_List").get_model() + + parent = None + iter = model.get_iter_first() + while iter: + t = model.get_value(iter, 0) + if t == domain: + parent = iter + break + iter = model.iter_next(iter) + + if parent == None: + parent = model.append(iter, (domain, "", "", "")) + model.append(parent, (cookie.domain, cookie.path, cookie.name, cookie.value)) + + def configure_ui_cookie_reset(self): + self.glade.get_widget("FlexRSS_Cookie_Domain").set_text('') + self.glade.get_widget("FlexRSS_Cookie_Path").set_text('/') + self.glade.get_widget("FlexRSS_Cookie_Name").set_text('') + self.glade.get_widget("FlexRSS_Cookie_Value").set_text('') + + def configure_cb_cookie_selected(self, selection): + model, iter = selection.get_selected() + + self.configure_ui_cookie_reset() + self.glade.get_widget("FlexRSS_Cookies_Editor").hide() + + def configure_cb_cookie_delete(self, src): + selection = self.glade.get_widget("FlexRSS_Cookies_List").get_selection() + model, iter = selection.get_selected() + + domain = None + path = None + name = None + + if not model.iter_has_child(iter): + path = model.get_value(iter, 1) + name = model.get_value(iter, 2) + domain = model.get_value(iter, 0) + + try: + self.cookies.clear("." + domain, path, name) + except: + pass + try: + self.cookies.clear(domain, path, name) + except: + pass + + # UI + if model.iter_has_child(iter): + i = model.iter_children(iter) + while model.remove(i): + pass + model.remove(iter) + else: + p = model.iter_parent(iter) + model.remove(iter) + if not model.iter_has_child(p): + model.remove(p) + + self.write_config(True) + + def configure_cb_toolbar_clicked(self, button): + self.configure() + + def configure_ui_show_toolbar_button(self): + if self.toolbar_button == None: + import gtk + + icon = gtk.Image() + icon.set_from_file(self.path + "/FlexRSS.png") + self.toolbar_button = gtk.ToolButton(icon_widget=icon, label="FlexRSS") + self.toolbar_button.connect("clicked", self.configure_cb_toolbar_clicked) + self.interface.toolbar.add(self.toolbar_button) + self.toolbar_button.show_all() + + def configure_cb_toolbar_toggled(self, box): + if box.get_active(): + self.configure_ui_show_toolbar_button() + self.config.show_toolbar_button = True + self.write_config() + else: + self.toolbar_button.destroy() + self.toolbar_button = None + self.config.show_toolbar_button = False + self.write_config() + + def configure_cb_threaded_toggled(self, box): + self.config.threaded_retrieval = box.get_active() + self.write_config() + + def configure(self, widget=None): + if self.glade: # Dialog already running + return + + import gtk, gtk.glade, gobject + + self.glade = gtk.glade.XML(self.path + "/FlexRSS.glade") + + # Intialize feed lists + feeds_model = gtk.TreeStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING) + feeds_view = self.glade.get_widget("FlexRSS_Feeds") + filters_feeds_view = self.glade.get_widget("FlexRSS_Filters_Feed") + feeds_view.set_model(feeds_model) + filters_feeds_view.set_model(feeds_model) + feeds_model.set_sort_func(1, self.configure_ui_sort_cmp) + feeds_model.set_sort_column_id(1, gtk.SORT_ASCENDING) + + # Setup columns for feeds tab + renderer_name = gtk.CellRendererText() + column_name = gtk.TreeViewColumn(_("Feed Name"), renderer_name, text=1) + feeds_view.append_column(column_name) + renderer_url = gtk.CellRendererText() + column_url = gtk.TreeViewColumn(_("URL"), renderer_url, text=2) + feeds_view.append_column(column_url) + feeds_view.set_search_column(1) + feeds_view.set_search_equal_func(gtk_treeview_search_cb_stristr) + + # Set callback for when selection is changed in feeds tab + # I can't figure out how to do this in Glade + feeds_view.get_selection().connect("changed", self.configure_cb_feed_selected) + + # Setup columns for feed list on filters tab + renderer_name = gtk.CellRendererText() + column_name = gtk.TreeViewColumn(_("Feed Name"), renderer_name, text=1) + filters_feeds_view.append_column(column_name) + + # Allow multiple selections of feeds in filters tab + # I can't figure out how to do this in Glade + filters_feeds_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + # Populate feed lists + if self.config.feeds: + for feed in self.config.feeds: + this_feed = feeds_model.append(None, (feed['id'], feed['name'], feed['url'])) + for item in self.feeds[feed['id']]['data']: + feeds_model.append(this_feed, (0, item['title'], item['link'])) + + + # Initialize filters list + filters_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + filters_view = self.glade.get_widget("FlexRSS_Filters_List") + filters_view.set_model(filters_model) + feeds_model.set_sort_func(1, self.configure_ui_sort_cmp) + filters_model.set_sort_column_id(1, gtk.SORT_ASCENDING) + + # Setup columns for filters list + renderer_enabled = gtk.CellRendererToggle() + column_enabled = gtk.TreeViewColumn(_("Enabled"), renderer_enabled, active=2) + filters_view.append_column(column_enabled) + renderer_enabled.connect('toggled', self.configure_cb_filter_toggled) + renderer_name = gtk.CellRendererText() + column_name = gtk.TreeViewColumn(_("Filter Name"), renderer_name, text=1) + filters_view.append_column(column_name) + + # Set callback for when selection is changed in feeds tab + # I can't figure out how to do this in Glade + filters_view.get_selection().connect("changed", self.configure_cb_filter_selected) + + # Populate filters list + if self.config.filters: + for filter in self.config.filters: + filters_model.append((filter['id'], filter['name'], filter['enabled'])) + + # Filter types + filter_types = self.glade.get_widget('FlexRSS_Filters_Type') + filter_types.append_text(_("Generic")) + filter_types.append_text(_("TV Show")) + filter_types.append_text(_("TV Show (dated)")) + + # Calendar buttons for dated tv show history restrictions + from CalendarButton import CalendarButton + dated_from = CalendarButton() + dated_from.connect('date-selected', self.configure_cb_test_filter) + dated_to = CalendarButton() + dated_to.connect('date-selected', self.configure_cb_test_filter) + dated_from.show() + dated_to.show() + history_table = self.glade.get_widget('FlexRSS_History_TVShow_Dated') + history_table.attach(dated_from, 1, 2, 0, 1, gtk.EXPAND|gtk.FILL, gtk.FILL) + history_table.attach(dated_to, 1, 2, 1, 2, gtk.EXPAND|gtk.FILL, gtk.FILL) + self.history_calendarbuttons = (dated_from, dated_to) + + # Initialize cookies list + cookies_model = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) + cookies_view = self.glade.get_widget("FlexRSS_Cookies_List") + cookies_view.set_model(cookies_model) + cookies_view.get_selection().connect("changed", self.configure_cb_cookie_selected) + + # Setup column for cookies + domain_renderer = gtk.CellRendererText() + domain_column = gtk.TreeViewColumn(_("Domain"), domain_renderer, text=0) + path_renderer = gtk.CellRendererText() + path_column = gtk.TreeViewColumn(_("Path"), path_renderer, text=1) + name_renderer = gtk.CellRendererText() + name_column = gtk.TreeViewColumn(_("Name"), name_renderer, text=2) + value_renderer = gtk.CellRendererText() + value_column = gtk.TreeViewColumn(_("Value"), value_renderer, text=3) + cookies_view.append_column(domain_column) + cookies_view.append_column(path_column) + cookies_view.append_column(name_column) + cookies_view.append_column(value_column) + + for cookie in self.cookies: + self.configure_ui_add_cookie(cookies_model, cookie) + + # Interface + self.glade.get_widget("FlexRSS_Interface_ShowButton").set_active(self.config.show_toolbar_button) + self.glade.get_widget("FlexRSS_Retrieval_Threaded").set_active(self.config.threaded_retrieval) + + # Callbacks for UI events + actions = { + # Feeds tab + "on_FlexRSS_MainWindow_destroy" : self.configure_cb_closed, + "on_FlexRSS_Feeds_New_pressed" : self.configure_cb_feed_new, + "on_FlexRSS_Feeds_Save_pressed" : self.configure_cb_feed_save, + "on_FlexRSS_Feeds_Delete_pressed" : self.configure_cb_feed_delete, + "on_FlexRSS_Feeds_button_press_event" : self.configure_cb_feed_popup, + + # Filters tab + "on_FlexRSS_Filters_Add_pressed" : self.configure_cb_filter_new, + "on_FlexRSS_Action_Save_pressed" : self.configure_cb_filter_save, + "on_FlexRSS_Filter_Patern_Add_pressed" : self.configure_cb_add_pattern, + "on_FlexRSS_Filters_Type_changed" : self.configure_cb_test_filter, + "on_FlexRSS_Filters_Test_Pattern" : self.configure_cb_test_filter, + "on_FlexRSS_Filters_Delete_pressed" : self.configure_cb_delete_filter, + "on_FlexRSS_History_TVShow_From_Enabled_toggled" : self.configure_cb_filter_history_toggle, + "on_FlexRSS_History_TVShow_Thru_Enabled_toggled" : self.configure_cb_filter_history_toggle, + "on_FlexRSS_History_TVShow_Dated_From_Enabled_toggled" : self.configure_cb_filter_history_toggle, + "on_FlexRSS_History_TVShow_Dated_Thru_Enabled_toggled" : self.configure_cb_filter_history_toggle, + "on_FlexRSS_History_TVShow_From_Season_changed" : self.configure_cb_test_filter, + "on_FlexRSS_History_TVShow_From_Episode_changed" : self.configure_cb_test_filter, + "on_FlexRSS_History_TVShow_Thru_Season_changed" : self.configure_cb_test_filter, + "on_FlexRSS_History_TVShow_Thru_Episode_changed" : self.configure_cb_test_filter, + + # Configuration tab + "on_FlexRSS_Cookie_New_pressed" : self.configure_cb_cookie_new, + "on_FlexRSS_Cookie_Save_pressed" : self.configure_cb_cookie_save, + "on_FlexRSS_Cookie_Delete_pressed" : self.configure_cb_cookie_delete, + "on_FlexRSS_Interface_ShowButton_toggled" : self.configure_cb_toolbar_toggled, + "on_FlexRSS_Retrieval_Threaded_toggled" : self.configure_cb_threaded_toggled } + if hasattr(self.interface, 'interactive_add_torrent_path'): + actions["on_FlexRSS_Filter_Output_Location_current_folder_changed"] = self.configure_cb_output_set + else: + self.glade.get_widget('FlexRSS_Filter_Output').hide() + self.glade.signal_autoconnect(actions) + + self.glade.get_widget("FlexRSS_MainWindow").show() + + def cmp_history(a, b): + try: + if a.has_key('series'): + if a['series'] > b['series']: + return 1 + elif a['series'] < b['series']: + return -1 + else: + if a['episode'] > b['episode']: + return 1 + elif a['episode'] < b['episode']: + return -1 + else: + return 0 + except: + return 0 + + def strptime2regex(self, input): + # Does'n exactly live up to its name yet. Currently just + # replaces %y,%Y,%m,%d with named patterns. In the future, it + # would be nice to allow escaping (e.g., %%Y means literal %Y) + # and expand it to support other formats. + patterns = [('%Y', '(?P[0-9]{4})'), + ('%y', '(?P[0-9]{2})'), + ('%m', '(?P[0-9]{1,2})'), + ('%d', '(?P[0-9]{1,2})')] + + out = input + for p in patterns: + out = out.replace(p[0], p[1]) + + return out + + def replace_tv_show_patterns(self, input): + patterns = [('%s', '(?P[0-9]+)'), + ('%e', '(?P[0-9]+)')] + + out = input + for p in patterns: + out = out.replace(p[0], p[1]) + + return out + + def test_range(self, type, data, h_from=None, h_thru=None): + if type == self.config.constants['TV Show']: + if h_from != None: + if data['series'] < h_from['season']: + return False + elif data['series'] == h_from['season']: + if data['episode'] < h_from['episode']: + return False + + if h_thru != None: + if data['series'] > h_thru['season']: + return False + elif data['series'] == h_thru['season']: + if data['episode'] > h_thru['episode']: + return False + + return True + + elif type == self.config.constants['TV Show (dated)']: + if h_from != None: + if data['year'] < h_from['year']: + return False + elif data['year'] == h_from['year']: + if data['month'] < h_from['month']: + return False + elif data['month'] == h_from['month']: + if data['day'] < h_from['day']: + return False + + if h_thru != None: + if data['year'] < h_thru['year']: + return False + elif data['year'] == h_thru['year']: + if data['month'] < h_thru['month']: + return False + elif data['month'] == h_thru['month']: + if data['day'] < h_thru['day']: + return False + + return True + + else: + return True + + def test_pattern(self, type, subject, pattern): + import re, time + result = False + + if len(pattern) < 1: + return False + + if type == self.config.constants['TV Show (dated)']: + pattern = self.strptime2regex(pattern) + elif type == self.config.constants['TV Show']: + pattern = self.replace_tv_show_patterns(pattern) + + # Wow, so this is lame... + if pattern[0] != '^': + pattern = '.*' + pattern + + try: + exp = re.compile(pattern, re.IGNORECASE) + except: + print 'Broken expression: ' + pattern + return False + + match = exp.match(subject) + if match: +# print 'Match: ' + subject +# print ' ' + pattern + if type == self.config.constants['TV Show']: + try: + series = int(match.group('s')) + except: + series = 0 + + try: + episode = int(match.group('e')) + except: + episode = 0 + + result = { 'series' : series, + 'episode' : episode } + + elif type == self.config.constants['TV Show (dated)']: + try: + year = int(match.group('Y')) + except: + try: + year = int(match.group('y')) + if year > 70: + year += 1900 + else: + year += 2000 + except: + year = 0 + + try: + month = int(match.group('m')) + except: + month = 0 + + try: + day = int(match.group('d')) + except: + day = 0 + + result = { 'year' : year, + 'month' : month, + 'day' : day } + + else: + result = True + + return result + + def make_opener(self): + import urllib2 + + return urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookies)) + + def make_request(self, location, referer=None): + import urllib2, deluge.common + + req = urllib2.Request(location) + req.add_header("User-Agent", "FlexRSS/%d.%d.%d (%s/%s)" % (self.version[0], self.version[1], self.version[2], deluge.common.PROGRAM_NAME, deluge.common.PROGRAM_VERSION)) + if referer != None: + req.add_header('Referer', referer) + + return req + + def open_url(self, location): + return self.make_opener().open(self.make_request(location)) + + def download_torrent(self, location, path=None, queue_top=False, pause=False, referer=None): + import tempfile, os, deluge.core + + try: + fd = self.open_url(location) + except: + return False + + inf = fd.info() + if inf["content-type"] != "application/x-bittorrent": + print "Warning: torrent content-type not application/x-bittorrent" + + (tmpfd, filename) = tempfile.mkstemp(".torrent", "flexrss-") + tfd = os.fdopen(tmpfd, 'wb') + tfd.write(fd.read()) + fd.close() + tfd.close() + + unique_id = False + + try: + if path is None: + path = self.interface.config.get('default_download_path') + unique_id = self.interface.manager.add_torrent(filename, path, self.interface.config.get('use_compact_storage')) + self.interface.torrent_model_append(unique_id) + + if queue_top == True: + try: + self.interface.manager.queue_top(unique_id) + except: + pass + if pause == True: + try: + self.interface.manager.set_user_pause(unique_id, True) + except: + pass + except deluge.core.DuplicateTorrentError, e: + print 'FlexRSS: torrent already exists.' + return True + except: + print '*** FlexRSS error: unable to add torrent.' + return False + + return True + + def parse_feed(self, id): + import time, feedparser, urllib2, cookielib + + feed = self.config.getFeed(id) + + #print 'Parsing feed: ' + feed['url'] + try: + parsed = feedparser.parse(self.open_url(feed['url'])) + if (not parsed) or (not parsed.entries): + print 'Unable to parse feed: ' + feed['url'] + return + except: + print 'Unable to update feed: ' + feed['url'] + return + #print 'Retrieval successful: ' + feed['url'] + + data = [] + + for entry in parsed.entries: + entryTitle = entry['title'] + + try: + entryLink = entry.links[0]['href'] + except: + try: + entryLink = entry.enclosures[0]['href'] + except: + print "Skipping %s\n" % entryTitle + continue + + try: + data.append({ 'title': entryTitle, 'link': entryLink }) + except: + continue + + if self.config.filters: + for filter in self.config.filters: + try: + if not ((feed['id'] in filter['feeds']) or (0 in filter['feeds'])): + continue + except TypeError: + filter['feeds'] = [0] + + if filter['enabled'] != True: + continue + + for pattern in filter['patterns']: + # (setq python-indent 4 + # tab-width 4) + # Okay, I dislike python substantially less now. + # Edit (~a month later): I still dislike it a lot, though. + try: + if pattern[1] == 'Title': + subject = entryTitle + else: + subject = entryLink + except: + print 'Unable to find subject.' + + match = self.test_pattern(filter['type'], subject, pattern[0]) + if match: + # Filter matched. Check history to see if + # we should download it. + if filter['type'] == self.config.constants['Generic']: # Dirty hack. + match = { 'url' : entryLink } + + torrent_url = entryLink + replace = filter.get('replace', {'pattern': '', 'with': ''}) + if len(replace['pattern']) > 0: + try: + import re + + p = re.compile(replace['pattern'], re.IGNORECASE) + torrent_url = p.sub(replace['with'], torrent_url) + except: + print '*** FlexRSS error: s/%s/%s/i failed.' % (replace['pattern'], replace['with']) + return + + if not self.config.checkHistory(filter['id'], filter['type'], match): + print filter + if filter.has_key('history'): + res = False + + if filter['type'] == self.config.constants['Generic']: + if filter['history'].get(filter['type'], []).count(torrent_url) == 0: + res = self.download_torrent(torrent_url, filter.get('path', None), filter['queue_top'], filter['pause'], feed['url']) + else: + h_from = filter['history'][filter['type']].get('from', None) + h_thru = filter['history'][filter['type']].get('thru', None) + if self.test_range(filter['type'], match, h_from, h_thru): + res = self.download_torrent(torrent_url, filter.get('path', None), filter['queue_top'], filter['pause'], feed['url']) + + print res + if res == True: + if filter['delete'] == True: + self.config.deleteFilter(filter['id']) + self.write_config() + + model = self.glade.get_widget("FlexRSS_Filters_List").get_model() + iter = model.get_iter_first(); + while iter != None: + if model.get_value(iter, 0) == filter['id']: + model.remove(iter) + break + iter = model.iter_next(iter) + else: + self.config.addHistory(filter['id'], filter['type'], match) + self.write_config() + + self.feeds[feed['id']]['data'] = data + if self.glade: # Update config window... + feeds_model = self.glade.get_widget("FlexRSS_Feeds").get_model() + iter = feeds_model.get_iter_first() + while iter != None: + if id == feeds_model.get_value(iter, 0): + break + iter = feeds_model.iter_next(iter) + + i = feeds_model.iter_children(iter) + if i != None: + while feeds_model.remove(i): + pass + + for item in data: + feeds_model.append(iter, (0, item['title'], item['link'])) + + def update(self): + import time, threading + + current_time = time.time() + + # I feel dirty for this. Oh, how I miss C. + for id in self.feeds: + if self.feeds[id]['cfg']['enabled'] == True: + if (current_time - self.feeds[id]['cfg']['interval']) > self.feeds[id]['updated']: + self.feeds[id]['updated'] = current_time + if ( self.config.threaded_retrieval ): + threading.Thread(target=self.parse_feed, args=(self.feeds[id]['cfg']['id'],)).start() + else: + self.parse_feed(self.feeds[id]['cfg']['id']) + + def unload(self): + if self.toolbar_button: + self.toolbar_button.destroy() + self.toolbar_button = None + + def deluge_version_compare(self, version): + import deluge.common + va = deluge.common.PROGRAM_VERSION.split('.') + dv = int(va[0]) * 1000000 + dv = dv + (int(va[1]) * 10000) + dv = dv + (int(va[2]) * 100) + if len(va) >= 4: + dv = dv + int(va[3]) + + if dv < version: + return -1 + elif dv > version: + return 1 + else: + return 0 + + def __init__(self, path, core, interface, defaults): + self.path = path + self.core = core + self.interface = interface + self.version = defaults['VERSION'] + + self.load_config() + + if self.config.show_toolbar_button == True: + self.configure_ui_show_toolbar_button() + + self.feeds = {} + if self.config.feeds: + for feed in self.config.feeds: + self.feeds[feed['id']] = { 'cfg' : feed, + 'updated' : 0, + 'data' : [] } + + # Debugging + # self.configure() diff --git a/plugins/SimpleRSS/__init__.py b/plugins/SimpleRSS/__init__.py deleted file mode 100644 index 29cc62ed2..000000000 --- a/plugins/SimpleRSS/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# -# __init__.py -# -# Copyright (C) "Mark Adamson" 2007 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -plugin_name = _("Simple RSS") -plugin_author = "Mark Adamson" -plugin_version = "1.0" -plugin_description = _(""" -Download Torrents automatically from SimpleRSS Feeds - -Add RSS feeds on the 'Feeds' tab, then add filters for TV shows (or whatever) on the 'Filters' tab. Double-click entries on the 'Torrents' tab to download extra torrents from the feeds. The Options are pretty self-explanatary. - -Please message me (SatNav) on the forums and let me know how you get on.. - -Enjoy!""") - - -def deluge_init(deluge_path): - global path - path = deluge_path - - -from SimpleRSS.plugin import plugin_SimpleRSS - -def enable(core, interface): - global path - return plugin_SimpleRSS(path, core, interface) diff --git a/plugins/SimpleRSS/plugin.py b/plugins/SimpleRSS/plugin.py deleted file mode 100644 index 597f3a239..000000000 --- a/plugins/SimpleRSS/plugin.py +++ /dev/null @@ -1,475 +0,0 @@ -# -*- coding: utf-8 -*- -# -# plugin.py -# -# Copyright (C) Marcos Pinto 2007 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - - -class plugin_SimpleRSS: - def __init__(self, path, deluge_core, deluge_interface): - #set up system thingies - import gtk, gtk.glade, os, ConfigParser, feedparser - import deluge.common, deluge.dgtk, deluge.pref - from time import asctime - self.path = path - self.core = deluge_core - self.interface = deluge_interface - #set up feeds file - self.feeds_config = ConfigParser.ConfigParser() - self.feeds_file = deluge.common.CONFIG_DIR + "/feeds.conf" - if not os.path.isfile(self.feeds_file): - f = open(self.feeds_file, mode='w') - f.flush() - f.close() - self.feeds_config.read(self.feeds_file) - #set up filters file - self.filters_config = ConfigParser.ConfigParser() - self.filters_file = deluge.common.CONFIG_DIR + "/filters.conf" - if not os.path.isfile(self.filters_file): - f = open(self.filters_file, mode='w') - f.flush() - f.close() - self.filters_config.read(self.filters_file) - #set up the preferences dialog - glade = gtk.glade.XML(path + "/rss.glade") - self.dlg = glade.get_widget("dialog") - self.dlg.set_position(gtk.WIN_POS_CENTER) - self.dlg.set_icon_from_file(self.path + "/rss.png") - #set up the feeds list viewer - self.feeds_view = glade.get_widget("feeds_view") - model = gtk.ListStore(str, str, str) - self.feeds_view.set_model(model) - deluge.dgtk.add_text_column(self.feeds_view, _("Name"), 0) - deluge.dgtk.add_text_column(self.feeds_view, _("URL"), 1) - deluge.dgtk.add_text_column(self.feeds_view, _("Last Entry Date"), 2) - #set up the torrents list viewer - self.torrents_view = glade.get_widget("torrents_view") - model = gtk.ListStore(str, str, str, str) - self.torrents_view.set_model(model) - deluge.dgtk.add_text_column(self.torrents_view, _("Feed"), 0) - deluge.dgtk.add_text_column(self.torrents_view, _("Name"), 1) - deluge.dgtk.add_text_column(self.torrents_view, _("URL"), 2) - deluge.dgtk.add_text_column(self.torrents_view, _("Date"), 3) - # Setup the filters list viewer - self.filters_view = glade.get_widget("filters_view") - model = gtk.ListStore(str) - self.filters_view.set_model(model) - deluge.dgtk.add_text_column(self.filters_view, _("Name"), 0) - # set up the feed choice combobox - self.feedchoice_combo = glade.get_widget("feedchoice_combo") - model = gtk.ListStore(str) - self.feedchoice_combo.set_model(model) - cell = gtk.CellRendererText() - self.feedchoice_combo.pack_start(cell, True) - self.feedchoice_combo.add_attribute(cell, 'text', 0) - # set up the torrents feed choice combobox - self.torrents_fcc = glade.get_widget("torrents_fcc") - model = gtk.ListStore(str) - self.torrents_fcc.set_model(model) - cell = gtk.CellRendererText() - self.torrents_fcc.pack_start(cell, True) - self.torrents_fcc.add_attribute(cell, 'text', 0) - #set up the rest of the GUI elements - self.name_entry = glade.get_widget("name_entry") - self.url_entry = glade.get_widget("url_entry") - self.button_addfeed = glade.get_widget("button_addfeed") - self.button_delfeed = glade.get_widget("button_delfeed") - self.chkfeeds = glade.get_widget("chkfeeds") - self.filtername_entry = glade.get_widget("filtername_entry") - self.filterexp_entry = glade.get_widget("filterexp_entry") - self.button_delfilter = glade.get_widget("button_delfilter") - self.checkonstart_button = glade.get_widget("checkonstart_button") - self.update_entry = glade.get_widget("update_entry") - #Connect event signals - self.filters_view.connect("cursor-changed", self.filter_row_clicked) - dic = { "addfeed_clicked" : self.addfeed_clicked, - "delfeed_clicked" : self.delfeed_clicked, - "addfilter_clicked" : self.addfilter_clicked, - "delfilter_clicked" : self.delfilter_clicked, - "row_clicked" : self.row_clicked, - "feedtext_changed" : self.feedtext_changed, - "filtername_lostfocus" : self.filtername_lostfocus, - "filterexp_lostfocus" : self.filterexp_lostfocus, - "feedchoice_combo_changed" : self.feedchoice_combo_changed, - "torrents_fcc_changed" : self.torrents_fcc_changed, - "torrents_view_row_activated" : self.torrents_view_row_activated, - "chkfeeds_clicked" : self.chkfeeds_clicked, - "cancel_clicked" : self.dialog_cancel, - "ok_clicked" : self.dialog_ok } - glade.signal_autoconnect(dic) - self.feeds_view.get_selection().set_select_function(self.row_clicked) - self.timer = 0 - self.torrents_list = [] - #self.checkfeeds() - # Access the interface's toolbar - self.toolbar = self.interface.toolbar - # Make a toolbar button - icon = gtk.Image() - icon.set_from_file(self.path + "/rss.png") # Toolbar items should be 22x22 pixel images - self.button = gtk.ToolButton(icon_widget=icon, label=_("RSS")) - self.ttips = gtk.Tooltips() - self.button.set_tooltip(self.ttips, _("SimpleRSS Broadcatcher")) - self.button.connect("clicked", self.rss_clicked) # Connect the signal handler for the button - self.toolbar.add(self.button) # Add button to toolbar - self.button.show_all() # Show the button - self.checkonstart = False - #load options - if self.feeds_config.has_option("DEFAULT", "interval"): - self.interval = self.feeds_config.getint("DEFAULT", "interval") - self.update_entry.set_text(self.feeds_config.get("DEFAULT", "interval")) - else: - self.interval = 900 - self.feeds_config.set("DEFAULT", "interval", 900) - if self.feeds_config.has_option("DEFAULT", "checkonstart"): - self.checkonstart = self.feeds_config.getboolean("DEFAULT", "checkonstart") - self.checkonstart_button.set_active(self.checkonstart) - else: - self.checkonstart = False - self.feeds_config.set("DEFAULT", "checkonstart", False) - if self.checkonstart_button.get_active(): - self.timer = self.interval - 5 - - def rss_clicked(self, button): - self.configure(self) - - def unload(self): - self.toolbar.remove(self.button) # Remove the button from the toolbar - f = open(self.feeds_file, "w") - self.feeds_config.write(f) - f.close() - - def feedtext_changed(self, args): - a = (self.name_entry.get_text() != "") - b = (self.url_entry.get_text() != "") - if(a and b): - self.button_addfeed.set_sensitive(1) - else: - self.button_addfeed.set_sensitive(0) - - def addfeed_clicked(self, args): - self.feeds_view.get_model().append([self.name_entry.get_text(), - self.url_entry.get_text(), ""]) - self.feedchoice_combo.append_text(self.name_entry.get_text()) - self.torrents_fcc.append_text(self.name_entry.get_text()) - self.feeds_config.add_section(self.name_entry.get_text()) - self.feeds_config.set(self.name_entry.get_text(), "url", self.url_entry.get_text()) - self.feeds_config.set(self.name_entry.get_text(), "lastchecked", "") - self.name_entry.set_text("") - self.url_entry.set_text("") - - def delfeed_clicked(self, args): - (model, selection) = self.feeds_view.get_selection().get_selected() - text = self.feeds_view.get_model().get_value(selection, 0) - model2 = self.feedchoice_combo.get_model() - model3 = self.torrents_fcc.get_model() - the_iter = model2.get_iter_first() - print text - while the_iter is not None: - print model2.get_value(the_iter, 0) - if (model2.get_value(the_iter, 0) == text): - remove_iter = the_iter - the_iter = model2.iter_next(the_iter) - model2.remove(remove_iter) - the_iter = model3.get_iter_first() - while the_iter is not None: - print model3.get_value(the_iter, 0) - if (model3.get_value(the_iter, 0) == text): - remove_iter = the_iter - the_iter = model3.iter_next(the_iter) - model3.remove(remove_iter) - model.remove(selection) - for filt in self.filters_config.sections(): - if self.filters_config.get(filt, "feed") == text: - self.filters_config.set(filt, "feed", "All") - self.feeds_config.remove_section(text) - self.button_delfeed.set_sensitive(0) - - def row_clicked(self, args): - self.button_delfeed.set_sensitive(1) - return True - - def addfilter_clicked(self, args): - unique = True - for filt in self.filters_config.sections(): - if filt == _("New Filter"): - unique = False - - if unique: - self.current_filter = _("New Filter") - self.filters_config.add_section(_("New Filter")) - new_iter = self.filters_view.get_model().append([_("New Filter")]) - self.filters_view.get_selection().select_iter(new_iter) - self.filters_config.set(_("New Filter"), "filterexp", "") - self.filters_config.set(_("New Filter"), "feed", "All") - self.filtername_entry.set_text(_("New Filter")) - self.feedchoice_combo.set_active(0) - - self.filtername_entry.set_sensitive(1) - self.filterexp_entry.set_sensitive(1) - self.feedchoice_combo.set_sensitive(1) - self.filterexp_entry.set_text("") - self.filtername_entry.grab_focus() - self.button_delfilter.set_sensitive(1) - - def delfilter_clicked(self, args): - model, selection = self.filters_view.get_selection().get_selected() - self.filters_config.remove_section(self.current_filter) - model.remove(selection) - self.current_filter = None - - self.filtername_entry.set_text("") - self.filterexp_entry.set_text("") - self.feedchoice_combo.set_active(-1) - self.filtername_entry.set_sensitive(0) - self.filterexp_entry.set_sensitive(0) - self.feedchoice_combo.set_sensitive(0) - self.button_delfilter.set_sensitive(0) - - def filter_row_clicked(self, widget): - selection = self.filters_view.get_selection() - model, selection_iter = selection.get_selected() - print model - print selection_iter - self.current_filter = self.filters_view.get_model().get_value(selection_iter, 0) - self.filtername_entry.set_text(self.current_filter) - self.filterexp_entry.set_text(self.filters_config.get(self.current_filter, "filterexp")) - feed = self.filters_config.get(self.current_filter, "feed") - model2 = self.feedchoice_combo.get_model() - the_iter = model2.get_iter_first() - while the_iter is not None: - #print model2.get_value(the_iter, 0) - if (model2.get_value(the_iter, 0) == feed): - set_iter = the_iter - the_iter = model2.iter_next(the_iter) - self.feedchoice_combo.set_active_iter(set_iter) - - self.filtername_entry.set_sensitive(1) - self.filterexp_entry.set_sensitive(1) - self.feedchoice_combo.set_sensitive(1) - self.button_delfilter.set_sensitive(1) - - def filtername_lostfocus(self, args, spare): - (model, selection) = self.filters_view.get_selection().get_selected() - old_filtername = self.filters_view.get_model().get_value(selection, 0) - self.filters_config.remove_section(old_filtername) - model.remove(selection) - - self.current_filter = self.filtername_entry.get_text() - new_iter = self.filters_view.get_model().append([self.current_filter]) - self.filters_view.get_selection().select_iter(new_iter) - self.filters_config.add_section(self.current_filter) - self.filters_config.set(self.current_filter, "filterexp", self.filterexp_entry.get_text()) - self.filters_config.set(self.current_filter, "feed", self.feedchoice_combo.get_active_text()) - - def filterexp_lostfocus(self, args, spare): - self.filters_config.set(self.current_filter, "filterexp", self.filterexp_entry.get_text()) - - def feedchoice_combo_changed(self, args): - self.filters_config.set(self.current_filter, "feed", self.feedchoice_combo.get_active_text()) - - def torrents_fcc_changed(self, args): - model = self.torrents_view.get_model() - model.clear() - if self.torrents_fcc.get_active_text() == _("All"): - for (date, feed, title, link) in self.torrents_list: - self.torrents_view.get_model().append((feed, title, link, date)) - else: - for (date, feed, title, link) in self.torrents_list: - if feed == self.torrents_fcc.get_active_text(): - self.torrents_view.get_model().append((feed, title, link, date)) - - def torrents_view_row_activated(self, widget, spare1, spare2): - selection = widget.get_selection() - model, selection_iter = selection.get_selected() - self.interface.interactive_add_torrent_url(widget.get_model().get_value(selection_iter, 2)) - - - def chkfeeds_clicked(self, args): - self.checkfeeds() - - def checkfeeds(self): - import feedparser, datetime - from time import asctime, strptime - - avail = {} - sorted = {} - self.torrents_list = [] - for name in self.feeds_config.sections(): - print "Attempting to parse " + name - tmp = feedparser.parse(self.feeds_config.get(name, "url")) - try: - print "Parsed " + tmp['feed']['title'] - avail[name] = True - except: - print "Failed to download/parse " + name - avail[name] = False - if avail[name]: - entries = [] - entries.extend( tmp[ "items" ] ) - decorated = [(entry["date_parsed"], entry) for entry in entries] - tmplist = [(entry["date_parsed"], name, entry["title"], entry.links[0].href) for entry in entries] - decorated.sort() - self.torrents_list.extend(tmplist) - #decorated.reverse() # for most recent entries first - sorted[name] = [entry for (date,entry) in decorated] - - model = self.torrents_view.get_model() - model.clear() - self.torrents_list.sort() - self.torrents_list.reverse() - #self.torrents_view.get_model().append([entry for entry in self.torrents_list]) - for (date,feed,title,link) in self.torrents_list: - self.torrents_view.get_model().append((feed, title, link, date)) - #for key in sorted.keys(): - # print "listing entries in " + key - # - # for entry in sorted[key]: - # print entry.title - # for enclosure in entry.enclosures: - # self.torrents_view.get_model().append( (key, entry.title, enclosure.href, entry.date_parsed) ) - - checked = {} - for name in self.filters_config.sections(): - checkfiltername = name - checkfilterexp = self.filters_config.get(name, "filterexp") - checkfilterfeed = self.filters_config.get(name, "feed") - print "filter: " + checkfiltername - print "search: " + checkfilterexp - print "feed: " + checkfilterfeed - if checkfilterfeed == "All": - #print "made it to 'All'" - for feedname in sorted.keys(): - if avail[feedname]: - print feedname + " last checked: " + self.feeds_config.get(feedname, "lastchecked") - if self.feeds_config.get(feedname, "lastchecked") != "": - lastchecked = strptime(self.feeds_config.get(feedname, "lastchecked")) - else: - lastchecked = strptime(asctime(sorted[feedname][0].date_parsed)) - #print "searching feed: " + feedname - for entry in sorted[feedname]: - #print entry.title + ": " + asctime(entry.date_parsed) - if (strptime(asctime(entry.date_parsed)) > lastchecked): - #print entry.date_parsed - #print " > " - #print lastchecked - if entry.title.find(checkfilterexp) != -1: - #print "contains" + checkfilterexp - for enclosure in entry.enclosures: - print enclosure.href - self.interface.interactive_add_torrent_url(enclosure.href) - #self.feeds_config.set(feedname, "lastchecked", asctime(entry.date_parsed)) - else: - if avail[checkfilterfeed]: - print "searching feed: " + checkfilterfeed - if self.feeds_config.get(checkfilterfeed, "lastchecked") != "": - lastchecked = strptime(self.feeds_config.get(checkfilterfeed, "lastchecked")) - else: - #print sorted[checkfilterfeed][1].date_parsed - lastchecked = strptime(asctime(sorted[checkfilterfeed][0].date_parsed)) - print "lastchecked: " + asctime(lastchecked) - for entry in sorted[checkfilterfeed]: - #print entry.title + ": " + asctime(entry.date_parsed) - if (strptime(asctime(entry.date_parsed)) > lastchecked): - #print entry.date_parsed - #print " > " - #print lastchecked - if (entry.title.find(checkfilterexp) != -1): - #print "contains" + checkfilterexp - for enclosure in entry.enclosures: - print enclosure.href - self.interface.interactive_add_torrent_url(enclosure.href) - #self.feeds_config.set(checkfilterfeed, "lastchecked", asctime(entry.date_parsed)) - - for name in avail.keys(): - if avail[name]: - self.feeds_config.set(name, "lastchecked", asctime(sorted[name][len(sorted[name])-1].date_parsed)) - - self.timer = 0 - - - - - def configure(self, widget=None): - import gtk, gtk.glade - from deluge import common - self.dlg.show_all() - model = self.feeds_view.get_model() - model.clear() - model2 = self.feedchoice_combo.get_model() - model2.clear() - model3 = self.filters_view.get_model() - model3.clear() - model4 = self.torrents_fcc.get_model() - model4.clear() - self.filtername_entry.set_text("") - self.filterexp_entry.set_text("") - self.name_entry.set_text("") - self.url_entry.set_text("") - self.feedchoice_combo.append_text(_("All")) - self.torrents_fcc.append_text(_("All")) - self.torrents_fcc.set_active(0) - for name in self.feeds_config.sections(): - self.feeds_view.get_model().append( (name, self.feeds_config.get(name, "url"), self.feeds_config.get(name, "lastchecked") ) ) - self.feedchoice_combo.append_text(name) - self.torrents_fcc.append_text(name) - for filters in self.filters_config.sections(): - self.filters_view.get_model().append( ([filters]) ) - #self.checkfeeds() - self.button_addfeed.set_sensitive(0) - self.button_delfeed.set_sensitive(0) - self.filtername_entry.set_sensitive(0) - self.filterexp_entry.set_sensitive(0) - self.feedchoice_combo.set_sensitive(0) - self.button_delfilter.set_sensitive(0) - self.update_entry.set_text(str(self.interval)) - self.checkonstart_button.set_active(self.checkonstart) - - def dialog_ok(self, source): - self.dlg.hide_all() - self.interval = int(self.update_entry.get_text()) - self.feeds_config.set("DEFAULT", "interval", self.update_entry.get_text()) - self.feeds_config.set("DEFAULT", "checkonstart", self.checkonstart_button.get_active()) - f = open(self.filters_file, "w") - self.filters_config.write(f) - f.close() - f = open(self.feeds_file, "w") - self.feeds_config.write(f) - f.close() - - def dialog_cancel(self, source): - self.dlg.hide_all() - - def update(self): - self.timer += 1 - if self.timer >= self.interval: - import threading - - print "BONG!" - threading.Thread(target=self.checkfeeds).start - self.timer = 0 diff --git a/plugins/SimpleRSS/rss.glade b/plugins/SimpleRSS/rss.glade deleted file mode 100644 index 6da77ed49..000000000 --- a/plugins/SimpleRSS/rss.glade +++ /dev/null @@ -1,885 +0,0 @@ - - - - - - 5 - RSS Broadcatcher Settings - 512 - 384 - GDK_WINDOW_TYPE_HINT_NORMAL - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - - - - - 80 - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Feed Name: - - - False - False - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - 3 - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - - - - - 80 - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Feed URL: - - - False - False - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - 3 - - - - - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add - True - 0 - - - - - False - False - 1 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove - True - 0 - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - False - 3 - - - - - 3 - - - - - False - False - 1 - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Feeds - - - tab - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 160 - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 200 - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - 285 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - True - False - - - - - - - - True - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 6 - 2 - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add - True - 0 - - - - - 4 - 5 - GTK_FILL - GTK_FILL - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove - True - 0 - - - - 1 - 2 - 4 - 5 - GTK_FILL - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Feed - - - 3 - 4 - GTK_FILL - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Filter Exp: - - - 2 - 3 - GTK_FILL - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - 1 - 2 - 2 - 3 - GTK_FILL - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Filter Name: - - - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 5 - 6 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 1 - 2 - 5 - 6 - - - - - True - True - - - - - 1 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Filters - - - tab - 1 - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Feed: - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - 1 - - - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - - 1 - - - - - 2 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Torrents - - - tab - 2 - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Update Interval (seconds): - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - 5 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 7 - - - - - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 3 - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Check feeds on Deluge start - 0 - True - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 2 - - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Check Feeds Now - 0 - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 2 - - - - - False - False - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - - - - 4 - - - - - 3 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Options - - - tab - 3 - False - False - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - GTK_BUTTONBOX_END - - - True - gtk-cancel - True - 0 - - - - - - True - gtk-ok - True - 1 - - - - 1 - - - - - False - GTK_PACK_END - - - - - - diff --git a/plugins/SimpleRSS/rss.png b/plugins/SimpleRSS/rss.png deleted file mode 100644 index 4e9e4a7e2..000000000 Binary files a/plugins/SimpleRSS/rss.png and /dev/null differ