src/updatemanagernotifier.py
changeset 2991 75e616731cc3
parent 2990 2cc6693a7d83
child 2992 e48a94cff862
equal deleted inserted replaced
2990:2cc6693a7d83 2991:75e616731cc3
     1 #!/usr/bin/python2.6
       
     2 # 
       
     3 # CDDL HEADER START 
       
     4 # 
       
     5 # The contents of this file are subject to the terms of the 
       
     6 # Common Development and Distribution License (the "License").  
       
     7 # You may not use this file except in compliance with the License.  
       
     8 #
       
     9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
    10 # or http://www.opensolaris.org/os/licensing.
       
    11 # See the License for the specific language governing permissions
       
    12 # and limitations under the License.
       
    13 #
       
    14 # When distributing Covered Code, include this CDDL HEADER in each
       
    15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    16 # If applicable, add the following below this CDDL HEADER, with the
       
    17 # fields enclosed by brackets "[]" replaced with your own identifying
       
    18 # information: Portions Copyright [yyyy] [name of copyright owner]
       
    19 #
       
    20 # CDDL HEADER END
       
    21 #
       
    22 # Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
       
    23 #
       
    24 
       
    25 import os
       
    26 import subprocess
       
    27 import errno
       
    28 import sys
       
    29 import time
       
    30 import socket
       
    31 import locale
       
    32 import gettext
       
    33 import getopt
       
    34 import random
       
    35 try:
       
    36         import gobject
       
    37         gobject.threads_init()
       
    38         import gconf
       
    39         import gtk
       
    40         import pygtk
       
    41         pygtk.require("2.0")
       
    42 except ImportError:
       
    43         sys.exit(1)
       
    44 import pkg.client.progress as progress
       
    45 import pkg.misc as misc
       
    46 import pkg.gui.misc as gui_misc
       
    47 import pkg.gui.enumerations as enumerations
       
    48 from gettext import ngettext
       
    49 try:
       
    50         import pynotify
       
    51 except ImportError:
       
    52         print "%s package must be installed" % (
       
    53             gui_misc.package_name["SUNWpython26-notify"])
       
    54         sys.exit(1)
       
    55 
       
    56 # Put _() in the global namespace
       
    57 import __builtin__
       
    58 __builtin__._ = gettext.gettext
       
    59 
       
    60 START_DELAY_DEFAULT = 120
       
    61 REFRESH_PERIOD_DEFAULT = "Never"
       
    62 SHOW_NOTIFY_ICON_DEFAULT = True
       
    63 IMAGE_DIRECTORY_DEFAULT = "/"
       
    64 LASTCHECK_DIR_NAME = os.path.join(os.path.expanduser("~"),'.updatemanager/notify')
       
    65 CHECKFOR_UPDATES = "/usr/lib/pm-checkforupdates"
       
    66 UPDATEMANAGER = "pm-updatemanager"
       
    67 
       
    68 ICON_LOCATION = "/usr/share/update-manager/icons"
       
    69 NOTIFY_ICON_NAME = "updatemanager"
       
    70 GKSU_PATH = "/usr/bin/gksu"
       
    71 
       
    72 UPDATEMANAGER_PREFERENCES = "/apps/updatemanager/preferences"
       
    73 START_DELAY_PREFERENCES = "/apps/updatemanager/preferences/start_delay"
       
    74 REFRESH_PERIOD_PREFERENCES = "/apps/updatemanager/preferences/refresh_period"
       
    75 SHOW_NOTIFY_MESSAGE_PREFERENCES = "/apps/updatemanager/preferences/show_notify_message"
       
    76 SHOW_ICON_ON_STARTUP_PREFERENCES = "/apps/updatemanager/preferences/show_icon_on_startup"
       
    77 TERMINATE_AFTER_ICON_ACTIVATE_PREFERENCES = \
       
    78     "/apps/updatemanager/preferences/terminate_after_icon_activate"
       
    79 
       
    80 DAILY = "Daily"
       
    81 WEEKLY = "Weekly"
       
    82 MONTHLY = "Monthly"
       
    83 NEVER = "Never"
       
    84 
       
    85 DAILY_SECS = 24*60*60
       
    86 WEEKLY_SECS = 7*24*60*60
       
    87 # We asssume that a month has 30 days
       
    88 MONTHLY_SECS = 30*24*60*60
       
    89 NEVER_SECS = 365*24*60*60
       
    90 
       
    91 class UpdateManagerNotifier:
       
    92         def __init__(self):
       
    93                 os.nice(20)
       
    94                 try:
       
    95                         self.application_dir = os.environ["UPDATE_MANAGER_NOTIFIER_ROOT"]
       
    96                 except KeyError:
       
    97                         self.application_dir = "/"
       
    98                 misc.setlocale(locale.LC_ALL, "")
       
    99                 gettext.bindtextdomain("pkg", os.path.join(
       
   100                     self.application_dir,
       
   101                     "usr/share/locale"))
       
   102                 gettext.textdomain("pkg")
       
   103                 self.pr = None
       
   104                 self.last_check_filename = None
       
   105                 self.time_until_next_check = 0
       
   106                 self.status_icon = None
       
   107                 self.n_updates = 0
       
   108                 self.n_installs = 0
       
   109                 self.n_removes = 0
       
   110                 self.notify = None
       
   111                 self.host = None
       
   112                 self.last_check_time = 0
       
   113                 self.refresh_period = 0
       
   114                 self.timeout_id = 0
       
   115                 self.terminate_after_activate = False
       
   116 
       
   117                 self.client = gconf.client_get_default()
       
   118                 self.start_delay  =  self.get_start_delay()
       
   119                 # Allow gtk.main loop to start as quickly as possible
       
   120                 gobject.timeout_add(self.start_delay * 1000, self.check_and_start)
       
   121 
       
   122         def check_and_start(self):
       
   123                 self.check_already_running()
       
   124                 self.client.add_dir(UPDATEMANAGER_PREFERENCES, 
       
   125                     gconf.CLIENT_PRELOAD_NONE)
       
   126                 self.client.notify_add(REFRESH_PERIOD_PREFERENCES, 
       
   127                     self.refresh_period_changed)
       
   128                 self.client.notify_add(SHOW_ICON_ON_STARTUP_PREFERENCES, 
       
   129                     self.show_icon_changed)
       
   130                 self.refresh_period  =  self.get_refresh_period()
       
   131                 self.host = socket.gethostname()
       
   132 
       
   133                 self.last_check_time = self.get_last_check_time()
       
   134                 self.pr = progress.NullProgressTracker()
       
   135                 if self.get_show_icon_on_startup():
       
   136                         self.client.set_bool(SHOW_ICON_ON_STARTUP_PREFERENCES, False)
       
   137                         self.schedule_check_for_updates()
       
   138                 else:
       
   139                         gobject.idle_add(self.do_next_check)
       
   140                 return False
       
   141                 
       
   142         def refresh_period_changed(self, client, connection_id, entry, arguments):
       
   143                 old_delta = self.get_delta_for_refresh_period()
       
   144                 if entry.get_value().type == gconf.VALUE_STRING:
       
   145                         self.refresh_period = entry.get_value().get_string()
       
   146                 new_delta = self.get_delta_for_refresh_period()
       
   147                 if debug == True:
       
   148                         print "old_delta %d" % old_delta
       
   149                         print "new_delta %d" % new_delta
       
   150                 if old_delta > new_delta:
       
   151                         if self.timeout_id > 0:
       
   152                                 gobject.source_remove(self.timeout_id)
       
   153                                 self.timeout_id = 0
       
   154                         self.do_next_check()
       
   155 
       
   156         def show_icon_changed(self, client, connection_id, entry, arguments):
       
   157                 if entry.get_value().type == gconf.VALUE_BOOL:
       
   158                         show_icon = entry.get_value().get_bool()
       
   159                 if self.status_icon != None:
       
   160                         self.status_icon.set_visible(show_icon)
       
   161 
       
   162         def get_start_delay(self):
       
   163                 start_delay  =  self.client.get_int(START_DELAY_PREFERENCES)
       
   164                 if start_delay == 0:
       
   165                         start_delay = START_DELAY_DEFAULT
       
   166                 if debug == True:
       
   167                         start_delay = 1
       
   168                         print "start_delay: %d" % start_delay
       
   169                 return start_delay
       
   170 
       
   171         def get_refresh_period(self):
       
   172                 refresh_period  =  self.client.get_string(REFRESH_PERIOD_PREFERENCES)
       
   173                 if refresh_period == None:
       
   174                         refresh_period = REFRESH_PERIOD_DEFAULT
       
   175                 if debug == True:
       
   176                         print "refresh_period: %s" % refresh_period
       
   177                 return refresh_period
       
   178 
       
   179         def get_show_notify_message(self):
       
   180                 show_notify_message  =  \
       
   181                         self.client.get_bool(SHOW_NOTIFY_MESSAGE_PREFERENCES)
       
   182                 if debug == True:
       
   183                         print "show_notify_message: %d" % show_notify_message
       
   184                 return show_notify_message
       
   185 
       
   186         def get_show_icon_on_startup(self):
       
   187                 show_icon_on_startup  =  \
       
   188                         self.client.get_bool(SHOW_ICON_ON_STARTUP_PREFERENCES)
       
   189                 if debug == True:
       
   190                         print "show_icon_on_startup: %d" % show_icon_on_startup
       
   191                 return show_icon_on_startup
       
   192 
       
   193         def get_terminate_after_activate(self):
       
   194                 terminate_after_activate  =  \
       
   195                         self.client.get_bool(TERMINATE_AFTER_ICON_ACTIVATE_PREFERENCES)
       
   196                 if debug == True:
       
   197                         print "terminate_after_activate: %d" % terminate_after_activate
       
   198                 return terminate_after_activate
       
   199 
       
   200         def get_last_check_time(self):
       
   201                 if (self.last_check_filename == None):
       
   202                         self.last_check_filename = \
       
   203                                 os.path.join(LASTCHECK_DIR_NAME,
       
   204                                     self.host + '-lastcheck')
       
   205                 try:
       
   206                         f = open(self.last_check_filename, "r")
       
   207 
       
   208                         try:
       
   209                                 return float(f.read(64))
       
   210                         finally:
       
   211                                 f.close()
       
   212 
       
   213                 except IOError, strerror:
       
   214                         if debug == True:
       
   215                                 print "Unable to get last check time error %s" % strerror
       
   216 
       
   217                 return 0
       
   218 
       
   219         def set_last_check_time(self):
       
   220                 try:
       
   221                         os.makedirs(LASTCHECK_DIR_NAME)
       
   222                 except os.error, eargs:
       
   223                         if eargs[0] != errno.EEXIST: # File exists
       
   224                                 raise os.error, args
       
   225 
       
   226                 try:
       
   227                         f = open(self.last_check_filename, "w")
       
   228 
       
   229                         try:
       
   230                                 f.write(str(self.last_check_time))
       
   231                         finally:
       
   232                                 f.close()
       
   233 
       
   234                 except IOError, strerror:
       
   235                         print "I/O error: %s opening %s" \
       
   236                                 % (strerror, self.last_check_filename)
       
   237 
       
   238         def get_delta_for_refresh_period(self):
       
   239                 if self.refresh_period == DAILY:
       
   240                         delta = DAILY_SECS
       
   241                 elif self.refresh_period == WEEKLY:
       
   242                         delta = WEEKLY_SECS
       
   243                 elif self.refresh_period == MONTHLY:
       
   244                         delta = MONTHLY_SECS
       
   245                 else:
       
   246                         delta = NEVER_SECS
       
   247                 return delta
       
   248 
       
   249         def is_check_required(self):
       
   250                 delta = self.get_delta_for_refresh_period()
       
   251                 if delta == NEVER_SECS:
       
   252                         self.time_until_next_check = NEVER_SECS
       
   253                         return False
       
   254                 if self.last_check_time == 0:
       
   255                         return True
       
   256                 current_time = time.time()
       
   257                 if debug == True:
       
   258                         print "current time %f " % current_time
       
   259                         print "last check time %f " % self.last_check_time
       
   260                 self.time_until_next_check = self.last_check_time + delta - current_time
       
   261                 if debug == True:
       
   262                         print "time until next check %f " % self.time_until_next_check
       
   263                 if self.time_until_next_check <= 0:
       
   264                         return True
       
   265                 else:
       
   266                         return False
       
   267 
       
   268         def show_status_icon(self, value):
       
   269                 if self.status_icon == None:
       
   270                         self.status_icon = self.create_status_icon()
       
   271                 self.client.set_bool(SHOW_ICON_ON_STARTUP_PREFERENCES, value)
       
   272                 self.status_icon.set_visible(value)
       
   273                 if not value:
       
   274                         return
       
   275 
       
   276                 toolfmt = _("<b>Updates are available: </b>\n"
       
   277                     "%(updates)s %(installs)s %(removes)s")
       
   278                 tooltip = self.__set_updates_str(toolfmt)
       
   279                 self.status_icon.set_tooltip_markup(tooltip)
       
   280 
       
   281         def __set_updates_str(self, str_fmt):
       
   282                 if self.n_updates == 0:
       
   283                         updates_str = ""
       
   284                 else:
       
   285                         updates_fmt = ngettext("%d Update,", "%d Updates,",
       
   286                             self.n_updates)
       
   287                         updates_str = updates_fmt % self.n_updates
       
   288                 if self.n_installs == 0:
       
   289                         installs_str = ""
       
   290                 else:
       
   291                         installs_fmt = ngettext("%d Install,", "%d Installs,",
       
   292                             self.n_installs)
       
   293                         installs_str = installs_fmt % self.n_installs
       
   294                 if self.n_removes == 0:
       
   295                         removes_str = ""
       
   296                 else:
       
   297                         removes_fmt = ngettext("%d Remove", "%d Removes",
       
   298                             self.n_removes)
       
   299                         removes_str = removes_fmt % self.n_removes
       
   300                 updates_str = str_fmt % \
       
   301                     {"updates": updates_str,
       
   302                     "installs": installs_str,
       
   303                     "removes": removes_str}
       
   304                 updates_str = updates_str.rstrip(', ')
       
   305                 return updates_str
       
   306 
       
   307         def schedule_check_for_updates(self):
       
   308                 self.last_check_time = time.time()
       
   309                 # Add random delay so that servers will not be hit 
       
   310                 # all at once
       
   311                 if debug:
       
   312                         random_delay = 0
       
   313                 else:
       
   314                         random_delay = random.randint(0, 1800)
       
   315                 gobject.timeout_add(random_delay * 1000, self.check_for_updates)
       
   316 
       
   317         def check_for_updates(self):
       
   318                 proc = subprocess.Popen([CHECKFOR_UPDATES,
       
   319                             '--nice', '--checkupdates-cache',
       
   320                             '--image-dir', IMAGE_DIRECTORY_DEFAULT],
       
   321                             stdout=subprocess.PIPE)
       
   322 
       
   323                 output = proc.communicate()[0].strip()
       
   324                 lines = output.splitlines()
       
   325                 n_updates = 0
       
   326                 n_installs = 0
       
   327                 n_removes = 0
       
   328                 for line in lines:
       
   329                         if line.startswith("n_updates"):
       
   330                                 updates = line.split(":", 1)
       
   331                                 n_updates = int(updates[1]) 
       
   332                         if line.startswith("n_installs"):
       
   333                                 installs = line.split(":", 1)
       
   334                                 n_installs = int(installs[1]) 
       
   335                         if line.startswith("n_removes"):
       
   336                                 removes = line.split(":", 1)
       
   337                                 n_removes = int(removes[1])
       
   338                 return_code = proc.wait()
       
   339                 if debug:
       
   340                         print "return from subprocess is %d" % return_code
       
   341                 self.set_last_check_time()
       
   342                 if return_code == enumerations.UPDATES_AVAILABLE:
       
   343                         self.n_updates = n_updates
       
   344                         self.n_installs = n_installs
       
   345                         self.n_removes = n_removes
       
   346                         self.show_status_icon(True)
       
   347                 else:
       
   348                         self.show_status_icon(False)
       
   349                 self.schedule_next_check_for_checks()
       
   350                 return False                                
       
   351 
       
   352         def create_status_icon(self):
       
   353                 icon_theme = gtk.IconTheme()
       
   354                 icon_theme.append_search_path(ICON_LOCATION)
       
   355                 icon = gui_misc.get_icon(icon_theme, NOTIFY_ICON_NAME, 24)
       
   356                 status_icon = gtk.status_icon_new_from_pixbuf(icon)
       
   357                 status_icon.set_visible(False)
       
   358                 status_icon.connect('activate', self.activate_status_icon)
       
   359                 status_icon.connect('notify', self.notify_status_icon)
       
   360                 return status_icon
       
   361 
       
   362         def notify_status_icon(self, status_icon, paramspec):
       
   363                 if paramspec.name == "embedded" and self.status_icon.is_embedded():
       
   364                         if self.get_show_notify_message():
       
   365                                 gobject.idle_add(self.show_notify_message)
       
   366 
       
   367         def activate_status_icon(self, status_icon):
       
   368                 self.show_status_icon(False)
       
   369                 gobject.spawn_async([GKSU_PATH, UPDATEMANAGER])
       
   370                 if self.get_terminate_after_activate():
       
   371                         gtk.main_quit()
       
   372                         sys.exit(0)
       
   373                 else:
       
   374                         self.schedule_next_check_for_checks()
       
   375 
       
   376         def show_notify_message(self):
       
   377                 if self.notify == None:
       
   378                         if pynotify.init("UpdateManager"):
       
   379                                 notify_fmt = _("Updates available\n"
       
   380                                     "%(updates)s %(installs)s %(removes)s")
       
   381                                 notify_str = self.__set_updates_str(notify_fmt)
       
   382                                 notify_str += _("\nPlease click on icon to update.")
       
   383                                 self.notify = pynotify.Notification(\
       
   384                                     _("Update Manager"), notify_str)
       
   385 
       
   386                 if self.notify != None:
       
   387                         self.set_notify_position()
       
   388                         self.notify.show()
       
   389 
       
   390         def set_notify_position(self):
       
   391                 geometry = self.status_icon.get_geometry()
       
   392                 rectangle = geometry[1]
       
   393                 orientation = geometry[2]
       
   394                 x = rectangle.x
       
   395                 y = rectangle.y
       
   396 
       
   397                 if orientation == gtk.ORIENTATION_HORIZONTAL and y > 200:
       
   398                         x += 10
       
   399                         y += 5
       
   400                 elif orientation == gtk.ORIENTATION_HORIZONTAL and y <=200:
       
   401                         x += 10
       
   402                         y += 25
       
   403                 elif orientation == gtk.ORIENTATION_VERTICAL and x >200:
       
   404                         x -= 5
       
   405                         y += 10
       
   406                 else:
       
   407                         x += 25
       
   408                         y += 10
       
   409                 self.notify.set_hint_int32("x", x)
       
   410                 self.notify.set_hint_int32("y", y)
       
   411 
       
   412         def schedule_next_check_for_checks(self):
       
   413                 """This schedules the next time to wake up to check if it's
       
   414                 necessary to check for updates yet."""
       
   415                 if self.time_until_next_check <= 0:
       
   416                         next_check_time = self.get_delta_for_refresh_period()
       
   417                 else:
       
   418                         next_check_time = self.time_until_next_check
       
   419                 if debug == True:
       
   420                         print "scheduling next check: %s" % next_check_time
       
   421                 self.timeout_id = gobject.timeout_add(int(next_check_time * 1000),
       
   422                     self.do_next_check)
       
   423 
       
   424         def do_next_check(self):
       
   425                 self.timeout_id = 0
       
   426                 if debug == True:
       
   427                         print "Called do_next_check"
       
   428                         print "time for check: %f - %f \n" % (time.time(), \
       
   429                                 self.last_check_time)
       
   430                 if self.is_check_required():
       
   431                         self.schedule_check_for_updates()
       
   432                 else:
       
   433                         self.schedule_next_check_for_checks()
       
   434                 return False
       
   435 
       
   436         @staticmethod
       
   437         def check_already_running():
       
   438                 atom = gtk.gdk.atom_intern("UPDATEMANAGERNOTIFIER",
       
   439                                            only_if_exists = False)
       
   440                 pid = os.getpid()
       
   441                 atom_args = [pid, ]
       
   442                 fail = True
       
   443 
       
   444                 is_running = gtk.gdk.get_default_root_window().property_get(atom)
       
   445                 if is_running != None:
       
   446                         old_pid = is_running[2][0]
       
   447                         try:
       
   448                                 os.kill(old_pid, 0)
       
   449                         except os.error, eargs:
       
   450                                 if eargs[0] != errno.ESRCH: # No such process
       
   451                                         raise os.error, args
       
   452                                 # Old process no longer exists
       
   453                                 fail = False
       
   454                 else:
       
   455                         # Atom does not exist
       
   456                         fail = False
       
   457 
       
   458                 if fail == True:
       
   459                         print _("Another instance of UpdateManagerNotify is running")
       
   460                         sys.exit(1)
       
   461 
       
   462                 gtk.gdk.get_default_root_window().property_change(atom,
       
   463                         "INTEGER", 16, gtk.gdk.PROP_MODE_REPLACE, atom_args)
       
   464 
       
   465 ###############################################################################
       
   466 #-----------------------------------------------------------------------------#
       
   467 # Main
       
   468 #-----------------------------------------------------------------------------#
       
   469 
       
   470 def main():
       
   471         gtk.main()
       
   472         return 0
       
   473 
       
   474 if __name__ == '__main__':
       
   475         debug = False
       
   476         try:
       
   477                 opts, args = getopt.getopt(sys.argv[1:], "hd", ["help", "debug"])
       
   478         except getopt.error, msg:
       
   479                 print "%s, for help use --help" % msg
       
   480                 sys.exit(2)
       
   481 
       
   482         for option, argument in opts:
       
   483                 if option in ("-h", "--help"):
       
   484                         print "Use -d (--debug) to run in debug mode."
       
   485                         sys.exit(0)
       
   486                 if option in ("-d", "--debug"):
       
   487                         debug = True
       
   488 
       
   489         updatemanager_notifier = UpdateManagerNotifier()
       
   490 
       
   491         main()