On December 21, 2007 08:17:52 [EMAIL PROTECTED] wrote:
> Hello,
>
> Here it is. After diving into python and gPodder code ( I must say gPodder
> is often nicely implemented so it wasn't too hard, maybe comments are
> sometime missing) and spending hours seaking on internet, I'm able to
> deliver you a patch with a first implentation of a status icon on the sytem
> tray.
>
> Please, try it, test it and/or review its code (it should be easy to review
> since it almost consist of one class), and share you comments, suggestions,
> critisms with us.
>
> regard,
> Jérôme Chabod
>
> -------------------------------------------------------------
>
> WHAT IT DOES:
> -------------
> It displays an icon in the sytem tray after gPodder starts. This icon
> allows following features: * hide/show gpodder
>     left clicking several times on this icon will respectively bring
> gPodder to front (if not already), hide gPodder (only the icon will remain
> visible), show gPodder again. * popup menu
>     right clicking on the icon will popup a menu for quick access to some
> gPodder functionality ("update feed", "download all episodes",
> "préferences", "about" and "exit") * download status and notification
>     during gPodder is downloading, the icons's tooltip will display how
> many files are currently dowloading and the progression percentage. Once
> all downloads are finished (terminated or canceled), a notification message
> is poped up
>
> GOAL:
> -----
> My idea behind this icon is to have gPodder starting automaticaly and
> running in background for its daily job. To achieve that, gPodder should be
> able to: 1. start hidden, only the icon should be visible (activated by a
> configuration option) 2. check for new episodes (either automaticaly at
> startup  - a configuration flag already exists -, triggered by the icon's
> popup menu or even periodically) 3. inform: status in tooltip and special
> icon, notification after update 4. download new episodes (either
> automaticaly when new episodes where found - a configuration flag already
> exists - or triggered by the popup menu) 5. inform: status in tooltip and
> special icon, notification after download 6. notify errors and
> troubleshouting
>
> TODO:
> -----
> * add configuration options:
>     - show icon in systray (on by default)
>     - display notification (on by default)
>     - start gPodder iconified (off by default)
>     - 4 options when exiting gPodder: "exit", "run in systray", "run in
> systray until all downloads finished", "ask me" ("ask me" by default) *
> status and notification for channel updates (check for new episodes) *
> status and notification for mp3 device synchronisation
> * add a status symbol to the icon: dowloading, checking for new episodes,
> synchronising (if somebody could help here) * check periodically for new
> episode (peridiodicity should be configurable) * error handling: what to do
> in case of error?
>     - critical errors where the use can't help (bug, missing
> dependency...). - troubleshouting where the user might do something (server
> down, xml invalid, disk full, ...). Maybe send a notification popup and
> make icon blinking
>
>

Hello Jérôme,

Attached is a very basic autoupdate addition to your patch. What it does, is 
every 20min it runs update_feed_cache. This behaviour is controlled by a 
configuration option in the Preferences window.

I also modified your patch a bit to expose some functions that are used for 
autoupdate and may be useful in other places. I don't know if this was the 
best way to do it, change whatever you want if I did something stupid :)

The main problem with autoupdate is that it will still try to update even 
while you are accessing the DB (eg. updating your iPod or even manually 
updating the feed cache) so we need to find a way to disable autoupdate when 
these events are happening. I think this can be done by setting the 
autoupdate config option to False temporally when doing any DB access. Sadly, 
I don't really know enough to do this properly :(

Thanks for your hard work,

nick
diff -ur gpodder-r502/src/gpodder/config.py gpodder-r502-dev/src/gpodder/config.py
--- gpodder-r502/src/gpodder/config.py	2007-12-21 16:44:13.000000000 -0500
+++ gpodder-r502-dev/src/gpodder/config.py	2007-12-21 16:52:19.000000000 -0500
@@ -61,6 +61,7 @@
     'on_sync_mark_played': ( bool, False ),
     'on_sync_delete': ( bool, False ),
     'auto_remove_old_episodes': ( bool, False ),
+    'autoupdate': ( bool, False ),
 
     # Settings that are updated directly in code
     'ipod_mount': ( str, '/media/ipod' ),
diff -ur gpodder-r502/src/gpodder/gui.py gpodder-r502-dev/src/gpodder/gui.py
--- gpodder-r502/src/gpodder/gui.py	2007-12-21 16:44:13.000000000 -0500
+++ gpodder-r502-dev/src/gpodder/gui.py	2007-12-21 19:06:30.000000000 -0500
@@ -17,6 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import dbus
 import os
 import gtk
 import gtk.gdk
@@ -164,6 +165,9 @@
 
         gl = gPodderLib()
 
+        #TODO: use a configuration flag to make it optional
+        self.gp_status_icon = GPodderStatusIcon(self)
+
         gl.config.connect_gtk_window( self.gPodder)
         gl.config.connect_gtk_paned( 'paned_position', self.channelPaned)
 
@@ -288,6 +292,9 @@
             if len(old_episodes) > 0:
                 self.delete_episode_list(old_episodes, confirm=False)
                 self.updateComboBox()
+                
+        # Start the auto-update feature 
+        self.auto_update(first_run = True, timeout = 20)
 
 
     def treeview_channels_query_tooltip(self, treeview, x, y, keyboard_tooltip, tooltip):
@@ -753,7 +760,7 @@
         if finish_proc:
             finish_proc()
 
-    def update_feed_cache(self, force_update = True):
+    def update_feed_cache(self, force_update = True, show_update_dialog = True):
         title = _('Downloading podcast feeds')
         heading = _('Downloading feeds')
         body = _('Podcast feeds contain channel metadata and information about current episodes.')
@@ -785,7 +792,8 @@
         please_wait.vbox.pack_start( label_body)
         please_wait.vbox.pack_start( myprogressbar)
         please_wait.vbox.pack_end( mylabel)
-        please_wait.show_all()
+        if show_update_dialog:
+            please_wait.show_all()
 
         # center the dialog on the gPodder main window
         ( x, y ) = self.gPodder.get_position()
@@ -806,7 +814,8 @@
         thread = Thread( target = self.update_feed_cache_proc, args = args)
         thread.start()
 
-        please_wait.run()
+        if show_update_dialog:
+            please_wait.run()
         please_wait.destroy()
 
         self.updateComboBox()
@@ -1386,6 +1395,16 @@
         self.treeAvailable.get_selection().select_all()
         self.on_btnDownloadedDelete_clicked( widget, args)
         self.treeAvailable.get_selection().unselect_all()
+        
+    def auto_update(self, first_run = True, timeout = 20):
+        if gPodderLib().config.autoupdate and not first_run:
+            log('Auto-updating feed cache...', sender = self)
+            if self.gp_status_icon.is_iconified():
+                self.gp_status_icon.send_notification("Updating Feed Cache...")
+                self.update_feed_cache( force_update = True, show_update_dialog = False )
+            else:
+                self.update_feed_cache( force_update = True, show_update_dialog = True )
+        gobject.timeout_add(timeout*60*1000, self.auto_update, False, timeout)
 
 
 class gPodderChannel(GladeWidget):
@@ -1500,6 +1519,7 @@
         gl.config.connect_gtk_togglebutton( 'fssync_channel_subfolders', self.cbChannelSubfolder)
         gl.config.connect_gtk_togglebutton( 'on_sync_mark_played', self.on_sync_mark_played)
         gl.config.connect_gtk_togglebutton( 'on_sync_delete', self.on_sync_delete)
+        gl.config.connect_gtk_togglebutton( 'autoupdate', self.autoupdate )
         gl.config.connect_gtk_spinbutton( 'max_downloads', self.spinMaxDownloads)
         gl.config.connect_gtk_togglebutton( 'max_downloads_enabled', self.cbMaxDownloads)
         gl.config.connect_gtk_spinbutton( 'limit_rate_value', self.spinLimitDownloads)
@@ -2192,6 +2212,137 @@
             self.callback([])
  
 
+class GPodderStatusIcon(gtk.StatusIcon):
+    """ this class display a status icon in the system tray
+    this icon serves to show or hide gPodder, notify dowload status 
+    and provide a popupmenu for quick acces to some 
+    gPodder functionalities
+       
+    author: Jérôme Chabod <jerome.chabod at ifrance.com>
+    12/20/2007: first release
+       
+    """
+
+    def  __init__( self, gpodder):
+        gtk.StatusIcon.__init__(self)
+
+        log("create status icon",  sender = self)
+        self.set_from_file(scalable_dir)
+        self.__gp = gpodder
+        
+        self.set_tooltip("gPodder")
+  
+        # build and connect the popup menu
+        menu = gtk.Menu()
+        menuItem = gtk.ImageMenuItem("Check for Updates")
+        menuItem.connect('activate',  self.__gp.on_itemUpdate_activate)
+        menu.append(menuItem)        
+        menuItem = gtk.ImageMenuItem("Download all new episodes")
+        menuItem.connect('activate',  self.__gp.on_itemDownloadAllNew_activate)
+        menu.append(menuItem)        
+        menu.append( gtk.SeparatorMenuItem())
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
+        menuItem.connect('activate',  self.__gp.on_itemPreferences_activate)
+        menu.append(menuItem)
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
+        menuItem.connect('activate',  self.__gp.on_itemAbout_activate)
+        menu.append(menuItem)
+        menu.append( gtk.SeparatorMenuItem())
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
+        menuItem.connect('activate',  self.__gp.close_gpodder, self)
+        menu.append(menuItem)
+        self.connect('popup-menu', self.__on_popup_menu, menu)
+
+        self.connect('activate', self.__on_activate)
+        self.set_visible(True)
+            
+        # register the status icon to be notified 
+        # by the download status manager
+        dsm = services.download_status_manager
+        # dsm.register( 'list-changed', self.__download_list_changed)
+        dsm.register( 'progress-changed', self.__download_progress_changed)
+        log("status icon created",  sender = self)
+
+    def __on_popup_menu(self, widget, button, time, data = None):
+        """ Triggered when the popup menu is called, 
+        generaly by right clicking on the icon.
+        Display the popup menu created in cronstructor.
+        
+        """
+        log("popup systray menu",  sender = self)
+        data.show_all()
+        data.popup(None, None, None, 3, time)
+
+    def __on_activate(self, widget, data=None):
+        """ Triggered when the icon is activated, 
+        generaly by left clicking on the icon.
+        Iconify or deiconify the gpodder gui. 
+        When iconified, the application is hidden from taskbar
+           
+        """
+        if  self.is_iconified():
+            self.uniconify_main_window()
+        else:
+            self.iconify_main_window()            
+
+    def __download_list_changed( self):
+        """ callback by dowload manager. not used yet """
+        log("download_list_changed was notified to systray icon", sender = self)
+        
+    def __download_progress_changed( self, count, percentage):
+        """ callback by download manager during dowloading.
+        It updates the tooltip with information on how many 
+        files are dowloaded and the percentage of dowload
+        
+        """
+        #TODO: change status icon during downloading
+        tt = ""
+        if count == 0:
+            tt="gPodder"
+            self.send_notification("download finished")	
+        else:
+            if count == 1:
+                tt = "downloading one file"
+            else:
+                tt = "downloading %d files" % (count)
+            tt += "\n%d%% accomplished" % (percentage)
+        self.set_tooltip(tt)
+
+    def send_notification( self, message, title = "gPodder"):
+        """ Use the gnome notifier system to diplay a notification message 
+        
+        """
+        log(' systray icon notifies: "%s"', message,  sender = self)
+        
+        #TODO: use a configuration flag to make this optional
+        try:
+            session_bus = dbus.SessionBus()
+            obj = session_bus.get_object("org.freedesktop.Notifications",
+                                         "/org/freedesktop/Notifications")
+            interface = dbus.Interface(
+                        obj, "org.freedesktop.Notifications")
+            interface.Notify("gPodder", 1, scalable_dir, title, 
+                             message, [], {}, 5000)
+        except Exception, detail:
+            log("Error while creating dbus interface for notification: %s"
+                , detail,  sender = self)
+          
+    def is_iconified(self):
+        return not self.__gp.gpodder_main_window.get_property('is-active')
+        
+    def uniconify_main_window(self):
+        if self.is_iconified():
+            log("gpodder restored",  sender = self)
+            self.__gp.gpodder_main_window.deiconify()
+            self.__gp.gpodder_main_window.set_skip_taskbar_hint(False) 
+        
+    def iconify_main_window(self):
+        if not self.is_iconified():
+            log("gpodder iconified",  sender = self)
+            self.__gp.gpodder_main_window.set_skip_taskbar_hint(True)
+            self.__gp.gpodder_main_window.iconify()
+
+
 def main():
     gobject.threads_init()
     gtk.window_set_default_icon_name( 'gpodder')
diff -ur gpodder-r502/data/gpodder.glade gpodder-r502-dev/data/gpodder.glade
--- gpodder-r502/data/gpodder.glade	2007-12-21 16:44:13.000000000 -0500
+++ gpodder-r502-dev/data/gpodder.glade	2007-12-21 16:50:23.000000000 -0500
@@ -3962,7 +3962,7 @@
 	      <child>
 		<widget class="GtkTable" id="table9">
 		  <property name="visible">True</property>
-		  <property name="n_rows">13</property>
+		  <property name="n_rows">14</property>
 		  <property name="n_columns">3</property>
 		  <property name="homogeneous">False</property>
 		  <property name="row_spacing">2</property>
@@ -4061,8 +4061,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">10</property>
-		      <property name="bottom_attach">11</property>
+		      <property name="top_attach">11</property>
+		      <property name="bottom_attach">12</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
 		    </packing>
@@ -4081,8 +4081,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">1</property>
-		      <property name="top_attach">11</property>
-		      <property name="bottom_attach">12</property>
+		      <property name="top_attach">12</property>
+		      <property name="bottom_attach">13</property>
 		      <property name="x_padding">6</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options">fill</property>
@@ -4110,8 +4110,8 @@
 		    <packing>
 		      <property name="left_attach">1</property>
 		      <property name="right_attach">2</property>
-		      <property name="top_attach">11</property>
-		      <property name="bottom_attach">12</property>
+		      <property name="top_attach">12</property>
+		      <property name="bottom_attach">13</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
 		    </packing>
@@ -4132,8 +4132,8 @@
 		    <packing>
 		      <property name="left_attach">2</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">11</property>
-		      <property name="bottom_attach">12</property>
+		      <property name="top_attach">12</property>
+		      <property name="bottom_attach">13</property>
 		      <property name="y_options"></property>
 		    </packing>
 		  </child>
@@ -4145,8 +4145,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">7</property>
-		      <property name="bottom_attach">8</property>
+		      <property name="top_attach">8</property>
+		      <property name="bottom_attach">9</property>
 		      <property name="y_padding">3</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
@@ -4169,8 +4169,8 @@
 		    <packing>
 		      <property name="left_attach">1</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">6</property>
-		      <property name="bottom_attach">7</property>
+		      <property name="top_attach">7</property>
+		      <property name="bottom_attach">8</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
 		    </packing>
@@ -4189,8 +4189,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">1</property>
-		      <property name="top_attach">6</property>
-		      <property name="bottom_attach">7</property>
+		      <property name="top_attach">7</property>
+		      <property name="bottom_attach">8</property>
 		      <property name="x_padding">6</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options">fill</property>
@@ -4218,8 +4218,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">5</property>
-		      <property name="bottom_attach">6</property>
+		      <property name="top_attach">6</property>
+		      <property name="bottom_attach">7</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
 		    </packing>
@@ -4232,8 +4232,8 @@
 		    <packing>
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">4</property>
-		      <property name="bottom_attach">5</property>
+		      <property name="top_attach">5</property>
+		      <property name="bottom_attach">6</property>
 		      <property name="y_padding">3</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
@@ -4241,6 +4241,29 @@
 		  </child>
 
 		  <child>
+		    <widget class="GtkCheckButton" id="auto_remove_old_episodes">
+		      <property name="visible">True</property>
+		      <property name="tooltip" translatable="yes">If checked, gPodder will delete played episodes that are older than the specified amout of days (in the Downloads tab) on every startup.</property>
+		      <property name="can_focus">True</property>
+		      <property name="label" translatable="yes">Delete played old episodes on startup</property>
+		      <property name="use_underline">True</property>
+		      <property name="relief">GTK_RELIEF_NORMAL</property>
+		      <property name="focus_on_click">True</property>
+		      <property name="active">False</property>
+		      <property name="inconsistent">False</property>
+		      <property name="draw_indicator">True</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">1</property>
+		      <property name="right_attach">3</property>
+		      <property name="top_attach">3</property>
+		      <property name="bottom_attach">4</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+
+		  <child>
 		    <widget class="GtkImage" id="image2420">
 		      <property name="visible">True</property>
 		      <property name="icon_size">6</property>
@@ -4254,7 +4277,7 @@
 		      <property name="left_attach">0</property>
 		      <property name="right_attach">1</property>
 		      <property name="top_attach">1</property>
-		      <property name="bottom_attach">4</property>
+		      <property name="bottom_attach">5</property>
 		      <property name="x_padding">6</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options">fill</property>
@@ -4262,11 +4285,10 @@
 		  </child>
 
 		  <child>
-		    <widget class="GtkCheckButton" id="auto_remove_old_episodes">
+		    <widget class="GtkCheckButton" id="autoupdate">
 		      <property name="visible">True</property>
-		      <property name="tooltip" translatable="yes">If checked, gPodder will delete played episodes that are older than the specified amout of days (in the Downloads tab) on every startup.</property>
 		      <property name="can_focus">True</property>
-		      <property name="label" translatable="yes">Delete played old episodes on startup</property>
+		      <property name="label" translatable="yes">Auto-update feed cache every 20 minutes</property>
 		      <property name="use_underline">True</property>
 		      <property name="relief">GTK_RELIEF_NORMAL</property>
 		      <property name="focus_on_click">True</property>
@@ -4277,8 +4299,8 @@
 		    <packing>
 		      <property name="left_attach">1</property>
 		      <property name="right_attach">3</property>
-		      <property name="top_attach">3</property>
-		      <property name="bottom_attach">4</property>
+		      <property name="top_attach">4</property>
+		      <property name="bottom_attach">5</property>
 		      <property name="x_options">fill</property>
 		      <property name="y_options"></property>
 		    </packing>
_______________________________________________
gpodder-devel mailing list
gpodder-devel@lists.berlios.de
https://lists.berlios.de/mailman/listinfo/gpodder-devel

Reply via email to