From 5400519abcbc811ea8a5ff8760a2e1282a9cd999 Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Tue, 17 Apr 2012 19:53:57 +0200 Subject: [PATCH] [RigoDaemon] feed App Management notes to clients, bump API Update Rigo as well and let it push the notes to the Terminal Widget (they come from stdout and stderr, sorry) --- rigo/RigoDaemon/app.py | 104 ++++++++++++++++++++++++++++++-- rigo/rigo/controllers/daemon.py | 42 ++++++++++--- 2 files changed, 132 insertions(+), 14 deletions(-) diff --git a/rigo/RigoDaemon/app.py b/rigo/RigoDaemon/app.py index f0a80bd19..9ce16da3f 100755 --- a/rigo/RigoDaemon/app.py +++ b/rigo/RigoDaemon/app.py @@ -11,6 +11,7 @@ """ import os +import stat # entropy.i18n will pick this up os.environ['ETP_GETTEXT_DOMAIN'] = "rigo" @@ -265,8 +266,10 @@ class FakeOutFile(object): Fake Standard Output / Error file object """ - def __init__(self, entropy_client): + def __init__(self, entropy_client, app_mgmt_mutex, app_mgmt_notes): self._entropy = entropy_client + self._app_mgmt_mutex = app_mgmt_mutex + self._app_mgmt_notes = app_mgmt_notes self._rfd, self._wfd = os.pipe() task = ParallelTask(self._pusher) task.name = "FakeOutFilePusher" @@ -282,6 +285,16 @@ class FakeOutFile(object): if err.errno == errno.EINTR: continue raise + # record to App Management Log, if enabled + with self._app_mgmt_mutex: + fobj = self._app_mgmt_notes['fobj'] + if fobj is not None: + try: + fobj.write(chunk) + except (OSError, IOError) as err: + write_output("_pusher thread: " + "cannot write to app log: " + "%s" % (repr(err),)) self._entropy.output(chunk, _raw=True) def close(self): @@ -376,7 +389,7 @@ class RigoDaemonService(dbus.service.Object): BUS_NAME = DbusConfig.BUS_NAME OBJECT_PATH = DbusConfig.OBJECT_PATH - API_VERSION = 3 + API_VERSION = 4 """ RigoDaemon is the dbus service Object in charge of executing @@ -548,9 +561,18 @@ class RigoDaemonService(dbus.service.Object): self._deferred_shutdown = False self._deferred_shutdown_mutex = Lock() + self._app_mgmt_mutex = Lock() + self._app_mgmt_notes = { + 'fobj': None, + 'path': None + } + Entropy.set_daemon(self) self._entropy = Entropy() - self._fakeout = FakeOutFile(self._entropy) + self._fakeout = FakeOutFile( + self._entropy, + self._app_mgmt_mutex, + self._app_mgmt_notes) executable_path = sys.argv[0] write_output( @@ -1177,6 +1199,41 @@ class RigoDaemonService(dbus.service.Object): # put it back self._action_queue_waiter.release() else: + + # before unbusy, read App Management Notes + with self._app_mgmt_mutex: + # 0o644 + perms = stat.S_IREAD | stat.S_IWRITE \ + | stat.S_IRGRP | stat.S_IROTH + + fobj = self._app_mgmt_notes['fobj'] + app_log_path = self._app_mgmt_notes['path'] + if fobj is not None: + try: + fobj.close() + except OSError: + write_output( + "_action_queue_finally: " + "unexpected close() error %s" % ( + repr(err),)) + self._app_mgmt_notes['fobj'] = None + + # root is already owning it + try: + os.chmod(app_log_path, perms) + except OSError as err: + if err.errno == errno.ENOENT: + # wtf, file vanished? + app_log_path = "" + elif err.errno == errno.EPERM: + # somebody changed the permissions + app_log_path = "" + else: + write_output( + "_action_queue_finally: " + "unexpected error %s" % (repr(err),)) + app_log_path = "" + try: self._unbusy(activity) except ActivityStates.AlreadyAvailableError: @@ -1193,7 +1250,7 @@ class RigoDaemonService(dbus.service.Object): GLib.idle_add( self.activity_completed, activity, success) GLib.idle_add( - self.applications_managed, outcome) + self.applications_managed, outcome, app_log_path) self._maybe_signal_configuration_updates() is_app = True @@ -2403,8 +2460,10 @@ class RigoDaemonService(dbus.service.Object): self._enqueue_action_busy_hold_sem.acquire() try: activity = ActivityStates.MANAGING_APPLICATIONS + busied = False try: self._busy(activity) + busied = True except ActivityStates.BusyError: # I am already busy doing other stuff, cannot # satisfy request @@ -2416,6 +2475,39 @@ class RigoDaemonService(dbus.service.Object): "already busy, just enqueue", debug=True) + if busied: + # Setup Application Management + # Install/Remove notes + tmp_fd, tmp_path = tempfile.mkstemp( + prefix="RigoDaemonAppMgmt", + suffix=".notes") + with self._app_mgmt_mutex: + fobj = self._app_mgmt_notes['fobj'] + path = self._app_mgmt_notes['path'] + if fobj is not None: + try: + fobj.close() + except (OSError, IOError): + write_output( + "enqueue_application_action: " + "busied, but cannot close previous fd") + if path is not None: + try: + os.remove(path) + except (OSError, IOError): + write_output( + "enqueue_application_action: " + "busied, but cannot remove previous path") + try: + fobj = os.fdopen(tmp_fd, "w") + except OSError as err: + write_output( + "enqueue_application_action: " + "cannot open tmp_fd: %s" % (repr(err),)) + fobj = None + self._app_mgmt_notes['fobj'] = fobj + self._app_mgmt_notes['path'] = tmp_path + task = ParallelTask( self._enqueue_application_action_internal, pid, package_id, repository_id, package_path, @@ -2902,8 +2994,8 @@ class RigoDaemonService(dbus.service.Object): " %s" % (locals(),), debug=True) @dbus.service.signal(dbus_interface=BUS_NAME, - signature='s') - def applications_managed(self, outcome): + signature='ss') + def applications_managed(self, outcome, app_log_path): """ Enqueued Application actions have been completed. """ diff --git a/rigo/rigo/controllers/daemon.py b/rigo/rigo/controllers/daemon.py index d54f1ee82..9a091eab1 100644 --- a/rigo/rigo/controllers/daemon.py +++ b/rigo/rigo/controllers/daemon.py @@ -18,8 +18,9 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """ - +import sys import time +import codecs from threading import Lock, Semaphore, current_thread from collections import deque @@ -178,7 +179,7 @@ class RigoServiceController(GObject.Object): _UNAVAILABLE_REPOSITORIES_SIGNAL = "unavailable_repositories" _OLD_REPOSITORIES_SIGNAL = "old_repositories" _NOTICEBOARDS_AVAILABLE_SIGNAL = "noticeboards_available" - _SUPPORTED_APIS = [3] + _SUPPORTED_APIS = [4] def __init__(self, rigo_app, activity_rwsem, entropy_client, entropy_ws): @@ -709,7 +710,8 @@ class RigoServiceController(GObject.Object): box.add_button(_("Show me"), _show_me) self._nc.append(box) - def _applications_managed_signal(self, outcome, local_activity): + def _applications_managed_signal(self, outcome, app_log_path, + local_activity): """ Signal coming from RigoDaemon notifying us that the MANAGING_APPLICATIONS is over. @@ -759,6 +761,28 @@ class RigoServiceController(GObject.Object): if outcome != DaemonAppTransactionOutcome.SUCCESS: self._notify_app_management_outcome(None, outcome) + # Send Application Management notes to Terminal. + if app_log_path: + enc = etpConst['conf_encoding'] + app_notes = None + try: + with codecs.open(app_log_path, encoding=enc) as log_f: + app_notes = log_f.read() + except (IOError, OSError,) as err: + const_debug_write( + __name__, + "_applications_managed_signal: " + "cannot read app_log_path: %s" % (repr(err),)) + if app_notes is not None: + if len(app_notes) > 3: # chars + if self._terminal is not None: + self._terminal.reset() + self._output_signal( + app_notes, None, None, False, 0, "info", + 0, 0, False, True) + if self._wc is not None: + self._wc.expand_terminal() + # we don't expect to fail here, it would # mean programming error. self.unbusy(local_activity) @@ -2154,14 +2178,15 @@ class RigoServiceController(GObject.Object): signal_sem = Semaphore(1) - def _applications_managed_signal(outcome): + def _applications_managed_signal(outcome, app_log_path): if not signal_sem.acquire(False): # already called, no need to call again return # this is done in order to have it called # only once by two different code paths self._applications_managed_signal( - outcome, LocalActivityStates.MANAGING_APPLICATIONS) + outcome, app_log_path, + LocalActivityStates.MANAGING_APPLICATIONS) with self._registered_signals_mutex: # connect our signal @@ -2252,7 +2277,7 @@ class RigoServiceController(GObject.Object): # callback in random threads. GLib.idle_add(self._applications_managed_signal, DaemonAppTransactionOutcome.SUCCESS, - local_activity) + "", local_activity) def _package_install_request(self, package_path, simulate=False): """ @@ -2492,14 +2517,15 @@ class RigoServiceController(GObject.Object): signal_sem = Semaphore(1) - def _applications_managed_signal(outcome): + def _applications_managed_signal(outcome, app_log_path): if not signal_sem.acquire(False): # already called, no need to call again return # this is done in order to have it called # only once by two different code paths self._applications_managed_signal( - outcome, LocalActivityStates.UPGRADING_SYSTEM) + outcome, app_log_path, + LocalActivityStates.UPGRADING_SYSTEM) with self._registered_signals_mutex: # connect our signal