Advertisement
Guest User

Untitled

a guest
Aug 24th, 2017
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.13 KB | None | 0 0
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*-
  3. """Simple notify2-based apt-get update notifier
  4.  
  5. Requires:
  6. - dbus-python (A.K.A. python-dbus)
  7. - notify2
  8. - python-gobject (for Python 2.x)
  9.  
  10. (Though it shouldn't be too difficult to adapt to Python 3.x since
  11. python-gobject is the only dependency with a changed API.)
  12. """
  13.  
  14. from __future__ import (absolute_import, division, print_function,
  15. with_statement, unicode_literals)
  16.  
  17. __author__ = "Stephan Sokolow (deitarion/SSokolow)"
  18. __version__ = "0.1"
  19. __license__ = "MIT"
  20.  
  21. import os, re, subprocess, sys
  22. import gobject
  23. import notify2 as notify
  24.  
  25. from dbus.mainloop.glib import DBusGMainLoop
  26. DBusGMainLoop(set_as_default=True)
  27.  
  28. TERM_CMD = ['urxvt', '-e']
  29. TIMEOUT = 1000 * 3600 * 23 # 23 hours
  30. ICON_PATH = os.path.expanduser(
  31. "~/.local/share/icons/elementary/apps/16/update-notifier.svg")
  32.  
  33. def die(title, msg):
  34. """Send an error message via libnotify and exit"""
  35. notification = notify.Notification(title, msg, ICON_PATH)
  36. notification.set_timeout(notify.EXPIRES_NEVER)
  37. notification.show()
  38. sys.exit(1)
  39.  
  40. def enwindow():
  41. """Ensure that we are running in a terminal window"""
  42. argv = TERM_CMD + sys.argv + ['--no-prompt']
  43. try:
  44. os.execvp(argv[0], argv)
  45. except OSError:
  46. die("Failed to launch terminal!",
  47. "Could not run command:\n{}".format(
  48. repr(argv)))
  49.  
  50. class AptWrapper(object):
  51. """API abstraction to make it easy to swap in `python-apt` later"""
  52. _apt_command = ["/usr/bin/apt-get", "dist-upgrade"]
  53. _re_apt_line = re.compile(r"""^Inst[ ]
  54. (?P<name>\S+)[ ]
  55. \[(?P<oldver>[^\]]*)\][ ]
  56. \((?P<newver>\S+)[ ]
  57. (?P<source>\S+)[ ]
  58. \[(?P<arch>[^\]]+)\].*\)
  59. """, re.VERBOSE | re.MULTILINE)
  60.  
  61. def apply_updates(self):
  62. """Request that pending updates be applied"""
  63. argv = ['sudo'] + self._apt_command
  64. try:
  65. subprocess.check_call(argv)
  66. except (OSError, subprocess.CalledProcessError):
  67. die("apt-get Failure!",
  68. "Attempting to call the following command returned failure:\n"
  69. "{}".format(repr(argv)))
  70.  
  71. def get_updates(self):
  72. """Retrieve a list of pending package updates"""
  73. argv = (self._apt_command +
  74. ['-s', '-q', '-y', '--allow-unauthenticated'])
  75.  
  76. try:
  77. pkgs = self._re_apt_line.findall(subprocess.check_output(argv))
  78. except (OSError, subprocess.CalledProcessError):
  79. die("apt-get Failure!",
  80. "Attempting to call the following command returned failure:\n"
  81. "{}".format(repr(argv)))
  82.  
  83. pkgs = [{'name': x[0], 'old_ver': x[1], 'new_ver': x[2]} for x in pkgs]
  84. pkgs.sort()
  85. return pkgs
  86.  
  87. class NotificationPrompt(object):
  88. """API wrapper for using a libnotify popup as a prompt"""
  89. def __init__(self, mainloop, userdata=None, timeout=TIMEOUT):
  90. self.loop = mainloop
  91. self.timeout = timeout
  92. self.userdata = userdata
  93.  
  94. def cb_cancel(self, userdata):
  95. """Callback to quit the program when the notification is closed"""
  96. self.loop.quit()
  97.  
  98. def prompt(self, title, msg, cb_ok, cb_ok_title='OK'):
  99. notification = notify.Notification(title, msg, ICON_PATH)
  100. notification.set_timeout(self.timeout)
  101. notification.connect('closed', self.cb_cancel)
  102. notification.add_action('ok', cb_ok_title, cb_ok, self.userdata)
  103. notification.show()
  104.  
  105. def cb_update_requested(notification=None, action_key=None):
  106. """Callback to pop up an apt-get terminal if the button is clicked"""
  107. if notification:
  108. notification.close()
  109. enwindow()
  110.  
  111. AptWrapper().apply_updates()
  112.  
  113.  
  114. def main():
  115. """The main entry point, compatible with setuptools entry points."""
  116. # If we're running on Python 2, take responsibility for preventing
  117. # output from causing UnicodeEncodeErrors. (Done here so it should only
  118. # happen when not being imported by some other program.)
  119. if sys.version_info.major < 3:
  120. reload(sys)
  121. sys.setdefaultencoding('utf-8') # pylint: disable=no-member
  122.  
  123. from argparse import ArgumentParser, RawTextHelpFormatter
  124. parser = ArgumentParser(formatter_class=RawTextHelpFormatter,
  125. description=__doc__.replace('\r\n', '\n').split('\n--snip--\n')[0])
  126. parser.add_argument('--version', action='version',
  127. version="%%(prog)s v%s" % __version__)
  128. parser.add_argument('--no-prompt', default=False, action='store_true',
  129. help="Jump straight to applying updates")
  130.  
  131. args = parser.parse_args()
  132. loop = gobject.MainLoop()
  133.  
  134. notify.init("update_notifier")
  135. apt = AptWrapper()
  136.  
  137. if args.no_prompt:
  138. cb_update_requested()
  139. elif apt.get_updates():
  140. if 'actions' in notify.get_server_caps():
  141. prompt = NotificationPrompt(loop)
  142. prompt.prompt("Updates Available",
  143. "Packages updates are available via apt-get",
  144. cb_update_requested, 'Update')
  145. else:
  146. raise NotImplementedError("TODO: Fall back to Zenity")
  147.  
  148. gobject.timeout_add(TIMEOUT, loop.quit)
  149. loop.run()
  150.  
  151. if __name__ == '__main__':
  152. main()
  153.  
  154. # vim: set sw=4 sts=4 expandtab :
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement