"Fossies" - the Fresh Open Source Software Archive

Member "coda-6.9.5/coda-src/vtools/gcodacon.in" (23 Mar 2010, 22236 Bytes) of package /linux/misc/old/coda-6.9.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #!@PYTHON@
    2 #
    3 #              Coda File System
    4 #                 Release 6
    5 #
    6 #       Copyright (c) 2007 Carnegie Mellon University
    7 #
    8 # This  code  is  distributed "AS IS" without warranty of any kind under
    9 # the terms of the GNU General Public License Version 2, as shown in the
   10 # file  LICENSE.  The  technical and financial  contributors to Coda are
   11 # listed in the file CREDITS.
   12 #
   13 #
   14 # gcodacon - Show the Coda client's volume/reintegration state
   15 
   16 import pygtk
   17 pygtk.require('2.0')
   18 import gtk, gobject
   19 import os, socket, re, time
   20 
   21 # send no more than 20 updates per second to the notification daemon
   22 NOTIFICATION_INTERVAL = 0.05
   23 
   24 try:
   25     from pynotify import Notification, init as notify_init, \
   26     URGENCY_LOW, URGENCY_NORMAL, URGENCY_CRITICAL
   27     if not notify_init("GCodacon"):
   28     print "*** failed to initialize pynotify"
   29     Notification = None
   30 except ImportError:
   31     print "*** failed to import pynotify"
   32     Notification = None
   33     URGENCY_LOW, URGENCY_NORMAL, URGENCY_CRITICAL = 0, 1, 2
   34 
   35 venus_conf=os.path.join('@sysconfdirx@', 'venus.conf')
   36 dirty_threshold=100 # At what point do we start to worry about pending changes
   37 
   38 ##########################################################################
   39 ### Icon
   40 ##########################################################################
   41 # bunch of colors
   42 black  = "#000000"
   43 dblue  = "#4E3691"
   44 lblue  = "#754FC6"
   45 dred   = "#C40B0B"
   46 lred   = "#FF2222"
   47 orange = "#FFA61B"
   48 yellow = "#FFEA00"
   49 dgreen = "#55C155"
   50 lgreen = "#6EFB6E"
   51 white  = "#FFFFFF"
   52 transp = "None"
   53 
   54 # places we can change the color of
   55 backgr  = ' '
   56 circle  = '@'
   57 server  = '$'
   58 edges   = '.'
   59 top     = '#'
   60 topedge = '+'
   61 
   62 # default color mapping
   63 ICON_XPM_COLORMAP = { backgr : transp, circle : transp, edges : black,
   64     server : white, topedge : dblue, top : lblue }
   65 
   66 # actual image data
   67 ICON_XPM_DATA = [
   68 #/* XPM */
   69 #static char * icon_xpm[] = {
   70 #"64 64 6 1",
   71 #"  c None",
   72 #". c #000000",
   73 #"+ c #4E3691",
   74 #"@ c #FF2222",
   75 #"# c #754FC6",
   76 #"$ c #FFFFFF",
   77 "                                                                ",
   78 "                          @@@@@@@@@@@@                          ",
   79 "                      @@@@@@@@@@@@@@@@@@@@                      ",
   80 "                    @@@@@@@@@@@@@@@@@#@@@@@@                    ",
   81 "                  @@@@@@@@@@@@@@@@######@@@@@@                  ",
   82 "                @@@@@@@@@@@@@@@###########@@@@@@                ",
   83 "              @@@@@@@@@@@@@@################@@@@@@              ",
   84 "             @@@@@@@@@@@@#####################@@@@@             ",
   85 "            @@@@@@@@@@##########################@@@@            ",
   86 "           @@@@@@@@##############################@@@@           ",
   87 "          @@@@@@##################################@@@@          ",
   88 "         @@@@@@###############################+++++@@@@         ",
   89 "        @@@@@@#############################++++++++@@@@@        ",
   90 "       @@@@@@+++########################++++++++++.@@@@@@       ",
   91 "      @@@@@@@+++++###################++++++++++$...@@@@@@@      ",
   92 "      @@@@@@@+++++++##############++++++++++$$$$...@@@@@@@      ",
   93 "     @@@@@@@@..+++++++#########++++++++++$$$$$$$...@@@@@@@@     ",
   94 "     @@@@@@@@...$+++++++####++++++++++$$$$$$$$$$...@@@@@@@@     ",
   95 "    @@@@@@@@@...$$$++++++++++++++++$$$$$$$$$$$$$...@@@@@@@@@    ",
   96 "    @@@@@@@@@...$$$$$+++++++++++$$$$$$$$$$....$$...@@@@@@@@@    ",
   97 "   @@@@@@@@@@...$$$$$$$++++++$$$$$$$$$$.......$$...@@@@@@@@@@   ",
   98 "   @@@@@@@@@@...$$$$$$$$.+.$$$$$$$$$........$$$$...@@@@@@@@@@   ",
   99 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$.......$$$$$$$...@@@@@@@@@@@  ",
  100 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$....$$$$$$$$$$...@@@@@@@@@@@  ",
  101 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  102 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  103 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  104 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  105 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  106 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  107 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  108 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  109 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  110 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  111 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  112 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  113 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  114 " @@@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@@ ",
  115 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  116 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  117 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  118 "  @@@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@@  ",
  119 "   @@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@   ",
  120 "   @@@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@@   ",
  121 "    @@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@    ",
  122 "    @@@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@@    ",
  123 "     @@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@     ",
  124 "     @@@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@@     ",
  125 "      @@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@      ",
  126 "      @@@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$$...@@@@@@@      ",
  127 "       @@@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$$$$....@@@@@@       ",
  128 "        @@@@@...$$$$$$$$...$$$$$$$$$$$$$$$$$.......@@@@@        ",
  129 "         @@@@...$$$$$$$$...$$$$$$$$$$$$$$.........@@@@@         ",
  130 "          @@@....$$$$$$$...$$$$$$$$$$$..........@@@@@@          ",
  131 "           @@......$$$$$...$$$$$$$$..........@@@@@@@@           ",
  132 "            @@.......$$$...$$$$$..........@@@@@@@@@@            ",
  133 "             @@@.......$...$$..........@@@@@@@@@@@@             ",
  134 "              @@@@..................@@@@@@@@@@@@@@              ",
  135 "                @@@@.............@@@@@@@@@@@@@@@                ",
  136 "                  @@@@........@@@@@@@@@@@@@@@@                  ",
  137 "                    @@@@...@@@@@@@@@@@@@@@@@                    ",
  138 "                      @@@@@@@@@@@@@@@@@@@@                      ",
  139 "                          @@@@@@@@@@@@                          ",
  140 "                                                                "
  141 #};
  142 ]
  143 
  144 ##########################################################################
  145 ### Define states
  146 ##########################################################################
  147 # We add states to a global list 'STATES'. Each state contains,
  148 #   desc    - description of the state (for tooltips and such)
  149 #   test    - function which is passed # cmls and a list of volume flags
  150 #         and should return true if this state is active
  151 #
  152 # When picking a volume state we walk the global list in order and return
  153 # the first where 'test' returns true.
  154 STATES = []
  155 class State: # really used as a 'struct' to hold all state specific data
  156     def __init__(self, desc, urgency, cmap={}, test=None):
  157     colormap = ICON_XPM_COLORMAP.copy()
  158     colormap.update(cmap)
  159     xpm_hdr = ["64 64 6 1"] + map(lambda c: "%s\tc %s" %c, colormap.items())
  160     image = gtk.gdk.pixbuf_new_from_xpm_data(xpm_hdr + ICON_XPM_DATA)
  161 
  162     self.desc = desc
  163     self.urgency = urgency
  164     self.image = image
  165     self.icon = image.scale_simple(24, 24, gtk.gdk.INTERP_HYPER)
  166     self.test = test
  167     STATES.append(self)
  168 
  169 ##########################################################################
  170 # Actual states follow
  171 NO_MARINER = \
  172 State(desc="Unable to communicate with venus",
  173       urgency=URGENCY_CRITICAL,
  174       cmap = { circle : black, server : black, top : black, topedge : black })
  175 
  176 State(desc="%(cmls)d operations pending: Not authenticated",
  177       urgency=URGENCY_CRITICAL,
  178       test=lambda cmls,flags: cmls and 'unauth' in flags,
  179       cmap = { circle : lred, server : yellow })
  180 
  181 State(desc="%(cmls)d operations pending: Conflict detected",
  182       urgency=URGENCY_CRITICAL,
  183       test=lambda cmls,flags: 'conflict' in flags,
  184       cmap = { circle : lred, server : dred })
  185 
  186 State(desc="%(cmls)d operations pending: Servers unreachable",
  187       urgency=URGENCY_CRITICAL,
  188       test=lambda cmls,flags: cmls and 'unreachable' in flags,
  189       cmap = { circle : black, server : orange })
  190 
  191 State(desc="%(cmls)d operations pending: Application Specific Resolver active",
  192       urgency=URGENCY_NORMAL,
  193       test=lambda cmls,flags: 'asr' in flags,
  194       cmap = { circle : yellow, server : orange })
  195 
  196 State(desc="%(cmls)d operations pending",
  197       urgency=URGENCY_NORMAL,
  198       test=lambda cmls,flags: cmls > dirty_threshold and \
  199     not 'resolve' in flags and not 'reint' in flags,
  200       cmap = { circle : transp, server : orange })
  201 
  202 UNREACH = \
  203 State(desc="Servers unreachable",
  204       urgency=URGENCY_NORMAL,
  205       test=lambda cmls,flags: 'unreachable' in flags,
  206       cmap = { circle : black, server : white })
  207 
  208 State(desc="%(cmls)d operations pending: Resolving",
  209       urgency=URGENCY_NORMAL,
  210       test=lambda cmls,flags: 'resolve' in flags,
  211       cmap = { circle : orange, server : dgreen })
  212 
  213 State(desc="%(cmls)d operations pending: Reintegrating",
  214       urgency=URGENCY_LOW,
  215       test=lambda cmls,flags: 'reint' in flags,
  216       cmap = { circle : lgreen, server : dgreen })
  217 
  218 State(desc="%(cmls)d operations pending",
  219       urgency=URGENCY_LOW,
  220       test=lambda cmls,flags: cmls,
  221       cmap = { circle : transp, server : dgreen })
  222 
  223 CLEAN = \
  224 State(desc="No local changes",
  225       urgency=URGENCY_LOW,
  226       test=lambda cmls,flags: 1)
  227 
  228 ##########################################################################
  229 ### Coda specific helper functions
  230 ##########################################################################
  231 # Read Coda configuration files
  232 def parse_codaconf(conffile):
  233     settings = {}
  234     for line in open(conffile):
  235     # Skip anything starting with '#', lines should look like <key>=<value>
  236     m = re.match("^([^#][^=]+)=(.*)[ \t]*$", line)
  237     if not m: continue
  238     key, value = m.groups()
  239 
  240     # The value may be quoted, strip balanced quotes
  241     m = re.match('^"(.*)"$', value)
  242     if m: value = m.group(1)
  243 
  244     settings[key] = value
  245     return settings
  246 
  247 ##########################################################################
  248 # Base class that handles the connection to venus's mariner port
  249 class MarinerListener:
  250     def __init__(self, use_tcp=0, debug=0):
  251     self.use_tcp = use_tcp
  252     self.debug = debug
  253 
  254     if not self.use_tcp:
  255         venusconf = parse_codaconf(venus_conf)
  256         mariner = venusconf.get("marinersocket", "/usr/coda/spool/mariner")
  257         self.addrs = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, 0, mariner)]
  258     else:
  259         self.addrs = socket.getaddrinfo(None, 'venus', socket.AF_INET,
  260                      socket.SOCK_STREAM, 0)
  261     self.__reconnect()
  262 
  263     # callbacks which can be overridden by subclasses
  264     def connected(self):    pass
  265     def disconnected(self): pass
  266     def data_ready(self, line): pass
  267     def data_done(self):    pass
  268 
  269     def __reconnect(self):
  270     if self.__connect() == False: return # false == connected
  271     self.disconnected()
  272     gobject.timeout_add(5000, self.__connect)
  273 
  274     def __connect(self):
  275     for host in self.addrs:
  276         try:
  277         s = socket.socket(host[0], host[1], host[2])
  278         s.connect(host[4])
  279         s.send('set:volstate\n')
  280         break
  281         except socket.error:
  282         continue
  283     else:
  284         return True # we end up here if we couldn't connect
  285 
  286     self.connected()
  287     gobject.io_add_watch(s, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
  288                  self.__data_ready)
  289     return False
  290 
  291     def __data_ready(self, fd, condition):
  292     # socket error, maybe venus died?
  293     if condition & (gobject.IO_ERR | gobject.IO_HUP):
  294         self.__reconnect()
  295         return False
  296 
  297     data = fd.recv(8192)
  298     if not data:
  299         self.__reconnect()
  300         return False
  301 
  302     self.buf += data
  303     while self.buf:
  304         try:
  305         line, self.buf = self.buf.split('\n', 1)
  306         except ValueError:
  307         break
  308 
  309         if self.debug:
  310         print line
  311         self.data_ready(line)
  312     self.data_done()
  313     return True
  314 
  315 ##########################################################################
  316 ### Wrappers around GTK objects
  317 ##########################################################################
  318 # About dialog
  319 class About(gtk.AboutDialog):
  320     def __init__(self):
  321     gtk.AboutDialog.__init__(self)
  322     self.set_comments("Show the Coda client's volume/reintegration state")
  323     self.set_copyright("Copyright (c) 2007 Carnegie Mellon University")
  324     self.set_website("http://www.coda.cs.cmu.edu/")
  325     self.set_website_label("Coda Distributed File System")
  326     self.set_license(' '.join("""\
  327     This code is distributed "AS IS" without warranty of any kind under the
  328     terms of the GNU General Public License Version 2, as shown in the file
  329     LICENSE. The technical and financial contributors to Coda are listed in
  330     the file CREDITS.""".split()))
  331     self.set_wrap_license(1)
  332 
  333     gtk.AboutDialog.run(self)
  334     self.destroy()
  335 
  336 ##########################################################################
  337 # wrapper around ListStore to provide transparent State -> state_idx mapping
  338 class VolumeList(gtk.ListStore):
  339     def __init__(self):
  340     gtk.ListStore.__init__(self, str, int, int)
  341     self.set_sort_column_id(1, gtk.SORT_ASCENDING)
  342 
  343     def __get(self, volname):
  344     for row in self:
  345         if row[0] == volname:
  346         return row
  347     raise KeyError
  348 
  349     def __setitem__(self, volname, state, cmls):
  350     try:
  351         row = self.__get(volname)
  352         row[1] = state
  353         row[2] = cmls
  354     except KeyError:
  355         self.append([volname, state, cmls])
  356 
  357     def __getitem__(self, volname):
  358     return STATES[self.__get(volname)[1]]
  359 
  360     def __delitem__(self, volname):
  361     row = self.__get(volname)
  362     self.remove(row.iter)
  363 
  364     def values(self):
  365     return [ STATES(row[1]) for row in self ]
  366 
  367     def update(self, volname, cmls, flags):
  368     for idx, state in enumerate(STATES):
  369         if state.test and state.test(cmls, flags):
  370         self.__setitem__(volname, idx, cmls)
  371         break
  372 
  373     def first(self):
  374     iter = self.get_iter_first()
  375     if not iter: return CLEAN
  376     return STATES[self.get_value(iter, 1)]
  377 
  378     def cmls(self):
  379     return reduce(lambda x,y:x+y, [ row[2] for row in self ])
  380 
  381 ##########################################################################
  382 # scrolled treeview widget to present the list of volumes and states
  383 class VolumeView(gtk.TreeView):
  384     def __init__(self, store):
  385     gtk.TreeView.__init__(self, store)
  386 
  387     def update_icon(column, cell, store, iter, arg=None):
  388         state = STATES[store.get_value(iter, 1)]
  389         cell.set_property('pixbuf', state.icon)
  390 
  391     cell = gtk.CellRendererPixbuf()
  392     column = gtk.TreeViewColumn('', cell)
  393     column.set_cell_data_func(cell, update_icon)
  394     self.append_column(column)
  395 
  396     def update_realm(column, cell, store, iter, arg=None):
  397         volume = store.get_value(iter, 0)
  398         cell.set_property('text', volume.split('@')[1])
  399 
  400     cell = gtk.CellRendererText()
  401     column = gtk.TreeViewColumn('Realm', cell)
  402     column.set_cell_data_func(cell, update_realm)
  403     column.set_resizable(1)
  404     self.append_column(column)
  405 
  406     def update_volume(column, cell, store, iter, arg=None):
  407         volume = store.get_value(iter, 0)
  408         cell.set_property('text', volume.split('@')[0])
  409 
  410     cell = gtk.CellRendererText()
  411     column = gtk.TreeViewColumn('Volume', cell, text=0)
  412     column.set_cell_data_func(cell, update_volume)
  413     column.set_resizable(1)
  414     self.append_column(column)
  415 
  416     def update_desc(column, cell, store, iter, arg=None):
  417         state = STATES[store.get_value(iter, 1)]
  418         cmls = store.get_value(iter, 2)
  419         cell.set_property('text', state.desc  % {'cmls' : cmls})
  420 
  421     cell = gtk.CellRendererText()
  422     column = gtk.TreeViewColumn('State', cell)
  423     column.set_cell_data_func(cell, update_desc)
  424     self.append_column(column)
  425 
  426     # search on volume name
  427     def search(store, column, key, iter, data=None):
  428         volume = store.get_value(iter, column)
  429         return not key in volume # return false when the row matches
  430 
  431     self.set_search_column(0)
  432     self.set_search_equal_func(search)
  433 
  434 class GlobalState:
  435     def __init__(self, activate, menu, enable_popups):
  436     self.popups = enable_popups
  437     self.next_time = 0
  438     self.next_message = self.next_urgency = None
  439     self.state = self.tray = self.notification = None
  440     if hasattr(gtk, "StatusIcon"):
  441         self.tray = gtk.StatusIcon()
  442         self.tray.connect("activate", activate)
  443         self.tray.connect("popup_menu", menu)
  444         self.set_state(CLEAN, 0)
  445         self.tray.set_visible(1)
  446 
  447     if Notification:
  448         self.notification = Notification("Coda Status")
  449         if self.tray and hasattr(self.notification,"attach_to_status_icon"):
  450         self.notification.attach_to_status_icon(self.tray)
  451 
  452     def is_embedded(self):
  453     return self.tray and self.tray.is_embedded()
  454 
  455     def set_state(self, state, cmls=0):
  456     if not self.tray and not self.notification:
  457         return
  458 
  459     if self.state != state:
  460         self.state = state
  461         if self.tray:
  462         self.tray.set_from_pixbuf(state.image)
  463     elif self.cmls == cmls:
  464         return
  465 
  466     self.cmls = cmls
  467     desc = state.desc % {'cmls' : cmls}
  468 
  469     if self.tray:
  470         self.tray.set_tooltip(desc)
  471 
  472     if self.notification and self.popups:
  473         now = time.time()
  474         notify_queued = self.next_message is not None
  475         self.next_message = desc
  476         self.next_urgency = state.urgency
  477         if notify_queued: return
  478         elif now < self.next_time:
  479         gobject.timeout_add(int((self.next_time - now) * 1000),
  480                     self.__show_notification)
  481         else:
  482             self.__show_notification()
  483 
  484     def __show_notification(self):
  485     self.notification.update("Coda Status", self.next_message)
  486     self.notification.set_urgency(self.next_urgency)
  487     self.next_message = self.next_urgency = None
  488     self.next_time = time.time() + NOTIFICATION_INTERVAL
  489     # catch errors when notification-daemon is not running
  490     try: self.notification.show()
  491     except gobject.GError: pass
  492     return False
  493 
  494 class App(MarinerListener):
  495     def __init__(self, options):
  496     self.buf = ''
  497     self.hide = options.dirty_only
  498 
  499     self.vols = VolumeList()
  500 
  501     def row_filter(store, iter, app):
  502         if app.hide and STATES[store.get_value(iter, 1)] in [CLEAN,UNREACH]:
  503             return False
  504         return True
  505 
  506     self.shown = self.vols.filter_new()
  507     self.shown.set_visible_func(row_filter, self)
  508 
  509     self.window = gtk.Window()
  510     self.window.connect("delete_event", self.delete_event)
  511     self.window.set_title('Coda volume/reintegration state')
  512     self.window.set_size_request(400, -1)
  513     self.window.set_default_size(-1, 200)
  514 
  515     self.treeview = VolumeView(self.shown)
  516     self.treeview.connect("button_press_event", self.button_event)
  517 
  518     scrolled = gtk.ScrolledWindow()
  519     scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
  520     scrolled.add(self.treeview)
  521     scrolled.show_all()
  522 
  523     self.window.add(scrolled)
  524 
  525     self.menu = gtk.Menu()
  526     item = gtk.CheckMenuItem(label="Show only dirty volumes")
  527     item.set_active(self.hide)
  528     item.connect("activate", self.toggle_filter)
  529     self.menu.append(item)
  530     item.show()
  531 
  532     if Notification:
  533         item = gtk.CheckMenuItem(label="Enable notifications")
  534         item.set_active(not not options.enable_popups)
  535         item.connect("activate", self.toggle_notifications)
  536         self.menu.append(item)
  537         item.show()
  538 
  539     item = gtk.SeparatorMenuItem()
  540     self.menu.append(item)
  541     item.show()
  542 
  543     item = gtk.ImageMenuItem(stock_id=gtk.STOCK_ABOUT)
  544     item.connect("activate", lambda x: About())
  545     self.menu.append(item)
  546     item.show()
  547 
  548     item = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
  549     item.connect_object("activate", gtk.main_quit, "popup.quit")
  550     self.menu.append(item)
  551     item.show()
  552 
  553     self.status = GlobalState(self.activate, lambda icon, button, time:
  554         self.menu.popup(None, None, gtk.status_icon_position_menu,
  555                 button, time, icon), options.enable_popups)
  556 
  557     self.test_idx = 0
  558     if options.test:
  559         self.connected()
  560         gobject.timeout_add(5000, self.test)
  561     else:
  562         MarinerListener.__init__(self, options.use_tcp, options.debug)
  563 
  564     # allow the statusicon to get embedded into the systray/panel
  565     gobject.timeout_add(1000, self.window_show)
  566 
  567     def window_show(self, *args):
  568     if not self.status.is_embedded():
  569         self.window.show()
  570     return False
  571 
  572     def test(self):
  573     try:
  574         self.status.set_state(STATES[self.test_idx])
  575         self.test_idx = self.test_idx + 1
  576     except IndexError:
  577         self.test_idx = 0
  578     return True
  579 
  580     # MarinerListener callbacks
  581     def connected(self):
  582     self.status.set_state(CLEAN)
  583 
  584     def disconnected(self):
  585     self.status.set_state(NO_MARINER)
  586 
  587     def data_ready(self, line):
  588     m = re.match('^volstate::(.*) ([^ ]*) (\d+)(.*)$', line)
  589     if not m: return
  590 
  591     vol, volstate, cmls, flags = m.groups()
  592     if volstate != 'deleted':
  593         flags = flags.split()
  594         flags.append(volstate)
  595         self.vols.update(vol, int(cmls), flags)
  596     else:
  597         self.vols.__delitem__(vol)
  598 
  599     def data_done(self):
  600     sysstate, cmls = self.vols.first(), self.vols.cmls()
  601     self.status.set_state(sysstate, cmls)
  602 
  603     # callbacks for gtk events
  604     def toggle_filter(self, widget):
  605     self.hide = not self.hide
  606     self.shown.refilter()
  607     return True
  608 
  609     def toggle_notifications(self, widget):
  610     self.status.popups = not self.status.popups
  611     return True
  612 
  613     def button_event(self, widget, event):
  614     if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
  615         self.menu.popup(None, None, None, event.button, event.time)
  616         return True
  617     return False
  618 
  619     def delete_event(self, widget, event=None, user_data=None):
  620     widget.hide()
  621     if not self.status.is_embedded():
  622         gtk.main_quit()
  623     return True
  624 
  625     def activate(self, *args):
  626     #if not self.window.get_property('visible'):
  627     if not self.window.is_active():
  628           self.window.present()
  629     else: self.window.hide()
  630     return True
  631 
  632 
  633 if __name__ == '__main__':
  634     from optparse import OptionParser
  635 
  636     parser = OptionParser()
  637     parser.add_option("-d", "--debug", dest="debug", action="store_true",
  638               help="Show updates as they are received from venus")
  639     parser.add_option("-n", "--notify", dest="enable_popups", action="store_true",
  640               help="Enable notification popups by default")
  641     parser.add_option("-o", "--only-dirty", dest="dirty_only",
  642               default=False, action="store_true",
  643               help="Show only dirty volumes in status window by default")
  644     parser.add_option("-t", "--use-tcp", dest="use_tcp", action="store_true",
  645               help="Use tcp to connect to venus")
  646     parser.add_option("--test", dest="test", action="store_true",
  647               help="Run a test which cycles through all states")
  648 
  649     options, args = parser.parse_args()
  650 
  651     try:
  652     app = App(options)
  653     gtk.main()
  654     except KeyboardInterrupt:
  655     pass
  656