[rigo] first chunk of RigoDaemon integration code, still WIP
This commit is contained in:
@@ -112,7 +112,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="staticViewVbox">
|
||||
<object class="GtkVBox" id="workViewVbox">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
@@ -124,6 +124,19 @@
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="staticViewVbox">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="appViewScrollWin">
|
||||
<property name="can_focus">True</property>
|
||||
@@ -508,7 +521,7 @@
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# -*- 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 os
|
||||
|
||||
from gi.repository import Vte
|
||||
|
||||
from entropy.const import etpConst, const_isunicode
|
||||
|
||||
class TerminalWidget(Vte.Terminal):
|
||||
|
||||
"""
|
||||
Rigo Terminal Widget.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# set $TERM variable, avoid shell scripts to complain
|
||||
os.environ['TERM'] = "xterm"
|
||||
Vte.Terminal.__init__(self)
|
||||
|
||||
def _configure(self):
|
||||
self.set_emulation("xterm")
|
||||
self.set_background_saturation(0.0)
|
||||
self.set_opacity(65535)
|
||||
self.get_font().set_size(9000)
|
||||
self.set_scrollback_lines(10000)
|
||||
|
||||
def reset(self):
|
||||
Vte.Terminal.reset(self, True, True)
|
||||
self._configure()
|
||||
|
||||
def feed_child(self, txt):
|
||||
# Workaround vte.Terminal bug not passing to .feed proper message RAW
|
||||
# size. feed() supports UTF-8 but then, string length is wrongly passed
|
||||
# by python, because it does not consider the fact that UTF-8 chars can
|
||||
# be 16bits long.
|
||||
raw_txt_len = len(txt)
|
||||
if const_isunicode(txt):
|
||||
raw_txt_len = len(txt.encode(etpConst['conf_encoding']))
|
||||
|
||||
return Vte.Terminal.feed(self, txt, raw_txt_len)
|
||||
+714
-55
@@ -26,15 +26,18 @@ import tempfile
|
||||
import time
|
||||
from threading import Lock
|
||||
|
||||
import dbus
|
||||
|
||||
sys.path.insert(0, "../lib")
|
||||
sys.path.insert(1, "../client")
|
||||
sys.path.insert(2, "./")
|
||||
sys.path.insert(3, "/usr/lib/entropy/lib")
|
||||
sys.path.insert(4, "/usr/lib/entropy/client")
|
||||
sys.path.insert(5, "/usr/lib/entropy/rigo")
|
||||
sys.path.insert(6, "/usr/lib/rigo")
|
||||
|
||||
|
||||
from gi.repository import Gtk, Gdk, Gio, GLib, GObject
|
||||
from gi.repository import Gtk, Gdk, Gio, GLib, GObject, Vte, Pango
|
||||
|
||||
from rigo.paths import DATA_DIR
|
||||
from rigo.enums import Icons, AppActions
|
||||
@@ -47,22 +50,489 @@ from rigo.ui.gtk3.widgets.notifications import NotificationBox, \
|
||||
from rigo.ui.gtk3.widgets.welcome import WelcomeBox
|
||||
from rigo.ui.gtk3.widgets.stars import ReactiveStar
|
||||
from rigo.ui.gtk3.widgets.comments import CommentBox
|
||||
from rigo.ui.gtk3.widgets.terminal import TerminalWidget
|
||||
from rigo.ui.gtk3.widgets.images import ImageBox
|
||||
from rigo.ui.gtk3.models.appliststore import AppListStore
|
||||
from rigo.ui.gtk3.utils import init_sc_css_provider, get_sc_icon_theme
|
||||
from rigo.utils import build_application_store_url, build_register_url, \
|
||||
escape_markup, prepare_markup
|
||||
|
||||
from RigoDaemon.enums import ActivityStates
|
||||
|
||||
from entropy.const import etpConst, etpUi, const_debug_write, \
|
||||
const_debug_enabled, const_convert_to_unicode
|
||||
const_debug_enabled, const_convert_to_unicode, const_isunicode
|
||||
from entropy.client.interfaces import Client
|
||||
from entropy.client.interfaces.repository import Repository
|
||||
from entropy.services.client import WebService
|
||||
from entropy.misc import TimeScheduled, ParallelTask
|
||||
from entropy.misc import TimeScheduled, ParallelTask, ReadersWritersSemaphore
|
||||
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 Rigo Daemon.
|
||||
Handles privileged requests on our behalf.
|
||||
"""
|
||||
|
||||
__gsignals__ = {
|
||||
# we request to lock the whole UI wrt repo
|
||||
# interaction
|
||||
"repositories-updating" : (GObject.SignalFlags.RUN_LAST,
|
||||
None,
|
||||
(GObject.TYPE_PYOBJECT,),
|
||||
),
|
||||
# View has been filled
|
||||
"repositories-updated" : (GObject.SignalFlags.RUN_LAST,
|
||||
None,
|
||||
tuple(),
|
||||
),
|
||||
}
|
||||
|
||||
DBUS_INTERFACE = "org.sabayon.Rigo"
|
||||
DBUS_PATH = "/daemon"
|
||||
_OUTPUT_SIGNAL = "output"
|
||||
_REPOSITORIES_UPDATED_SIGNAL = "repositories_updated"
|
||||
_TRANSFER_OUTPUT_SIGNAL = "transfer_output"
|
||||
|
||||
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._activity_rwsem_acquired_mutex = Lock()
|
||||
self._activity_rwsem_acquired_by_us = False
|
||||
self._nc = None
|
||||
self._wc = None
|
||||
self._avc = 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._connected_mutex = Lock()
|
||||
self._connected = False
|
||||
|
||||
def set_applications_controller(self, avc):
|
||||
"""
|
||||
Bind ApplicationsViewController object to this class.
|
||||
"""
|
||||
self._avc = avc
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
)
|
||||
self.__entropy_bus.connect_to_signal(
|
||||
self._OUTPUT_SIGNAL, self._output_signal,
|
||||
dbus_interface=self.DBUS_INTERFACE)
|
||||
self.__entropy_bus.connect_to_signal(
|
||||
self._REPOSITORIES_UPDATED_SIGNAL,
|
||||
self._repositories_updated_signal,
|
||||
dbus_interface=self.DBUS_INTERFACE)
|
||||
self.__entropy_bus.connect_to_signal(
|
||||
self._TRANSFER_OUTPUT_SIGNAL,
|
||||
self._transfer_output_signal,
|
||||
dbus_interface=self.DBUS_INTERFACE)
|
||||
return self.__entropy_bus
|
||||
|
||||
def _repositories_updated_signal(self, request_id, result, message):
|
||||
"""
|
||||
Signal coming from RigoDaemon notifying us that repositories have
|
||||
been updated.
|
||||
"""
|
||||
self._release_local_resources()
|
||||
# 1 -- ACTIVITY CRIT :: OFF
|
||||
with self._activity_rwsem_acquired_mutex:
|
||||
# this signal comes from RigoDaemon but might not be
|
||||
# caused by us
|
||||
if self._activity_rwsem_acquired_by_us:
|
||||
self._activity_rwsem_acquired_by_us = False
|
||||
self._activity_rwsem.writer_release()
|
||||
|
||||
self.emit("repositories-updated")
|
||||
if const_debug_enabled():
|
||||
const_debug_write(__name__, "RigoServiceController: unlock-ui")
|
||||
|
||||
def _output_signal(self, text, header, footer, back, importance, level,
|
||||
count_c, count_t, percent):
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
text = "%s/%s kB @ %s/sec, %s" % (
|
||||
round(float(downloaded_size)/1024, 1),
|
||||
round(total_size, 1),
|
||||
human_dt, time_remaining_secs)
|
||||
|
||||
self._wc.set_progress(fraction, text=text)
|
||||
|
||||
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 output_test(self):
|
||||
"""
|
||||
Test Output Signaling. Will be removed in future Rigo versions.
|
||||
"""
|
||||
if self._wc is not None:
|
||||
self._wc.activate_progress_bar()
|
||||
self._wc.activate_app_box()
|
||||
dbus.Interface(
|
||||
self._entropy_bus,
|
||||
dbus_interface=self.DBUS_INTERFACE).output_test()
|
||||
|
||||
def output_test_safe(self):
|
||||
"""
|
||||
Same as output_test() but thread-safe.
|
||||
"""
|
||||
GLib.idle_add(self.output_test)
|
||||
|
||||
def _connect(self):
|
||||
"""
|
||||
Inform RigoDaemon that a new Client is now connected.
|
||||
This MUST be called after Entropy Resources Lock is
|
||||
acquired in exclusive mode.
|
||||
"""
|
||||
with self._connected_mutex:
|
||||
if self._connected:
|
||||
return
|
||||
# inform daemon that a new instance is now connected
|
||||
dbus.Interface(
|
||||
self._entropy_bus,
|
||||
dbus_interface=self.DBUS_INTERFACE).connect()
|
||||
self._connected = True
|
||||
|
||||
def _release_local_resources(self):
|
||||
"""
|
||||
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._avc.clear_safe()
|
||||
self._entropy.close_repositories()
|
||||
|
||||
def _scale_up(self):
|
||||
"""
|
||||
Acquire (in blocking mode) the Entropy Resources Lock
|
||||
in exclusive mode. Scale up privileges, and ask for
|
||||
root password if not done yet.
|
||||
"""
|
||||
# FIXME, complete, need to be nice and not block, etc
|
||||
# FIXME, ask for password.
|
||||
self._entropy.promote_resources(blocking=True)
|
||||
self._connect()
|
||||
return True
|
||||
|
||||
def _update_repositories(self, repositories, force):
|
||||
"""
|
||||
Ask RigoDaemon to update repositories once we're
|
||||
100% sure that the UI is locked down.
|
||||
"""
|
||||
while not self._rigo.is_ui_locked():
|
||||
if const_debug_enabled():
|
||||
const_debug_write(__name__, "RigoServiceController: "
|
||||
"waiting Rigo UI lock")
|
||||
time.sleep(0.5)
|
||||
|
||||
if const_debug_enabled():
|
||||
const_debug_write(__name__, "RigoServiceController: "
|
||||
"rigo UI now locked!")
|
||||
|
||||
# 1 -- ACTIVITY CRIT :: ON
|
||||
self._activity_rwsem.writer_acquire()
|
||||
with self._activity_rwsem_acquired_mutex:
|
||||
self._activity_rwsem_acquired_by_us = True
|
||||
|
||||
# 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()
|
||||
|
||||
self._terminal.reset()
|
||||
self._release_local_resources()
|
||||
iface = dbus.Interface(
|
||||
self._entropy_bus,
|
||||
dbus_interface=self.DBUS_INTERFACE)
|
||||
iface.update_repositories(repositories, 1, force)
|
||||
|
||||
def update_repositories(self, repositories, force):
|
||||
"""
|
||||
Local method used to start Entropy repositories
|
||||
update.
|
||||
"""
|
||||
if not self._scale_up():
|
||||
return
|
||||
|
||||
if self._wc is not None:
|
||||
self._wc.activate_progress_bar()
|
||||
self._wc.deactivate_app_box()
|
||||
|
||||
self.emit("repositories-updating", Rigo.WORK_VIEW_STATE)
|
||||
if const_debug_enabled():
|
||||
const_debug_write(__name__, "RigoServiceController: "
|
||||
"repositories-updating")
|
||||
|
||||
task = ParallelTask(self._update_repositories,
|
||||
repositories, force)
|
||||
task.name = "UpdateRepositoriesThread"
|
||||
task.daemon = True
|
||||
task.start()
|
||||
|
||||
|
||||
class WorkViewController(GObject.Object):
|
||||
|
||||
def __init__(self, rigo_service, work_box):
|
||||
self._service = rigo_service
|
||||
self._box = work_box
|
||||
|
||||
def _setup_terminal_menu(self):
|
||||
"""
|
||||
Setup TerminalWidget Right Click popup-menu.
|
||||
"""
|
||||
self._terminal_menu = Gtk.Menu()
|
||||
|
||||
sall_menu_item = Gtk.ImageMenuItem.new_from_stock(
|
||||
"gtk-select-all", None)
|
||||
sall_menu_item.connect("activate", self._on_terminal_select_all)
|
||||
self._terminal_menu.append(sall_menu_item)
|
||||
|
||||
copy_menu_item = Gtk.ImageMenuItem.new_from_stock(
|
||||
"gtk-copy", None)
|
||||
copy_menu_item.connect("activate", self._on_terminal_copy)
|
||||
self._terminal_menu.append(copy_menu_item)
|
||||
|
||||
reset_menu_item = Gtk.ImageMenuItem.new_from_stock(
|
||||
"gtk-clear", None)
|
||||
reset_menu_item.connect("activate", self._on_terminal_reset)
|
||||
self._terminal_menu.append(reset_menu_item)
|
||||
|
||||
self._terminal_menu.show_all()
|
||||
|
||||
def _setup_terminal_area(self):
|
||||
"""
|
||||
Setup TerminalWidget area (including ScrollBar).
|
||||
"""
|
||||
hbox = Gtk.HBox()
|
||||
|
||||
self._terminal = TerminalWidget()
|
||||
self._terminal.connect(
|
||||
"button-press-event",
|
||||
self._on_terminal_click)
|
||||
self._terminal.reset()
|
||||
hbox.pack_start(self._terminal, True, True, 0)
|
||||
|
||||
scrollbar = Gtk.VScrollbar.new(self._terminal.adjustment)
|
||||
hbox.pack_start(scrollbar, False, False, 0)
|
||||
hbox.show_all()
|
||||
|
||||
return hbox
|
||||
|
||||
def _setup_progress_area(self):
|
||||
"""
|
||||
Setup Progress Bar area.
|
||||
"""
|
||||
self._progress_box = Gtk.VBox()
|
||||
|
||||
progress_align = Gtk.Alignment()
|
||||
progress_align.set_padding(10, 10, 0, 0)
|
||||
self._progress_bar = Gtk.ProgressBar()
|
||||
progress_align.add(self._progress_bar)
|
||||
self._progress_box.pack_start(progress_align, False, False, 0)
|
||||
self._progress_box.show_all()
|
||||
|
||||
return self._progress_box
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
Initialize WorkViewController controlled resources.
|
||||
"""
|
||||
self._setup_terminal_menu()
|
||||
|
||||
box = self._setup_progress_area()
|
||||
self._box.pack_start(box, False, False, 0)
|
||||
|
||||
box = self._setup_terminal_area()
|
||||
self._box.pack_start(box, True, True, 0)
|
||||
|
||||
self._service.set_terminal(self._terminal)
|
||||
self._service.set_work_controller(self)
|
||||
|
||||
self.deactivate_progress_bar()
|
||||
self.deactivate_app_box()
|
||||
|
||||
def activate_app_box(self):
|
||||
"""
|
||||
Activate the Application Box showing information
|
||||
about the Application being currently handled.
|
||||
"""
|
||||
# FIXME, complete
|
||||
|
||||
def deactivate_app_box(self):
|
||||
"""
|
||||
Deactivate the Application Box showing information
|
||||
about the Application being currently handled.
|
||||
"""
|
||||
# FIXME, complete
|
||||
|
||||
def activate_progress_bar(self):
|
||||
"""
|
||||
Activate the Progress Bar showing progress information.
|
||||
"""
|
||||
self._progress_box.show_all()
|
||||
|
||||
def deactivate_progress_bar(self):
|
||||
"""
|
||||
Deactivate the Progress Bar showing progress information.
|
||||
"""
|
||||
self._progress_box.hide()
|
||||
|
||||
def set_progress(self, fraction, text=None):
|
||||
"""
|
||||
Set Progress Bar progress, progress must be a value between
|
||||
0.0 and 1.0. You can also provide a new text for progress at
|
||||
the same time, the same will be escaped and cleaned out
|
||||
by the callee.
|
||||
"""
|
||||
self._progress_bar.set_fraction(fraction)
|
||||
if text is not None:
|
||||
self._progress_bar.set_text(escape_markup(text))
|
||||
|
||||
def set_progress_text(self, text):
|
||||
"""
|
||||
Set Progress Bar text. The same will be escaped and cleaned out by
|
||||
the callee.
|
||||
"""
|
||||
self._progress_bar.set_text(escape_markup(text))
|
||||
|
||||
def _on_terminal_click(self, widget, event):
|
||||
"""
|
||||
Right Click on the TerminalWidget area.
|
||||
"""
|
||||
if event.button == 3:
|
||||
self._terminal_menu.popup(
|
||||
None, None, None, None, event.button, event.time)
|
||||
|
||||
def _on_terminal_copy(self, widget):
|
||||
"""
|
||||
Copy to clipboard Terminal GtkMenuItem clicked.
|
||||
"""
|
||||
self._terminal.copy_clipboard()
|
||||
|
||||
def _on_terminal_reset(self, widget):
|
||||
"""
|
||||
Reset Terminal GtkMenuItem clicked.
|
||||
"""
|
||||
self._terminal.reset()
|
||||
|
||||
def _on_terminal_select_all(self, widget):
|
||||
"""
|
||||
Select All Terminal GtkMenuItem clicked.
|
||||
"""
|
||||
self._terminal.select_all()
|
||||
|
||||
|
||||
class ApplicationsViewController(GObject.Object):
|
||||
|
||||
@@ -77,6 +547,11 @@ class ApplicationsViewController(GObject.Object):
|
||||
None,
|
||||
tuple(),
|
||||
),
|
||||
# View has been filled
|
||||
"view-want-change" : (GObject.SignalFlags.RUN_LAST,
|
||||
None,
|
||||
(GObject.TYPE_PYOBJECT,),
|
||||
),
|
||||
# User logged in to Entropy Web Services
|
||||
"logged-in" : (GObject.SignalFlags.RUN_LAST,
|
||||
None,
|
||||
@@ -89,10 +564,13 @@ class ApplicationsViewController(GObject.Object):
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, entropy_client, entropy_ws, icons, nf_box,
|
||||
def __init__(self, activity_rwsem, entropy_client, entropy_ws,
|
||||
rigo_service, icons, nf_box,
|
||||
search_entry, store, view):
|
||||
GObject.Object.__init__(self)
|
||||
self._activity_rwsem = activity_rwsem
|
||||
self._entropy = entropy_client
|
||||
self._service = rigo_service
|
||||
self._icons = icons
|
||||
self._entropy_ws = entropy_ws
|
||||
self._search_entry = search_entry
|
||||
@@ -134,32 +612,52 @@ class ApplicationsViewController(GObject.Object):
|
||||
def _prepare_for_search(txt):
|
||||
return txt.replace(" ", "-").lower()
|
||||
|
||||
matches = []
|
||||
## special keywords hook
|
||||
if text == "rigo:update":
|
||||
self._update_repositories_safe()
|
||||
return
|
||||
if text == "rigo:vte":
|
||||
GLib.idle_add(self.emit, "view-want-change", Rigo.WORK_VIEW_STATE)
|
||||
return
|
||||
if text == "rigo:output":
|
||||
GLib.idle_add(self.emit, "view-want-change", Rigo.WORK_VIEW_STATE)
|
||||
GLib.idle_add(self._service.output_test)
|
||||
return
|
||||
|
||||
# exact match
|
||||
pkg_matches, rc = self._entropy.atom_match(
|
||||
text, multi_match = True,
|
||||
multi_repo = True, mask_filter = False)
|
||||
matches.extend(pkg_matches)
|
||||
# Do not execute search if repositories are
|
||||
# being hold by other write
|
||||
self._activity_rwsem.reader_acquire()
|
||||
try:
|
||||
|
||||
# atom searching (name and desc)
|
||||
search_matches = self._entropy.atom_search(
|
||||
text,
|
||||
repositories = self._entropy.repositories(),
|
||||
description = True)
|
||||
matches = []
|
||||
|
||||
matches.extend([x for x in search_matches if x not in matches])
|
||||
# exact match
|
||||
pkg_matches, rc = self._entropy.atom_match(
|
||||
text, multi_match = True,
|
||||
multi_repo = True, mask_filter = False)
|
||||
matches.extend(pkg_matches)
|
||||
|
||||
if not search_matches:
|
||||
# atom searching (name and desc)
|
||||
search_matches = self._entropy.atom_search(
|
||||
_prepare_for_search(text),
|
||||
repositories = self._entropy.repositories())
|
||||
text,
|
||||
repositories = self._entropy.repositories(),
|
||||
description = True)
|
||||
|
||||
matches.extend([x for x in search_matches if x not in matches])
|
||||
|
||||
# we have to decide if to show the treeview in
|
||||
# the UI thread, to avoid races (and also because we
|
||||
# have to...)
|
||||
self.set_many_safe(matches, _from_search=text)
|
||||
if not search_matches:
|
||||
search_matches = self._entropy.atom_search(
|
||||
_prepare_for_search(text),
|
||||
repositories = self._entropy.repositories())
|
||||
matches.extend([x for x in search_matches if x not in matches])
|
||||
|
||||
# we have to decide if to show the treeview in
|
||||
# the UI thread, to avoid races (and also because we
|
||||
# have to...)
|
||||
self.set_many_safe(matches, _from_search=text)
|
||||
|
||||
finally:
|
||||
self._activity_rwsem.reader_release()
|
||||
|
||||
def _setup_search_view(self, items_count, text):
|
||||
"""
|
||||
@@ -238,6 +736,19 @@ class ApplicationsViewController(GObject.Object):
|
||||
self._not_found_search_box = box_align
|
||||
return box_align
|
||||
|
||||
def _update_repositories(self):
|
||||
"""
|
||||
Spawn Repository Update on RigoDaemon
|
||||
"""
|
||||
self._service.update_repositories(
|
||||
self._entropy.repositories(), True)
|
||||
|
||||
def _update_repositories_safe(self):
|
||||
"""
|
||||
Same as _update_repositories() but thread safe.
|
||||
"""
|
||||
GLib.idle_add(self._update_repositories)
|
||||
|
||||
def setup(self):
|
||||
self._view.set_model(self._store)
|
||||
self._search_entry.connect(
|
||||
@@ -297,10 +808,13 @@ class NotificationViewController(GObject.Object):
|
||||
but also accepts external NotificationBox instances as well.
|
||||
"""
|
||||
|
||||
def __init__(self, entropy_client, entropy_ws, avc, notification_box):
|
||||
def __init__(self, activity_rwsem, entropy_client, entropy_ws, rigo_service,
|
||||
avc, notification_box):
|
||||
GObject.Object.__init__(self)
|
||||
self._activity_rwsem = activity_rwsem
|
||||
self._entropy = entropy_client
|
||||
self._entropy_ws = entropy_ws
|
||||
self._service = rigo_service
|
||||
self._avc = avc
|
||||
self._box = notification_box
|
||||
self._updates = None
|
||||
@@ -334,6 +848,9 @@ class NotificationViewController(GObject.Object):
|
||||
if self._entropy_ws.get(repository_id) is not None:
|
||||
available = True
|
||||
break
|
||||
if not repositories:
|
||||
# no repos to check against
|
||||
available = True
|
||||
|
||||
if not available:
|
||||
GLib.idle_add(self._notify_connectivity_issues)
|
||||
@@ -348,14 +865,19 @@ class NotificationViewController(GObject.Object):
|
||||
return sorted(updates, key=_key_func)
|
||||
|
||||
def __calculate_updates(self):
|
||||
if Repository.are_repositories_old():
|
||||
GLib.idle_add(self._notify_old_repositories_safe)
|
||||
return
|
||||
self._activity_rwsem.reader_acquire()
|
||||
try:
|
||||
if Repository.are_repositories_old():
|
||||
GLib.idle_add(self._notify_old_repositories_safe)
|
||||
return
|
||||
|
||||
updates, removal, fine, spm_fine = \
|
||||
self._entropy.calculate_updates()
|
||||
self._updates = self.__order_updates(updates)
|
||||
self._security_updates = self._entropy.calculate_security_updates()
|
||||
finally:
|
||||
self._activity_rwsem.reader_release()
|
||||
|
||||
updates, removal, fine, spm_fine = \
|
||||
self._entropy.calculate_updates()
|
||||
self._updates = self.__order_updates(updates)
|
||||
self._security_updates = self._entropy.calculate_security_updates()
|
||||
GLib.idle_add(self._notify_updates_safe)
|
||||
|
||||
def _notify_connectivity_issues(self):
|
||||
@@ -398,6 +920,8 @@ class NotificationViewController(GObject.Object):
|
||||
"""
|
||||
# FIXME, lxnay complete
|
||||
print("On Upgrade Request Received", args)
|
||||
# FIXME, this is for testing, REMOVE !!!!
|
||||
self._service.update_repositories(self._entropy.repositories(), True)
|
||||
|
||||
def _on_update(self, *args):
|
||||
"""
|
||||
@@ -405,6 +929,7 @@ class NotificationViewController(GObject.Object):
|
||||
"""
|
||||
# FIXME, lxnay complete
|
||||
print("On Update Request Received", args)
|
||||
self._service.update_repositories(self._entropy.repositories(), True)
|
||||
|
||||
def _on_update_show(self, *args):
|
||||
"""
|
||||
@@ -1355,30 +1880,66 @@ class ApplicationViewController(GObject.Object):
|
||||
|
||||
class Rigo(Gtk.Application):
|
||||
|
||||
class RigoHandler:
|
||||
class RigoHandler(object):
|
||||
|
||||
def __init__(self, rigo_app):
|
||||
self._app = rigo_app
|
||||
|
||||
def onDeleteWindow(self, window, event):
|
||||
# if UI is locked, do not allow to close Rigo
|
||||
if self._app.is_ui_locked():
|
||||
rc = self._app._show_yesno_dialog(
|
||||
None,
|
||||
escape_markup(_("Hey hey hey!")),
|
||||
escape_markup(_("Rigo is working, are you sure?")))
|
||||
print rc
|
||||
return True
|
||||
|
||||
def onDeleteWindow(self, *args):
|
||||
while True:
|
||||
try:
|
||||
entropy.tools.kill_threads()
|
||||
Gtk.main_quit(*args)
|
||||
Gtk.main_quit((window, event))
|
||||
except KeyboardInterrupt:
|
||||
continue
|
||||
break
|
||||
|
||||
# Possible Rigo Application UI States
|
||||
BROWSER_VIEW_STATE, STATIC_VIEW_STATE, \
|
||||
APPLICATION_VIEW_STATE = range(3)
|
||||
APPLICATION_VIEW_STATE, \
|
||||
WORK_VIEW_STATE = range(4)
|
||||
|
||||
def __init__(self):
|
||||
icons = get_sc_icon_theme(DATA_DIR)
|
||||
self._current_state_lock = False
|
||||
self._current_state = Rigo.STATIC_VIEW_STATE
|
||||
self._state_transactions = {
|
||||
Rigo.BROWSER_VIEW_STATE: (
|
||||
self._enter_browser_state,
|
||||
self._exit_browser_state),
|
||||
Rigo.STATIC_VIEW_STATE: (
|
||||
self._enter_static_state,
|
||||
self._exit_static_state),
|
||||
Rigo.APPLICATION_VIEW_STATE: (
|
||||
self._enter_application_state,
|
||||
self._exit_application_state),
|
||||
Rigo.WORK_VIEW_STATE: (
|
||||
self._enter_work_state,
|
||||
self._exit_work_state),
|
||||
}
|
||||
self._state_mutex = Lock()
|
||||
|
||||
icons = get_sc_icon_theme(DATA_DIR)
|
||||
app_handler = Rigo.RigoHandler(self)
|
||||
|
||||
self._activity_rwsem = ReadersWritersSemaphore()
|
||||
self._entropy = Client()
|
||||
self._entropy_ws = EntropyWebService(self._entropy)
|
||||
self._service = RigoServiceController(
|
||||
self, self._activity_rwsem,
|
||||
self._entropy, self._entropy_ws)
|
||||
|
||||
self._builder = Gtk.Builder()
|
||||
self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui"))
|
||||
self._builder.connect_signals(Rigo.RigoHandler())
|
||||
self._builder.connect_signals(app_handler)
|
||||
self._window = self._builder.get_object("rigoWindow")
|
||||
self._window.set_name("rigo-view")
|
||||
self._apps_view = self._builder.get_object("appsViewVbox")
|
||||
@@ -1391,6 +1952,8 @@ class Rigo(Gtk.Application):
|
||||
self._search_entry = self._builder.get_object("searchEntry")
|
||||
self._static_view = self._builder.get_object("staticViewVbox")
|
||||
self._notification = self._builder.get_object("notificationBox")
|
||||
self._work_view = self._builder.get_object("workViewVbox")
|
||||
self._work_view.set_name("rigo-view")
|
||||
|
||||
self._app_view_c = ApplicationViewController(
|
||||
self._entropy, self._entropy_ws, self._builder)
|
||||
@@ -1422,38 +1985,65 @@ class Rigo(Gtk.Application):
|
||||
Gdk.Screen.get_default(),
|
||||
DATA_DIR)
|
||||
|
||||
self._current_state = Rigo.STATIC_VIEW_STATE
|
||||
self._state_transactions = {
|
||||
Rigo.BROWSER_VIEW_STATE: (
|
||||
self._enter_browser_state,
|
||||
self._exit_browser_state),
|
||||
Rigo.STATIC_VIEW_STATE: (
|
||||
self._enter_static_state,
|
||||
self._exit_static_state),
|
||||
Rigo.APPLICATION_VIEW_STATE: (
|
||||
self._enter_application_state,
|
||||
self._exit_application_state),
|
||||
}
|
||||
self._state_mutex = Lock()
|
||||
self._avc = ApplicationsViewController(
|
||||
self._entropy, self._entropy_ws, icons, self._not_found_box,
|
||||
self._activity_rwsem,
|
||||
self._entropy, self._entropy_ws, self._service,
|
||||
icons, self._not_found_box,
|
||||
self._search_entry, self._app_store, self._view)
|
||||
|
||||
self._avc.connect("view-cleared", self._on_view_cleared)
|
||||
self._avc.connect("view-filled", self._on_view_filled)
|
||||
self._avc.connect("view-want-change", self._on_view_change)
|
||||
|
||||
self._nc = NotificationViewController(
|
||||
self._entropy, self._entropy_ws,
|
||||
self._activity_rwsem, self._entropy,
|
||||
self._entropy_ws, self._service,
|
||||
self._avc, self._notification)
|
||||
|
||||
self._app_view_c.set_notification_controller(self._nc)
|
||||
self._app_view_c.set_applications_controller(self._avc)
|
||||
|
||||
self._service.set_applications_controller(self._avc)
|
||||
self._service.set_notification_controller(self._nc)
|
||||
|
||||
self._service.connect("repositories-updating", self._on_repo_updating)
|
||||
self._service.connect("repositories-updated", self._on_repo_updated)
|
||||
|
||||
self._work_view_c = WorkViewController(
|
||||
self._service, self._work_view)
|
||||
|
||||
def is_ui_locked(self):
|
||||
"""
|
||||
Return whether the UI is currently locked.
|
||||
"""
|
||||
return self._current_state_lock
|
||||
|
||||
def _on_repo_updating(self, widget, state):
|
||||
"""
|
||||
Emitted by RigoServiceController when we're asked to
|
||||
lock down the UI to a given state.
|
||||
"""
|
||||
self._search_entry.set_sensitive(False)
|
||||
self._change_view_state(state, lock=True)
|
||||
|
||||
def _on_repo_updated(self, widget):
|
||||
"""
|
||||
Emitted by RigoServiceController when we're allowed to
|
||||
let the user mess again with the UI.
|
||||
"""
|
||||
with self._state_mutex:
|
||||
self._current_state_lock = False
|
||||
self._search_entry.set_sensitive(True)
|
||||
|
||||
def _on_view_cleared(self, *args):
|
||||
self._change_view_state(Rigo.STATIC_VIEW_STATE)
|
||||
|
||||
def _on_view_filled(self, *args):
|
||||
self._change_view_state(Rigo.BROWSER_VIEW_STATE)
|
||||
|
||||
def _on_view_change(self, widget, state):
|
||||
self._change_view_state(state)
|
||||
|
||||
def _on_application_show(self, *args):
|
||||
self._change_view_state(Rigo.APPLICATION_VIEW_STATE)
|
||||
|
||||
@@ -1516,13 +2106,31 @@ class Rigo(Gtk.Application):
|
||||
self._app_view.hide()
|
||||
self._app_view_c.hide()
|
||||
|
||||
def _change_view_state(self, state):
|
||||
def _enter_work_state(self):
|
||||
"""
|
||||
Action triggered when UI enters the Work View state (or mode).
|
||||
Either for Updating Repositories or Installing new Apps.
|
||||
"""
|
||||
self._work_view.show()
|
||||
|
||||
def _exit_work_state(self):
|
||||
"""
|
||||
Action triggered when UI exits the Work View state (or mode).
|
||||
"""
|
||||
self._work_view.hide()
|
||||
|
||||
def _change_view_state(self, state, lock=False):
|
||||
"""
|
||||
Change Rigo Application UI state.
|
||||
You can pass a custom widget that will be shown in case
|
||||
of static view state.
|
||||
"""
|
||||
with self._state_mutex:
|
||||
if self._current_state_lock:
|
||||
const_debug_write(
|
||||
__name__,
|
||||
"cannot change view state, UI locked")
|
||||
return False
|
||||
txc = self._state_transactions.get(state)
|
||||
if txc is None:
|
||||
raise AttributeError("wrong view state")
|
||||
@@ -1535,6 +2143,10 @@ class Rigo(Gtk.Application):
|
||||
# enter the new state
|
||||
enter_st()
|
||||
self._current_state = state
|
||||
if lock:
|
||||
self._current_state_lock = True
|
||||
|
||||
return True
|
||||
|
||||
def _change_view_state_safe(self, state):
|
||||
"""
|
||||
@@ -1562,6 +2174,19 @@ class Rigo(Gtk.Application):
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
|
||||
def _show_yesno_dialog(self, parent, title, message):
|
||||
"""
|
||||
Show ugly OK dialog window.
|
||||
"""
|
||||
dlg = Gtk.MessageDialog(parent=parent,
|
||||
type=Gtk.MessageType.INFO,
|
||||
buttons=GTK_BUTTONS_YES_NO)
|
||||
dlg.set_markup(message)
|
||||
dlg.set_title(title)
|
||||
rc = dlg.run()
|
||||
dlg.destroy()
|
||||
return rc
|
||||
|
||||
def _permissions_setup(self):
|
||||
"""
|
||||
Check execution privileges and spawn the Rigo UI.
|
||||
@@ -1577,8 +2202,9 @@ class Rigo(Gtk.Application):
|
||||
Gtk.main_quit()
|
||||
return
|
||||
|
||||
acquired = entropy.tools.acquire_entropy_locks(
|
||||
self._entropy, shared=True, max_tries=1)
|
||||
acquired = not self._entropy.wait_resources(
|
||||
max_lock_count=1,
|
||||
shared=True)
|
||||
if not acquired:
|
||||
self._show_ok_dialog(
|
||||
None,
|
||||
@@ -1588,9 +2214,40 @@ class Rigo(Gtk.Application):
|
||||
Gtk.main_quit()
|
||||
return
|
||||
|
||||
# check RigoDaemon, don't worry about races between Rigo Clients
|
||||
# it is fine to have multiple Rigo Clients connected. Mutual
|
||||
# exclusion is handled via Entropy Resources Lock (which is a file
|
||||
# based rwsem).
|
||||
activity = self._service.activity()
|
||||
if activity != ActivityStates.AVAILABLE:
|
||||
if activity == ActivityStates.NOT_AVAILABLE:
|
||||
msg = _("Background Service is currently not available")
|
||||
elif activity == ActivityStates.UPDATING_REPOSITORIES:
|
||||
# FIXME, jump to WORK_VIEW and show the progress.
|
||||
msg = _("Background Service is updating repositories")
|
||||
elif activity == ActivityStates.INSTALLING_APPLICATION:
|
||||
# FIXME, jump to WORK_VIEW and show the progress.
|
||||
msg = _("Background Service is installing Applications")
|
||||
elif activity == ActivityStates.UPGRADING_SYSTEM:
|
||||
# FIXME, jump to WORK_VIEW and show the progress.
|
||||
msg = _("Background Service is updating your system")
|
||||
elif activity == ActivityStates.INTERNAL_ROUTINES:
|
||||
msg = _("Background Service is currently busy")
|
||||
else:
|
||||
msg = _("Background Service is incompatible with Rigo")
|
||||
|
||||
self._show_ok_dialog(
|
||||
None,
|
||||
escape_markup(_("Rigo")),
|
||||
escape_markup(msg))
|
||||
entropy.tools.kill_threads()
|
||||
Gtk.main_quit()
|
||||
return
|
||||
|
||||
self._app_view_c.setup()
|
||||
self._avc.setup()
|
||||
self._nc.setup()
|
||||
self._work_view_c.setup()
|
||||
self._window.show()
|
||||
|
||||
def run(self):
|
||||
@@ -1608,5 +2265,7 @@ class Rigo(Gtk.Application):
|
||||
entropy.tools.kill_threads()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import signal
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
app = Rigo()
|
||||
app.run()
|
||||
|
||||
Reference in New Issue
Block a user