Files
entropy/rigo/rigo/controllers/daemon.py
T
2012-04-03 15:00:44 +02:00

1971 lines
71 KiB
Python

# -*- coding: utf-8 -*-
"""
Copyright (C) 2012 Fabio Erculiani
Authors:
Fabio Erculiani
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 3.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
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 time
from threading import Lock, Semaphore
import dbus
from gi.repository import Gtk, GLib, GObject
from rigo.enums import AppActions, RigoViewStates, \
LocalActivityStates
from rigo.models.application import Application
from rigo.ui.gtk3.widgets.notifications import NotificationBox, \
PleaseWaitNotificationBox, LicensesNotificationBox, \
OrphanedAppsNotificationBox
from rigo.utils import prepare_markup
from RigoDaemon.enums import ActivityStates as DaemonActivityStates, \
AppActions as DaemonAppActions, \
AppTransactionOutcome as DaemonAppTransactionOutcome, \
AppTransactionStates as DaemonAppTransactionStates
from RigoDaemon.config import DbusConfig as DaemonDbusConfig
from entropy.const import const_debug_write, \
const_debug_enabled
from entropy.misc import ParallelTask
from entropy.i18n import _, ngettext
from entropy.output import darkgreen, brown, darkred, red, blue
import entropy.tools
class RigoServiceController(GObject.Object):
"""
This is the Rigo Application frontend to RigoDaemon.
Handles privileged requests on our behalf.
"""
NOTIFICATION_CONTEXT_ID = "RigoServiceControllerContextId"
class ServiceNotificationBox(NotificationBox):
def __init__(self, message, message_type):
NotificationBox.__init__(
self, message,
tooltip=_("Good luck!"),
message_type=message_type,
context_id=RigoServiceController.NOTIFICATION_CONTEXT_ID)
class SharedLocker(object):
"""
SharedLocker ensures that Entropy Resources
lock and unlock operations are called once,
avoiding reentrancy, which is a property of
lock_resources() and unlock_resources(), even
during concurrent access.
"""
def __init__(self, entropy_client, locked):
self._entropy = entropy_client
self._locking_mutex = Lock()
self._locked = locked
def lock(self):
with self._locking_mutex:
lock = False
if not self._locked:
lock = True
self._locked = True
if lock:
self._entropy.lock_resources(
blocking=True, shared=True)
def unlock(self):
with self._locking_mutex:
unlock = False
if self._locked:
unlock = True
self._locked = False
if unlock:
self._entropy.unlock_resources()
__gsignals__ = {
# we request to lock the whole UI wrt repo
# interaction
"start-working" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT),
),
# Repositories have been updated
"repositories-updated" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,),
),
# Application actions have been completed
"applications-managed" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,),
),
# Application has been processed
"application-processed" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,),
),
# Application is being processed
"application-processing" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,),
),
"application-abort" : (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT,
GObject.TYPE_PYOBJECT,),
),
}
DBUS_INTERFACE = DaemonDbusConfig.BUS_NAME
DBUS_PATH = DaemonDbusConfig.OBJECT_PATH
_OUTPUT_SIGNAL = "output"
_REPOSITORIES_UPDATED_SIGNAL = "repositories_updated"
_TRANSFER_OUTPUT_SIGNAL = "transfer_output"
_PING_SIGNAL = "ping"
_RESOURCES_UNLOCK_REQUEST_SIGNAL = "resources_unlock_request"
_RESOURCES_LOCK_REQUEST_SIGNAL = "resources_lock_request"
_ACTIVITY_STARTED_SIGNAL = "activity_started"
_ACTIVITY_PROGRESS_SIGNAL = "activity_progress"
_ACTIVITY_COMPLETED_SIGNAL = "activity_completed"
_PROCESSING_APPLICATION_SIGNAL = "processing_application"
_APPLICATION_PROCESSING_UPDATE = "application_processing_update"
_APPLICATION_PROCESSED_SIGNAL = "application_processed"
_APPLICATIONS_MANAGED_SIGNAL = "applications_managed"
_UNSUPPORTED_APPLICATIONS_SIGNAL = "unsupported_applications"
_RESTARTING_UPGRADE_SIGNAL = "restarting_system_upgrade"
_SUPPORTED_APIS = [0]
def __init__(self, rigo_app, activity_rwsem,
entropy_client, entropy_ws):
GObject.Object.__init__(self)
self._rigo = rigo_app
self._activity_rwsem = activity_rwsem
self._nc = None
self._bottom_nc = None
self._wc = None
self._avc = None
self._apc = None
self._terminal = None
self._entropy = entropy_client
self._entropy_ws = entropy_ws
self.__dbus_main_loop = None
self.__system_bus = None
self.__entropy_bus = None
self.__entropy_bus_mutex = Lock()
self._registered_signals = {}
self._registered_signals_mutex = Lock()
self._local_transactions = {}
self._local_activity = LocalActivityStates.READY
self._local_activity_mutex = Lock()
self._reset_daemon_transaction_state()
self._please_wait_box = None
self._please_wait_mutex = Lock()
self._application_request_serializer = Lock()
# this controls the the busy()/unbusy()
# atomicity.
self._application_request_mutex = Lock()
# threads doing repo activities must coordinate
# with this
self._update_repositories_mutex = Lock()
def _reset_daemon_transaction_state(self):
"""
Reset local daemon transaction state bits.
"""
self._daemon_activity_progress = 0
self._daemon_processing_application_state = None
self._daemon_transaction_app = None
self._daemon_transaction_app_state = None
self._daemon_transaction_app_progress = -1
def set_applications_controller(self, avc):
"""
Bind ApplicationsViewController object to this class.
"""
self._avc = avc
def set_application_controller(self, apc):
"""
Bind ApplicationViewController object to this class.
"""
self._apc = apc
def set_terminal(self, terminal):
"""
Bind a TerminalWidget to this object, in order to be used with
events coming from dbus.
"""
self._terminal = terminal
def set_work_controller(self, wc):
"""
Bind a WorkViewController to this object in order to be used to
set progress status.
"""
self._wc = wc
def set_notification_controller(self, nc):
"""
Bind a NotificationViewController to this object.
"""
self._nc = nc
def set_bottom_notification_controller(self, bottom_nc):
"""
Bind a BottomNotificationViewController to this object.
"""
self._bottom_nc = bottom_nc
def setup(self, shared_locked):
"""
Execute object setup once initialization phase is complete.
This phase is comprehensive of all the set_* method calls.
"""
if self._apc is not None:
# connect application request events
self._apc.connect(
"application-request-action",
self._on_application_request_action)
# since we handle the lock/unlock of entropy
# resources here, we need to know what's the
# initial state
self._shared_locker = self.SharedLocker(
self._entropy, shared_locked)
def service_available(self):
"""
Return whether the RigoDaemon dbus service is
available.
"""
try:
self._entropy_bus
return True
except dbus.exceptions.DBusException:
return False
def busy(self, local_activity):
"""
Become busy, switch to some local activity.
If an activity is already taking place,
LocalActivityStates.BusyError is raised.
If the active activity equals the requested one,
LocalActivityStates.SameError is raised.
"""
with self._local_activity_mutex:
if self._local_activity == local_activity:
raise LocalActivityStates.SameError()
if self._local_activity != LocalActivityStates.READY:
raise LocalActivityStates.BusyError()
GLib.idle_add(self._bottom_nc.set_activity,
local_activity)
self._local_activity = local_activity
def unbusy(self, current_activity):
"""
Exit from busy state, switch to local activity called "READY".
If we're already out of any activity, raise
LocalActivityStates.AlreadyReadyError()
"""
with self._local_activity_mutex:
if self._local_activity == LocalActivityStates.READY:
raise LocalActivityStates.AlreadyReadyError()
if self._local_activity != current_activity:
raise LocalActivityStates.UnbusyFromDifferentActivity()
GLib.idle_add(self._bottom_nc.set_activity,
LocalActivityStates.READY)
self._local_activity = LocalActivityStates.READY
def local_activity(self):
"""
Return the current local activity (enum from LocalActivityStates)
"""
return self._local_activity
def local_transactions(self):
"""
Return the current local transaction state mapping.
"""
return self._local_transactions
def supported_apis(self):
"""
Return a list of supported RigoDaemon APIs.
"""
return RigoServiceController._SUPPORTED_APIS
@property
def repositories_lock(self):
"""
Return the Repositories Update Mutex object.
This lock protects repositories access during their
physical update.
"""
return self._update_repositories_mutex
@property
def _dbus_main_loop(self):
if self.__dbus_main_loop is None:
from dbus.mainloop.glib import DBusGMainLoop
self.__dbus_main_loop = DBusGMainLoop(set_as_default=True)
return self.__dbus_main_loop
@property
def _system_bus(self):
if self.__system_bus is None:
self.__system_bus = dbus.SystemBus(
mainloop=self._dbus_main_loop)
return self.__system_bus
@property
def _entropy_bus(self):
with self.__entropy_bus_mutex:
if self.__entropy_bus is None:
self.__entropy_bus = self._system_bus.get_object(
self.DBUS_INTERFACE, self.DBUS_PATH
)
# ping/pong signaling, used to let
# RigoDaemon release exclusive locks
# when no client is connected
self.__entropy_bus.connect_to_signal(
self._PING_SIGNAL, self._ping_signal,
dbus_interface=self.DBUS_INTERFACE)
# Entropy stdout/stderr messages
self.__entropy_bus.connect_to_signal(
self._OUTPUT_SIGNAL, self._output_signal,
dbus_interface=self.DBUS_INTERFACE)
# Entropy UrlFetchers messages
self.__entropy_bus.connect_to_signal(
self._TRANSFER_OUTPUT_SIGNAL,
self._transfer_output_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon Entropy Resources unlock requests
self.__entropy_bus.connect_to_signal(
self._RESOURCES_UNLOCK_REQUEST_SIGNAL,
self._resources_unlock_request_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon Entropy Resources lock requests
self.__entropy_bus.connect_to_signal(
self._RESOURCES_LOCK_REQUEST_SIGNAL,
self._resources_lock_request_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon is telling us that a new activity
# has just begun
self.__entropy_bus.connect_to_signal(
self._ACTIVITY_STARTED_SIGNAL,
self._activity_started_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon is telling us the activity
# progress
self.__entropy_bus.connect_to_signal(
self._ACTIVITY_PROGRESS_SIGNAL,
self._activity_progress_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon is telling us that an activity
# has been completed
self.__entropy_bus.connect_to_signal(
self._ACTIVITY_COMPLETED_SIGNAL,
self._activity_completed_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon tells us that a queue action
# is being processed as we cycle (lol)
self.__entropy_bus.connect_to_signal(
self._PROCESSING_APPLICATION_SIGNAL,
self._processing_application_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon tells us about an Application
# processing status update
self.__entropy_bus.connect_to_signal(
self._APPLICATION_PROCESSING_UPDATE,
self._application_processing_update_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon tells us that a queued app action
# is now complete
self.__entropy_bus.connect_to_signal(
self._APPLICATION_PROCESSED_SIGNAL,
self._application_processed_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon tells us that there are unsupported
# applications currently installed
self.__entropy_bus.connect_to_signal(
self._UNSUPPORTED_APPLICATIONS_SIGNAL,
self._unsupported_applications_signal,
dbus_interface=self.DBUS_INTERFACE)
# RigoDaemon tells us that the currently scheduled
# System Upgrade is being restarted due to further
# updates being available
self.__entropy_bus.connect_to_signal(
self._RESTARTING_UPGRADE_SIGNAL,
self._restarting_system_upgrade_signal,
dbus_interface=self.DBUS_INTERFACE)
return self.__entropy_bus
### GOBJECT EVENTS
def _on_application_request_action(self, apc, app, app_action):
"""
This event comes from ApplicationViewController notifying
that user would like to schedule the given action for App.
"app" is an Application object, "app_action" is an AppActions
enum value.
"""
const_debug_write(
__name__,
"_on_application_request_action: "
"%s -> %s" % (app, app_action))
self.application_request(app, app_action)
### DBUS SIGNALS
def _processing_application_signal(self, package_id, repository_id,
daemon_action, daemon_tx_state):
const_debug_write(
__name__,
"_processing_application_signal: received for "
"%d, %s, action: %s, tx state: %s" % (
package_id, repository_id, daemon_action,
daemon_tx_state))
def _rate_limited_set_application(app):
_sleep_secs = 1.0
if self._wc is not None:
last_t = getattr(self, "_rate_lim_set_app", 0.0)
cur_t = time.time()
if (abs(cur_t - last_t) < _sleep_secs):
# yeah, we're nazi, we sleep in the mainloop
time.sleep(_sleep_secs)
setattr(self, "_rate_lim_set_app", cur_t)
self._wc.set_application(app, daemon_action)
# circular dep trick
app = None
def _redraw_callback(*args):
if self._wc is not None:
GLib.idle_add(
_rate_limited_set_application, app)
app = Application(
self._entropy, self._entropy_ws,
(package_id, repository_id),
redraw_callback=_redraw_callback)
self._daemon_processing_application_state = daemon_tx_state
_rate_limited_set_application(app)
self._daemon_transaction_app = app
self._daemon_transaction_app_state = None
self._daemon_transaction_app_progress = 0
self.emit("application-processing", app, daemon_action)
def _application_processing_update_signal(
self, package_id, repository_id, app_transaction_state,
progress):
const_debug_write(
__name__,
"_application_processing_update_signal: received for "
"%i, %s, transaction_state: %s, progress: %i" % (
package_id, repository_id,
app_transaction_state, progress))
app = Application(
self._entropy, self._entropy_ws,
(package_id, repository_id))
self._daemon_transaction_app = app
self._daemon_transaction_app_progress = progress
self._daemon_transaction_app_state = app_transaction_state
def _application_processed_signal(self, package_id, repository_id,
daemon_action, app_outcome):
const_debug_write(
__name__,
"_application_processed_signal: received for "
"%i, %s, action: %s, outcome: %s" % (
package_id, repository_id, daemon_action, app_outcome))
self._daemon_transaction_app = None
self._daemon_transaction_app_progress = -1
self._daemon_transaction_app_state = None
app = Application(
self._entropy, self._entropy_ws,
(package_id, repository_id),
redraw_callback=None)
self.emit("application-processed", app, daemon_action,
app_outcome)
if app_outcome != DaemonAppTransactionOutcome.SUCCESS:
msg = prepare_markup(_("An <b>unknown error</b> occurred"))
if app_outcome == DaemonAppTransactionOutcome.DOWNLOAD_ERROR:
msg = prepare_markup(_("<b>%s</b> download failed")) % (
app.name,)
elif app_outcome == DaemonAppTransactionOutcome.INSTALL_ERROR:
msg = prepare_markup(_("<b>%s</b> install failed")) % (
app.name,)
elif app_outcome == DaemonAppTransactionOutcome.REMOVE_ERROR:
msg = prepare_markup(_("<b>%s</b> removal failed")) % (
app.name,)
elif app_outcome == \
DaemonAppTransactionOutcome.PERMISSION_DENIED:
msg = prepare_markup(_("<b>%s</b>, not authorized")) % (
app.name,)
elif app_outcome == DaemonAppTransactionOutcome.INTERNAL_ERROR:
msg = prepare_markup(_("<b>%s</b>, internal error")) % (
app.name,)
elif app_outcome == \
DaemonAppTransactionOutcome.DEPENDENCIES_NOT_FOUND_ERROR:
msg = prepare_markup(
_("<b>%s</b> dependencies not found")) % (
app.name,)
elif app_outcome == \
DaemonAppTransactionOutcome.DEPENDENCIES_COLLISION_ERROR:
msg = prepare_markup(
_("<b>%s</b> dependencies collision error")) % (
app.name,)
elif app_outcome == \
DaemonAppTransactionOutcome.DEPENDENCIES_NOT_REMOVABLE_ERROR:
msg = prepare_markup(
_("<b>%s</b> dependencies not removable error")) % (
app.name,)
elif app_outcome == \
DaemonAppTransactionOutcome.DISK_FULL_ERROR:
msg = prepare_markup(
_("Disk full, cannot download nor unpack Applications"))
box = NotificationBox(
msg,
tooltip=_("An error occurred"),
message_type=Gtk.MessageType.ERROR,
context_id="ApplicationProcessedSignalError")
def _show_me(*args):
self._bottom_nc.emit("show-work-view")
box.add_destroy_button(_("Ok, thanks"))
box.add_button(_("Show me"), _show_me)
self._nc.append(box)
def _applications_managed_signal(self, success, local_activity):
"""
Signal coming from RigoDaemon notifying us that the
MANAGING_APPLICATIONS is over.
"""
with self._registered_signals_mutex:
our_signals = self._registered_signals.get(
self._APPLICATIONS_MANAGED_SIGNAL)
if our_signals is None:
# not generated by us
return
if our_signals:
sig_match = our_signals.pop(0)
sig_match.remove()
else:
# somebody already consumed this signal
if const_debug_enabled():
const_debug_write(
__name__,
"_applications_managed_signal: "
"already consumed")
return
with self._application_request_mutex:
# should be safe to block in here, because
# the other thread can only block here when
# we're not in busy state
# This way repository in-RAM caches are reset
# otherwise installed repository metadata becomes
# inconsistent
self._release_local_resources(clear_avc=False)
# reset progress bar, we're done with it
if self._wc is not None:
self._wc.reset_progress()
# we don't expect to fail here, it would
# mean programming error.
self.unbusy(local_activity)
# 2 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
self.emit("applications-managed", success, local_activity)
const_debug_write(
__name__,
"_applications_managed_signal: applications-managed")
def _restarting_system_upgrade_signal(self, updates_amount):
"""
System Upgrade Activity is being restarted due to further updates
being available. This happens when RigoDaemon processed critical
updates during the previous activity execution.
"""
if self._nc is not None:
msg = "%s. %s" % (
_("<b>System Upgrade</b> Activity is being <i>restarted</i>"),
ngettext("There is <b>%i</b> more update",
"There are <b>%i</b> more updates",
int(updates_amount)) % (updates_amount,),)
box = self.ServiceNotificationBox(
prepare_markup(msg), Gtk.MessageType.INFO)
self._nc.append(box, timeout=20)
def _unsupported_applications_signal(self, manual_package_ids,
package_ids):
const_debug_write(
__name__,
"_unsupported_applications_signal: manual: "
"%s, normal: %s" % (
manual_package_ids, package_ids))
self._entropy.rwsem().reader_acquire()
try:
repository_id = self._entropy.installed_repository(
).repository_id()
finally:
self._entropy.rwsem().reader_release()
if manual_package_ids or package_ids:
manual_apps = []
apps = []
list_objs = [(manual_package_ids, manual_apps),
(package_ids, apps)]
for source_list, app_list in list_objs:
for package_id in source_list:
app = Application(
self._entropy, self._entropy_ws,
(package_id, repository_id))
app_list.append(app)
if self._nc is not None:
box = OrphanedAppsNotificationBox(
self._apc, self, self._entropy, self._entropy_ws,
manual_apps, apps)
self._nc.append(box)
def _repositories_updated_signal(self, result, message):
"""
Signal coming from RigoDaemon notifying us that repositories have
been updated.
"""
with self._registered_signals_mutex:
our_signals = self._registered_signals.get(
self._REPOSITORIES_UPDATED_SIGNAL)
if our_signals is None:
# not generated by us
return
if our_signals:
sig_match = our_signals.pop(0)
sig_match.remove()
else:
# somebody already consumed this signal
if const_debug_enabled():
const_debug_write(
__name__,
"_repositories_updated_signal: "
"already consumed")
return
# reset progress bar, we're done with it
if self._wc is not None:
self._wc.reset_progress()
local_activity = LocalActivityStates.UPDATING_REPOSITORIES
# we don't expect to fail here, it would
# mean programming error.
self.unbusy(local_activity)
# 1 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
self.repositories_lock.release()
self.emit("repositories-updated",
result, message)
const_debug_write(
__name__,
"_repositories_updated_signal: repositories-updated")
def _output_signal(self, text, header, footer, back, importance, level,
count_c, count_t, percent, raw):
"""
Entropy Client output() method from RigoDaemon comes here.
Will be redirected to a virtual terminal here in Rigo.
This is called in the Gtk.MainLoop.
"""
if count_c == 0 and count_t == 0:
count = None
else:
count = (count_c, count_t)
if self._terminal is None:
self._entropy.output(text, header=header, footer=footer,
back=back, importance=importance,
level=level, count=count,
percent=percent)
return
if raw:
self._terminal.feed_child(text.replace("\n", "\r\n"))
return
color_func = darkgreen
if level == "warning":
color_func = brown
elif level == "error":
color_func = darkred
count_str = ""
if count:
if len(count) > 1:
if percent:
fraction = float(count[0])/count[1]
percent_str = str(round(fraction*100, 1))
count_str = " ("+percent_str+"%) "
else:
count_str = " (%s/%s) " % (red(str(count[0])),
blue(str(count[1])),)
# reset cursor
self._terminal.feed_child(chr(27) + '[2K')
if back:
msg = "\r" + color_func(">>") + " " + header + count_str + text \
+ footer
else:
msg = "\r" + color_func(">>") + " " + header + count_str + text \
+ footer + "\r\n"
self._terminal.feed_child(msg)
def _transfer_output_signal(self, average, downloaded_size, total_size,
data_transfer_bytes, time_remaining_secs):
"""
Entropy UrlFetchers update() method (via transfer_output()) from
RigoDaemon comes here. Will be redirected to WorkAreaController
Progress Bar if available.
"""
if self._wc is None:
return
fraction = float(average) / 100
human_dt = entropy.tools.bytes_into_human(data_transfer_bytes)
total = round(total_size, 1)
if total > 1:
text = "%s/%s kB @ %s/sec, %s" % (
round(float(downloaded_size)/1024, 1),
total,
human_dt, time_remaining_secs)
else:
text = None
self._wc.set_progress(fraction, text=text)
def _ping_signal(self):
"""
Need to call pong() as soon as possible to hold all Entropy
Resources allocated by RigoDaemon.
"""
dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).pong()
def _resources_lock_request_signal(self, activity):
"""
RigoDaemon is asking us to acquire a shared Entropy Resources
lock. First we check if we have released it.
"""
const_debug_write(
__name__,
"_resources_lock_request_signal: "
"called, with remote activity: %s" % (activity,))
def _resources_lock():
const_debug_write(
__name__,
"_resources_lock_request_signal._resources_lock: "
"enter (sleep)")
self._shared_locker.lock()
clear_avc = True
if activity in (
DaemonActivityStates.MANAGING_APPLICATIONS,
DaemonActivityStates.UPGRADING_SYSTEM,):
clear_avc = False
self._release_local_resources(clear_avc=clear_avc)
const_debug_write(
__name__,
"_resources_lock_request_signal._resources_lock: "
"regained shared lock")
task = ParallelTask(_resources_lock)
task.name = "ResourceLockAfterRelease"
task.daemon = True
task.start()
def _resources_unlock_request_signal(self, activity):
"""
RigoDaemon is asking us to release our Entropy Resources Lock.
An ActivityStates value is provided in order to let us decide
if we can acknowledge the request.
"""
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"called, with remote activity: %s" % (activity,))
if activity == DaemonActivityStates.UPDATING_REPOSITORIES:
# did we ask that or is it another client?
local_activity = self.local_activity()
if local_activity == LocalActivityStates.READY:
def _update_repositories():
self._release_local_resources()
accepted = self._update_repositories(
[], False, master=False)
if accepted:
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"_update_repositories accepted, unlocking")
self._shared_locker.unlock()
# another client, bend over XD
# LocalActivityStates value will be atomically
# switched in the above thread.
task = ParallelTask(_update_repositories)
task.daemon = True
task.name = "UpdateRepositoriesExternal"
task.start()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"somebody called repo update, starting here too")
elif local_activity == \
LocalActivityStates.UPDATING_REPOSITORIES:
def _unlocker():
self._release_local_resources() # CANBLOCK
self._shared_locker.unlock()
task = ParallelTask(_unlocker)
task.daemon = True
task.name = "UpdateRepositoriesInternal"
task.start()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"it's been us calling repositories update")
# it's been us calling it, ignore request
return
else:
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"not accepting RigoDaemon resources unlock request, "
"local activity: %s" % (local_activity,))
elif activity == DaemonActivityStates.MANAGING_APPLICATIONS:
local_activity = self.local_activity()
if local_activity == LocalActivityStates.READY:
def _application_request():
self._release_local_resources(clear_avc=False)
accepted = self._application_request(
None, None, master=False)
if accepted:
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"_application_request accepted, unlocking")
self._shared_locker.unlock()
# another client, bend over XD
# LocalActivityStates value will be atomically
# switched in the above thread.
task = ParallelTask(_application_request)
task.daemon = True
task.name = "ApplicationRequestExternal"
task.start()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"somebody called app request, starting here too")
elif local_activity == \
LocalActivityStates.MANAGING_APPLICATIONS:
self._release_local_resources(clear_avc=False)
self._shared_locker.unlock()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"it's been us calling manage apps")
# it's been us calling it, ignore request
return
elif activity == DaemonActivityStates.UPGRADING_SYSTEM:
local_activity = self.local_activity()
if local_activity == LocalActivityStates.READY:
def _upgrade_system():
accepted = self._upgrade_system(
False, master=False)
if accepted:
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"_upgrade_system accepted, unlocking")
self._shared_locker.unlock()
# another client, bend over XD
# LocalActivityStates value will be atomically
# switched in the above thread.
task = ParallelTask(_upgrade_system)
task.daemon = True
task.name = "UpgradeSystemExternal"
task.start()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"somebody called sys upgrade, starting here too")
elif local_activity == \
LocalActivityStates.UPGRADING_SYSTEM:
self._shared_locker.unlock()
const_debug_write(
__name__,
"_resources_unlock_request_signal: "
"it's been us calling system upgrade")
# it's been us calling it, ignore request
return
else:
const_debug_write(
__name__,
"_resources_unlock_request_signal 2: "
"not accepting RigoDaemon resources unlock request, "
"local activity: %s" % (local_activity,))
def _activity_started_signal(self, activity):
"""
RigoDaemon is telling us that the scheduled activity,
either by us or by another Rigo, has just begun and
that it, RigoDaemon, has now exclusive access to
Entropy Resources.
"""
const_debug_write(
__name__,
"_activity_started_signal: "
"called, with remote activity: %s" % (activity,))
self._reset_daemon_transaction_state()
# reset please wait notification then
self._please_wait(None)
def _activity_progress_signal(self, activity, progress):
"""
RigoDaemon is telling us the currently running activity
progress.
"""
const_debug_write(
__name__,
"_activity_progress_signal: "
"called, with remote activity: %s, val: %i" % (
activity, progress,))
# update progress bar if it's not used for pushing
# download state
if self._daemon_processing_application_state == \
DaemonAppTransactionStates.MANAGE:
if self._wc is not None:
prog = float(progress) / 100
prog_txt = "%d %%" % (progress,)
self._wc.set_progress(prog, text=prog_txt)
self._daemon_activity_progress = progress
def _activity_completed_signal(self, activity, success):
"""
RigoDaemon is telling us that the scheduled activity,
has been completed.
"""
const_debug_write(
__name__,
"_activity_completed_signal: "
"called, with remote activity: %s, success: %s" % (
activity, success,))
self._reset_daemon_transaction_state()
### GP PUBLIC METHODS
def get_transaction_state(self):
"""
Return current RigoDaemon Application transaction
state information, if available.
"""
app = self._daemon_transaction_app
state = self._daemon_transaction_app_state
progress = self._daemon_transaction_app_progress
if app is None:
state = None
progress = -1
if state is None:
app = None
progress = -1
return app, state, progress
def application_request(self, app, app_action, simulate=False):
"""
Start Application Action (install/remove).
"""
task = ParallelTask(self._application_request,
app, app_action, simulate=simulate)
task.name = "ApplicationRequest{%s, %s}" % (
app, app_action,)
task.daemon = True
task.start()
def upgrade_system(self, simulate=False):
"""
Start a System Upgrade.
"""
task = ParallelTask(self._upgrade_system,
simulate=simulate)
task.name = "UpgradeSystem{simulate=%s}" % (
simulate,)
task.daemon = True
task.start()
def update_repositories(self, repositories, force):
"""
Start Entropy Repositories Update
"""
task = ParallelTask(self._update_repositories,
repositories, force)
task.name = "UpdateRepositoriesThread"
task.daemon = True
task.start()
def activity(self):
"""
Return RigoDaemon activity states (any of RigoDaemon.ActivityStates
values).
"""
return dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).activity()
def action_queue_length(self):
"""
Return the current size of the RigoDaemon Application Action Queue.
"""
return dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).action_queue_length()
def action(self, app):
"""
Return Application transaction state (RigoDaemon.AppAction enum
value).
"""
package_id, repository_id = app.get_details().pkg
return dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).action(
package_id, repository_id)
def exclusive(self):
"""
Return whether RigoDaemon is running in with
Entropy Resources acquired in exclusive mode.
"""
return dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).exclusive()
def api(self):
"""
Return RigoDaemon API version
"""
return dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).api()
def _release_local_resources(self, clear_avc=True):
"""
Release all the local resources (like repositories)
that shall be used by RigoDaemon.
For example, leaving EntropyRepository objects open
would cause sqlite3 to deadlock.
"""
self._entropy.rwsem().writer_acquire()
try:
if clear_avc:
self._avc.clear_safe()
self._entropy.close_repositories()
finally:
self._entropy.rwsem().writer_release()
def _please_wait(self, show):
"""
Show a Please Wait NotificationBox if show is not None,
otherwise hide, if there.
"show" contains the NotificationBox message.
"""
msg = _("Waiting for <b>RigoDaemon</b>, please wait...")
with self._please_wait_mutex:
if show and self._please_wait_box:
return
if not show and not self._please_wait_box:
return
if not show and self._please_wait_box:
# remove from NotificationController
# if there
box = self._please_wait_box
self._please_wait_box = None
GLib.idle_add(self._nc.remove, box)
return
if show and not self._please_wait_box:
# create a new Please Wait Notification Box
sem = Semaphore(0)
def _make():
box = PleaseWaitNotificationBox(
msg,
RigoServiceController.NOTIFICATION_CONTEXT_ID)
self._please_wait_box = box
sem.release()
self._nc.append(box)
GLib.idle_add(_make)
sem.acquire()
def _update_repositories(self, repositories, force,
master=True):
"""
Ask RigoDaemon to update repositories once we're
100% sure that the UI is locked down.
"""
# 1 -- ACTIVITY CRIT :: ON
self._activity_rwsem.writer_acquire() # CANBLOCK
local_activity = LocalActivityStates.UPDATING_REPOSITORIES
try:
self.busy(local_activity)
# will be unlocked when we get the signal back
except LocalActivityStates.BusyError:
const_debug_write(__name__, "_update_repositories: "
"LocalActivityStates.BusyError!")
# 1 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
return False
except LocalActivityStates.SameError:
const_debug_write(__name__, "_update_repositories: "
"LocalActivityStates.SameError!")
# 1 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
return False
self._please_wait(True)
accepted = self._update_repositories_unlocked(
repositories, force, master)
if not accepted:
self.unbusy(local_activity)
# 1 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
def _notify():
box = self.ServiceNotificationBox(
prepare_markup(
_("Another activity is currently in progress")),
Gtk.MessageType.ERROR)
box.add_destroy_button(_("K thanks"))
self._nc.append(box)
GLib.idle_add(_notify)
# unhide please wait notification
self._please_wait(False)
return False
return True
def _update_repositories_unlocked(self, repositories, force,
master):
"""
Internal method handling the actual Repositories Update
execution.
"""
if self._wc is not None:
GLib.idle_add(self._wc.activate_progress_bar)
GLib.idle_add(self._wc.deactivate_app_box)
GLib.idle_add(self.emit, "start-working",
RigoViewStates.WORK_VIEW_STATE, True)
const_debug_write(__name__, "RigoServiceController: "
"_update_repositories_unlocked: "
"start-working")
while not self._rigo.is_ui_locked():
const_debug_write(__name__, "RigoServiceController: "
"_update_repositories_unlocked: "
"waiting Rigo UI lock")
time.sleep(0.5)
const_debug_write(__name__, "RigoServiceController: "
"_update_repositories_unlocked: "
"rigo UI now locked!")
signal_sem = Semaphore(1)
def _repositories_updated_signal(result, message):
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._repositories_updated_signal(
result, message)
with self._registered_signals_mutex:
# connect our signal
sig_match = self._entropy_bus.connect_to_signal(
self._REPOSITORIES_UPDATED_SIGNAL,
_repositories_updated_signal,
dbus_interface=self.DBUS_INTERFACE)
# and register it as a signal generated by us
obj = self._registered_signals.setdefault(
self._REPOSITORIES_UPDATED_SIGNAL, [])
obj.append(sig_match)
# Clear all the NotificationBoxes from upper area
# we don't want people to click on them during the
# the repo update. Kill the completely.
if self._nc is not None:
self._nc.clear_safe(managed=False)
if self._terminal is not None:
self._terminal.reset()
self.repositories_lock.acquire()
# not allowing other threads to mess with repos
# will be released on repo updated signal
accepted = True
if master:
accepted = dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE
).update_repositories(repositories, force)
else:
# check if we need to cope with races
self._update_repositories_signal_check(
sig_match, signal_sem)
return accepted
def _update_repositories_signal_check(self, sig_match, signal_sem):
"""
Called via _update_repositories_unlocked() in order to handle
the possible race between RigoDaemon signal and the fact that
we just lost it.
This is only called in slave mode. When we didn't spawn the
repositories update directly.
"""
activity = self.activity()
if activity == DaemonActivityStates.UPDATING_REPOSITORIES:
return
# lost the signal or not, we're going to force
# the callback.
if not signal_sem.acquire(False):
# already called, no need to call again
const_debug_write(
__name__,
"_update_repositories_signal_check: abort")
return
const_debug_write(
__name__,
"_update_repositories_signal_check: accepting")
# Run in the main loop, to avoid calling a signal
# callback in random threads.
GLib.idle_add(self._repositories_updated_signal,
0, "", activity)
def _ask_blocking_question(self, ask_meta, message, message_type):
"""
Ask a task blocking question to User and waits for the
answer.
"""
box = self.ServiceNotificationBox(
prepare_markup(message), message_type)
def _say_yes(widget):
ask_meta['res'] = True
self._nc.remove(box)
ask_meta['sem'].release()
def _say_no(widget):
ask_meta['res'] = False
self._nc.remove(box)
ask_meta['sem'].release()
box.add_button(_("Yes, thanks"), _say_yes)
box.add_button(_("No, sorry"), _say_no)
self._nc.append(box)
def _notify_blocking_message(self, sem, message, message_type):
"""
Notify a task blocking information to User and wait for the
acknowledgement.
"""
box = self.ServiceNotificationBox(
prepare_markup(message), message_type)
def _say_kay(widget):
self._nc.remove(box)
if sem is not None:
sem.release()
box.add_button(_("Ok then"), _say_kay)
self._nc.append(box)
def _notify_blocking_licenses(self, ask_meta, app, license_map):
"""
Notify licenses that have to be accepted for Application and
block until User answers.
"""
box = LicensesNotificationBox(app, self._entropy, license_map)
def _license_accepted(widget, forever):
ask_meta['forever'] = forever
ask_meta['res'] = True
self._nc.remove(box)
ask_meta['sem'].release()
def _license_declined(widget):
ask_meta['res'] = False
self._nc.remove(box)
ask_meta['sem'].release()
box.connect("accepted", _license_accepted)
box.connect("declined", _license_declined)
self._nc.append(box)
def _application_request_removal_checks(self, app):
"""
Examine Application Removal Request on behalf of
_application_request_checks().
"""
removable = app.is_removable()
if not removable:
msg = _("<b>%s</b>\nis part of the Base"
" System and <b>cannot</b> be removed")
msg = msg % (app.get_markup(),)
message_type = Gtk.MessageType.ERROR
GLib.idle_add(
self._notify_blocking_message,
None, msg, message_type)
return False
return True
def _accept_licenses(self, license_list):
"""
Accept the given list of license ids.
"""
dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE).accept_licenses(
license_list)
def _application_request_install_checks(self, app):
"""
Examine Application Install Request on behalf of
_application_request_checks().
"""
installable = True
try:
installable = app.is_installable()
except Application.AcceptLicenseError as err:
# can be installed, but licenses have to be accepted
license_map = err.get()
const_debug_write(
__name__,
"_application_request_install_checks: "
"need to accept licenses: %s" % (license_map,))
ask_meta = {
'sem': Semaphore(0),
'forever': False,
'res': None,
}
GLib.idle_add(self._notify_blocking_licenses,
ask_meta, app, license_map)
ask_meta['sem'].acquire()
const_debug_write(
__name__,
"_application_request_install_checks: "
"unblock, accepted:: %s, forever: %s" % (
ask_meta['res'], ask_meta['forever'],))
if not ask_meta['res']:
return False
if ask_meta['forever']:
self._accept_licenses(license_map.keys())
return True
if not installable:
msg = prepare_markup(
_("<b>%s</b>\ncannot be installed at this time"
" due to <b>missing/masked</b> dependencies or"
" dependency <b>conflict</b>"))
msg = msg % (app.get_markup(),)
message_type = Gtk.MessageType.ERROR
GLib.idle_add(
self._notify_blocking_message,
None, msg, message_type)
return False
conflicting_apps = app.get_install_conflicts()
if conflicting_apps:
msg = prepare_markup(
_("Installing <b>%s</b> would cause the removal"
" of the following Applications: %s"))
msg = msg % (
app.name,
", ".join(
["<b>" + x.name + "</b>" for x in conflicting_apps]),)
message_type = Gtk.MessageType.WARNING
ask_meta = {
'res': None,
'sem': Semaphore(0),
}
GLib.idle_add(self._ask_blocking_question, ask_meta,
msg, message_type)
ask_meta['sem'].acquire() # CANBLOCK
if not ask_meta['res']:
return False
return True
def _application_request_checks(self, app, daemon_action):
"""
Examine Application Request before sending it to RigoDaemon.
Specifically, check for things like system apps removal asking
User confirmation.
"""
if daemon_action == DaemonAppActions.REMOVE:
accepted = self._application_request_removal_checks(app)
else:
accepted = self._application_request_install_checks(app)
if not accepted:
def _emit():
self.emit("application-abort", app, daemon_action)
GLib.idle_add(_emit)
return accepted
def _application_request_unlocked(self, app, daemon_action,
master, busied, simulate):
"""
Internal method handling the actual Application Request
execution.
"""
if app is not None:
package_id, repository_id = app.get_details().pkg
else:
package_id, repository_id = None, None
if busied:
if self._wc is not None:
GLib.idle_add(self._wc.activate_progress_bar)
# this will be back active once we have something
# to show
GLib.idle_add(self._wc.deactivate_app_box)
# Clear all the NotificationBoxes from upper area
# we don't want people to click on them during the
# the repo update. Kill the completely.
if self._nc is not None:
self._nc.clear_safe(managed=False)
# emit, but we don't really need to switch to
# the work view nor locking down the UI
GLib.idle_add(self.emit, "start-working", None, False)
const_debug_write(__name__, "RigoServiceController: "
"_application_request_unlocked: "
"start-working")
# don't check if UI is locked though
signal_sem = Semaphore(1)
def _applications_managed_signal(success):
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(
success, LocalActivityStates.MANAGING_APPLICATIONS)
with self._registered_signals_mutex:
# connect our signal
sig_match = self._entropy_bus.connect_to_signal(
self._APPLICATIONS_MANAGED_SIGNAL,
_applications_managed_signal,
dbus_interface=self.DBUS_INTERFACE)
# and register it as a signal generated by us
obj = self._registered_signals.setdefault(
self._APPLICATIONS_MANAGED_SIGNAL, [])
obj.append(sig_match)
const_debug_write(
__name__,
"_application_request_unlocked, about to 'schedule'")
accepted = True
if master:
accepted = dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE
).enqueue_application_action(
package_id, repository_id, daemon_action,
simulate)
const_debug_write(
__name__,
"service enqueue_application_action, got: %s, type: %s" % (
accepted, type(accepted),))
def _notify():
queue_len = self.action_queue_length()
msg = prepare_markup(_("<b>%s</b> action enqueued") % (
app.name,))
if queue_len > 0:
msg += prepare_markup(ngettext(
", <b>%i</b> Application enqueued so far...",
", <b>%i</b> Applications enqueued so far...",
queue_len)) % (queue_len,)
box = self.ServiceNotificationBox(
msg, Gtk.MessageType.INFO)
self._nc.append(box, timeout=10)
if accepted:
GLib.idle_add(_notify)
else:
self._applications_managed_signal_check(
sig_match, signal_sem,
DaemonActivityStates.MANAGING_APPLICATIONS,
LocalActivityStates.MANAGING_APPLICATIONS)
return accepted
def _applications_managed_signal_check(self, sig_match, signal_sem,
daemon_activity,
local_activity):
"""
Called via _application_request_unlocked() in order to handle
the possible race between RigoDaemon signal and the fact that
we just lost it.
This is only called in slave mode. When we didn't spawn the
repositories update directly.
"""
activity = self.activity()
if activity == daemon_activity:
return
# lost the signal or not, we're going to force
# the callback.
if not signal_sem.acquire(False):
# already called, no need to call again
const_debug_write(
__name__,
"_applications_managed_signal_check: abort")
return
const_debug_write(
__name__,
"_applications_managed_signal_check: accepting")
# Run in the main loop, to avoid calling a signal
# callback in random threads.
GLib.idle_add(self._applications_managed_signal,
True, local_activity)
def _application_request(self, app, app_action, simulate=False,
master=True):
"""
Forward Application Request (install or remove) to RigoDaemon.
Make sure there isn't any other ongoing activity.
"""
# Need to serialize access to this method because
# we're going to acquire several resources in a non-atomic
# way wrt access to this method.
with self._application_request_serializer:
with self._application_request_mutex:
busied = True
# since we need to writer_acquire(), which is blocking
# better try to allocate the local activity first
local_activity = LocalActivityStates.MANAGING_APPLICATIONS
try:
self.busy(local_activity)
except LocalActivityStates.BusyError:
const_debug_write(__name__, "_application_request: "
"LocalActivityStates.BusyError!")
# doing other stuff, cannot go ahead
return False
except LocalActivityStates.SameError:
const_debug_write(__name__, "_application_request: "
"LocalActivityStates.SameError, "
"no need to acquire writer")
# we're already doing this activity, do not acquire
# activity_rwsem
busied = False
if busied:
# 2 -- ACTIVITY CRIT :: ON
const_debug_write(__name__, "_application_request: "
"about to acquire writer end of "
"activity rwsem")
self._activity_rwsem.writer_acquire() # CANBLOCK
def _unbusy():
if busied:
self.unbusy(local_activity)
# 2 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
# clean terminal, make sure no crap is left there
if self._terminal is not None:
self._terminal.reset()
daemon_action = None
if app_action == AppActions.INSTALL:
daemon_action = DaemonAppActions.INSTALL
elif app_action == AppActions.REMOVE:
daemon_action = DaemonAppActions.REMOVE
accepted = True
do_notify = True
if master:
accepted = self._application_request_checks(
app, daemon_action)
if not accepted:
do_notify = False
const_debug_write(
__name__,
"_application_request, checks result: %s" % (
accepted,))
if accepted:
self._please_wait(True)
accepted = self._application_request_unlocked(
app, daemon_action, master,
busied, simulate)
if not accepted:
with self._application_request_mutex:
_unbusy()
def _notify():
box = self.ServiceNotificationBox(
prepare_markup(
_("Another activity is currently in progress")
),
Gtk.MessageType.ERROR)
box.add_destroy_button(_("K thanks"))
self._nc.append(box)
if do_notify:
GLib.idle_add(_notify)
# unhide please wait notification
self._please_wait(False)
return accepted
def _upgrade_system_license_check(self):
"""
Examine Applications that are going to be upgraded looking for
licenses to read and accept.
"""
self._entropy.rwsem().reader_acquire()
try:
update, remove, fine, spm_fine = \
self._entropy.calculate_updates()
if not update:
return True
licenses = self._entropy.get_licenses_to_accept(update)
if not licenses:
return True
finally:
self._entropy.rwsem().reader_release()
license_map = {}
for lic_id, pkg_matches in licenses.items():
obj = license_map.setdefault(lic_id, [])
for pkg_match in pkg_matches:
app = Application(
self._entropy, self._entropy_ws,
pkg_match)
obj.append(app)
const_debug_write(
__name__,
"_system_upgrade_license_checks: "
"need to accept licenses: %s" % (license_map,))
ask_meta = {
'sem': Semaphore(0),
'forever': False,
'res': None,
}
GLib.idle_add(self._notify_blocking_licenses,
ask_meta, None, license_map)
ask_meta['sem'].acquire()
const_debug_write(
__name__,
"_system_upgrade_license_checks: "
"unblock, accepted:: %s, forever: %s" % (
ask_meta['res'], ask_meta['forever'],))
if not ask_meta['res']:
return False
if ask_meta['forever']:
self._accept_licenses(license_map.keys())
return True
def _upgrade_system_checks(self):
"""
Examine System Upgrade Request before sending it to RigoDaemon.
"""
# add license check
accepted = self._upgrade_system_license_check()
if not accepted:
return False
return True
def _upgrade_system_unlocked(self, master, simulate):
"""
Internal method handling the actual System Upgrade
execution.
"""
if self._wc is not None:
GLib.idle_add(self._wc.activate_progress_bar)
# this will be back active once we have something
# to show
GLib.idle_add(self._wc.deactivate_app_box)
# Clear all the NotificationBoxes from upper area
# we don't want people to click on them during the
# the repo update. Kill the completely.
if self._nc is not None:
self._nc.clear_safe(managed=False)
# emit, but we don't really need to switch to
# the work view nor locking down the UI
GLib.idle_add(self.emit, "start-working", None, False)
const_debug_write(__name__, "RigoServiceController: "
"_upgrade_system_unlocked: "
"start-working")
# don't check if UI is locked though
signal_sem = Semaphore(1)
def _applications_managed_signal(success):
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(
success, LocalActivityStates.UPGRADING_SYSTEM)
with self._registered_signals_mutex:
# connect our signal
sig_match = self._entropy_bus.connect_to_signal(
self._APPLICATIONS_MANAGED_SIGNAL,
_applications_managed_signal,
dbus_interface=self.DBUS_INTERFACE)
# and register it as a signal generated by us
obj = self._registered_signals.setdefault(
self._APPLICATIONS_MANAGED_SIGNAL, [])
obj.append(sig_match)
const_debug_write(
__name__,
"_upgrade_system_unlocked, about to 'schedule'")
accepted = True
if master:
accepted = dbus.Interface(
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE
).upgrade_system(simulate)
const_debug_write(
__name__,
"service upgrade_system, accepted: %s" % (
accepted,))
def _notify():
msg = prepare_markup(
_("<b>System Upgrade</b> has begun, "
"now go make some coffee"))
box = self.ServiceNotificationBox(
msg, Gtk.MessageType.INFO)
self._nc.append(box, timeout=10)
if accepted:
GLib.idle_add(_notify)
else:
self._applications_managed_signal_check(
sig_match, signal_sem,
DaemonActivityStates.UPGRADING_SYSTEM,
LocalActivityStates.UPGRADING_SYSTEM)
return accepted
def _upgrade_system(self, simulate, master=True):
"""
Forward a System Upgrade Request to RigoDaemon.
"""
# This code has a lot of similarities wtih the application
# request one.
with self._application_request_mutex:
# since we need to writer_acquire(), which is blocking
# better try to allocate the local activity first
local_activity = LocalActivityStates.UPGRADING_SYSTEM
try:
self.busy(local_activity)
except LocalActivityStates.BusyError:
const_debug_write(__name__, "_upgrade_system: "
"LocalActivityStates.BusyError!")
# doing other stuff, cannot go ahead
return False
except LocalActivityStates.SameError:
const_debug_write(__name__, "_upgrade_system: "
"LocalActivityStates.SameError, "
"aborting")
return False
# 3 -- ACTIVITY CRIT :: ON
const_debug_write(__name__, "_upgrade_system: "
"about to acquire writer end of "
"activity rwsem")
self._activity_rwsem.writer_acquire() # CANBLOCK
def _unbusy():
self.unbusy(local_activity)
# 3 -- ACTIVITY CRIT :: OFF
self._activity_rwsem.writer_release()
# clean terminal, make sure no crap is left there
if self._terminal is not None:
self._terminal.reset()
do_notify = True
accepted = True
if master:
accepted = self._upgrade_system_checks()
if not accepted:
do_notify = False
const_debug_write(
__name__,
"_upgrade_system, checks result: %s" % (
accepted,))
if accepted:
self._please_wait(True)
accepted = self._upgrade_system_unlocked(
master, simulate)
if not accepted:
with self._application_request_mutex:
_unbusy()
def _notify():
box = self.ServiceNotificationBox(
prepare_markup(
_("Another activity is currently in progress")
),
Gtk.MessageType.ERROR)
box.add_destroy_button(_("K thanks"))
self._nc.append(box)
if do_notify:
GLib.idle_add(_notify)
# unhide please wait notification
self._please_wait(False)
return accepted