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() |
|