[RigoDaemon/Rigo] Add support for .etp and .tbz2 package files installation

This commit is contained in:
Fabio Erculiani
2012-04-09 15:38:31 +02:00
parent a3f7ff6d79
commit e68268abe2
6 changed files with 388 additions and 67 deletions
+125 -16
View File
@@ -59,7 +59,8 @@ EntropyCacher.WRITEBACK_TIMEOUT = 120
from entropy.const import etpConst, const_convert_to_rawstring, \
initconfig_entropy_constants, const_debug_write
from entropy.exceptions import DependenciesNotFound, \
DependenciesCollision, DependenciesNotRemovable, SystemDatabaseError
DependenciesCollision, DependenciesNotRemovable, SystemDatabaseError, \
EntropyPackageException
from entropy.i18n import _
from entropy.misc import LogFile, ParallelTask, TimeScheduled, \
ReadersWritersSemaphore, DirectoryMonitor
@@ -367,7 +368,7 @@ class RigoDaemonService(dbus.service.Object):
BUS_NAME = DbusConfig.BUS_NAME
OBJECT_PATH = DbusConfig.OBJECT_PATH
API_VERSION = 0
API_VERSION = 1
"""
RigoDaemon is the dbus service Object in charge of executing
@@ -380,10 +381,11 @@ class RigoDaemonService(dbus.service.Object):
class ActionQueueItem(object):
def __init__(self, package_id, repository_id, action,
simulate, authorized):
def __init__(self, package_id, repository_id, package_path,
action, simulate, authorized):
self._package_id = package_id
self._repository_id = repository_id
self._path = package_path
self._action = action
self._simulate = simulate
self._authorized = authorized
@@ -401,6 +403,12 @@ class RigoDaemonService(dbus.service.Object):
"""
return self._repository_id
def path(self):
"""
Return Application Package file path, if any.
"""
return self._path
def action(self):
"""
Return AppActions Action for this ActionQueueItem.
@@ -637,6 +645,12 @@ class RigoDaemonService(dbus.service.Object):
'org.freedesktop.DBus').GetConnectionUnixUser(
sender)
def _dbus_to_unicode(self, dbus_string):
"""
Convert dbus.String() to unicode object
"""
return dbus_string.decode(etpConst['conf_encoding'])
def _authorize(self, pid, action_id):
"""
Authorize privileged Activity.
@@ -770,6 +784,66 @@ class RigoDaemonService(dbus.service.Object):
GLib.idle_add(
self.repositories_updated, result, msg)
def _maybe_setup_package_repository(self, app_item):
"""
Setup the Entropy Repository for the Application in
the ActionQueueItem if path() is valid.
"""
path = app_item.path()
if app_item.action() == AppActions.INSTALL \
and path is not None:
write_output("_maybe_setup_package_repository: "
"this is a package file, generating "
"repo", debug=True)
self._rwsem.writer_acquire()
try:
pkg_matches = self._entropy.add_package_repository(
path)
return pkg_matches, True
except EntropyPackageException as err:
write_output("_maybe_setup_package_repository: "
"invalid package file, "
"%s" % (err,), debug=True)
return None, False
except Exception as err:
write_output("_maybe_setup_package_repository: "
"error during repository setup (err), "
"%s" % (repr(err),), debug=True)
return None, False
finally:
self._rwsem.writer_release()
return None, True
def _maybe_dismantle_package_repository(self, pkg_matches):
"""
Dismantle the Entropy Repository previously configured
by _maybe_setup_package_repository()
"""
repository_ids = set((x[1] for x in pkg_matches))
write_output(
"_maybe_dismantle_package_repository: "
"about to acquire rwsem for %s" % (pkg_matches,),
debug=True)
self._rwsem.writer_acquire()
try:
write_output(
"_maybe_dismantle_package_repository: "
"acquired rwsem for %s" % (pkg_matches,),
debug=True)
for repository_id in repository_ids:
try:
self._entropy.remove_repository(repository_id)
except Exception as err:
write_output(
"_maybe_dismantle_package_repository: "
"error during repository removal (err), "
"%s" % (repr(err),), debug=True)
finally:
self._rwsem.writer_release()
def _action_queue_worker_thread(self):
"""
Worker thread that handles Application Action requests
@@ -873,6 +947,15 @@ class RigoDaemonService(dbus.service.Object):
"doing %s" % (
item,), debug=True)
# Package file installation support
generated = True
if is_app:
pkg_matches, generated = \
self._maybe_setup_package_repository(item)
if not generated:
outcome = AppTransactionOutcome.INSTALL_ERROR
return
self._rwsem.reader_acquire()
try:
outcome = self._process_action(item, activity, is_app)
@@ -881,6 +964,8 @@ class RigoDaemonService(dbus.service.Object):
outcome,), debug=True)
finally:
self._rwsem.reader_release()
if is_app and pkg_matches:
self._maybe_dismantle_package_repository(pkg_matches)
finally:
_action_queue_finally(activity, outcome)
@@ -892,10 +977,12 @@ class RigoDaemonService(dbus.service.Object):
if is_app:
package_id, repository_id = item.pkg
action = item.action()
path = item.path()
else:
# upgrade
package_id, repository_id = None, None
action = None
path = None
simulate = item.simulate()
self._txs.reset()
@@ -905,13 +992,17 @@ class RigoDaemonService(dbus.service.Object):
if is_app:
if action == AppActions.REMOVE:
if path is not None:
# error, cannot remove an app from
# package path
return outcome
outcome = self._process_remove_action(
activity, action, simulate,
package_id, repository_id)
elif action == AppActions.INSTALL:
outcome = self._process_install_action(
activity, action, simulate,
package_id, repository_id)
package_id, repository_id, path)
else:
# upgrade
outcome = self._process_upgrade_action(
@@ -1192,7 +1283,7 @@ class RigoDaemonService(dbus.service.Object):
self._entropy.installed_repository().commit()
def _process_install_action(self, activity, action, simulate,
package_id, repository_id):
package_id, repository_id, path):
"""
Process Application Install Action.
"""
@@ -1760,10 +1851,11 @@ class RigoDaemonService(dbus.service.Object):
task.start()
return True
@dbus.service.method(BUS_NAME, in_signature='issb',
@dbus.service.method(BUS_NAME, in_signature='isssb',
out_signature='b', sender_keyword='sender')
def enqueue_application_action(self, package_id, repository_id,
action, simulate, sender=None):
package_path, action,
simulate, sender=None):
"""
Request RigoDaemon to enqueue a new Application Action, if
possible.
@@ -1771,7 +1863,8 @@ class RigoDaemonService(dbus.service.Object):
pid = self._get_caller_pid(sender)
write_output("enqueue_application_action called: "
"%s, %s, client pid: %s" % (
(package_id, repository_id), action, pid), debug=True)
(package_id, repository_id, package_path),
action, pid), debug=True)
self._enqueue_action_busy_hold_sem.acquire()
try:
@@ -1789,15 +1882,27 @@ class RigoDaemonService(dbus.service.Object):
"already busy, just enqueue",
debug=True)
def _enqueue():
def _enqueue(package_id, repository_id, package_path,
action, simulate):
if not package_path:
package_path = None
else:
self._dbus_to_unicode(package_path)
repository_id = self._dbus_to_unicode(repository_id)
package_id = int(package_id)
simulate = bool(simulate)
action = self._dbus_to_unicode(action)
try:
authorized = self._authorize(
pid, PolicyActions.MANAGE_APPLICATIONS)
item = self.ActionQueueItem(
int(package_id),
str(repository_id),
package_id,
repository_id,
package_path,
action,
bool(simulate),
simulate,
authorized)
self._action_queue.append(item)
if authorized:
@@ -1810,7 +1915,9 @@ class RigoDaemonService(dbus.service.Object):
finally:
self._enqueue_action_busy_hold_sem.release()
task = ParallelTask(_enqueue)
task = ParallelTask(_enqueue, package_id,
repository_id, package_path,
action, simulate)
task.name = "EnqueueApplicationActionThread"
task.daemon = True
task.start()
@@ -2166,7 +2273,7 @@ class RigoDaemonService(dbus.service.Object):
Load a new ConfigurationFiles object.
"""
write_output("reload_configuration_updates called", debug=True)
def _reload():
def _reload(pid):
authorized = self._authorize(
pid, PolicyActions.MANAGE_CONFIGURATION)
if not authorized:
@@ -2181,7 +2288,9 @@ class RigoDaemonService(dbus.service.Object):
finally:
self._rwsem.reader_release()
task = ParallelTask(_reload)
task = ParallelTask(
_reload,
self._get_caller_pid(sender))
task.name = "ReloadConfigurationUpdates"
task.daemon = True
task.start()
@@ -15,6 +15,7 @@
<method name="enqueue_application_action">
<arg name="package_id" type="i" direction="in"/>
<arg name="repository_id" type="s" direction="in"/>
<arg name="package_path" type="s" direction="in"/>
<arg name="action" type="s" direction="in"/>
<arg name="simulate" type="b" direction="in"/>
<arg name="accepted" type="b" direction="out"/>
+91 -3
View File
@@ -21,6 +21,7 @@ this program; if not, write to the Free Software Foundation, Inc.,
import time
from threading import Lock, Semaphore, current_thread
from collections import deque
import dbus
@@ -46,6 +47,7 @@ from RigoDaemon.config import DbusConfig as DaemonDbusConfig
from entropy.const import const_debug_write, \
const_debug_enabled, etpConst
from entropy.misc import ParallelTask
from entropy.exceptions import EntropyPackageException
from entropy.i18n import _, ngettext
from entropy.output import darkgreen, brown, darkred, red, blue
@@ -62,6 +64,7 @@ class RigoServiceController(GObject.Object):
NOTIFICATION_CONTEXT_ID = "RigoServiceControllerContextId"
ANOTHER_ACTIVITY_CONTEXT_ID = "AnotherActivityNotificationContextId"
SYSTEM_UPGRADE_CONTEXT_ID = "SystemUpgradeContextId"
PKG_INSTALL_CONTEXT_ID = "PackageInstallContextId"
class ServiceNotificationBox(NotificationBox):
@@ -169,7 +172,7 @@ class RigoServiceController(GObject.Object):
_UNSUPPORTED_APPLICATIONS_SIGNAL = "unsupported_applications"
_RESTARTING_UPGRADE_SIGNAL = "restarting_system_upgrade"
_CONFIGURATION_UPDATES_SIGNAL = "configuration_updates_available"
_SUPPORTED_APIS = [0]
_SUPPORTED_APIS = [1]
def __init__(self, rigo_app, activity_rwsem,
entropy_client, entropy_ws):
@@ -192,6 +195,8 @@ class RigoServiceController(GObject.Object):
self._registered_signals = {}
self._registered_signals_mutex = Lock()
self._package_repositories = deque()
self._local_transactions = {}
self._local_activity = LocalActivityStates.READY
self._local_activity_mutex = Lock()
@@ -702,6 +707,7 @@ class RigoServiceController(GObject.Object):
# This way repository in-RAM caches are reset
# otherwise installed repository metadata becomes
# inconsistent
self._release_local_package_repositories()
self._release_local_resources(clear_avc=False)
# reset progress bar, we're done with it
@@ -1211,6 +1217,17 @@ class RigoServiceController(GObject.Object):
task.daemon = True
task.start()
def package_install_request(self, package_path, simulate=False):
"""
Start Application Install Action for package file.
"""
task = ParallelTask(self._package_install_request,
package_path, simulate=simulate)
task.name = "PackageInstallRequest{%s, %s}" % (
package_path, simulate,)
task.daemon = True
task.start()
def upgrade_system(self, simulate=False):
"""
Start a System Upgrade.
@@ -1450,6 +1467,22 @@ class RigoServiceController(GObject.Object):
raise sem_data['exc']
return sem_data['res']
def _release_local_package_repositories(self):
"""
Release all the local package file repositories.
"""
self._entropy.rwsem().writer_acquire()
try:
while True:
try:
repository_id = self._package_repositories.popleft()
except IndexError:
break
self._entropy.remove_repository(
repository_id)
finally:
self._entropy.rwsem().writer_release()
def _release_local_resources(self, clear_avc=True):
"""
Release all the local resources (like repositories)
@@ -1936,8 +1969,12 @@ class RigoServiceController(GObject.Object):
"""
if app is not None:
package_id, repository_id = app.get_details().pkg
package_path = app.path
else:
package_id, repository_id = None, None
package_path = None
if package_path is None:
package_path = ""
sig_match = None
if busied:
@@ -1999,8 +2036,9 @@ class RigoServiceController(GObject.Object):
self._entropy_bus,
dbus_interface=self.DBUS_INTERFACE
).enqueue_application_action(
package_id, repository_id, daemon_action,
simulate)
package_id, repository_id,
package_path, daemon_action,
simulate)
def _enqueue_callback():
return self._execute_mainloop(_enqueue)
@@ -2063,6 +2101,56 @@ class RigoServiceController(GObject.Object):
DaemonAppTransactionOutcome.SUCCESS,
local_activity)
def _package_install_request(self, package_path, simulate=False):
"""
Forward Application Package Install request to RigoDaemon.
"""
self._entropy.rwsem().writer_acquire()
try:
package_matches = self._entropy.add_package_repository(
package_path)
except EntropyPackageException as exc:
def _notify():
msg = _("Package Install Error: <i>%s</i>") % (exc,)
box = self.ServiceNotificationBox(
prepare_markup(msg),
Gtk.MessageType.ERROR,
context_id=self.PKG_INSTALL_CONTEXT_ID)
box.add_destroy_button(_("Okay"))
self._nc.append(box)
GLib.idle_add(_notify)
return
finally:
self._entropy.rwsem().writer_release()
accepted = False
accepted_count = 0
repository_ids = set()
for package_match in package_matches:
pkg_id, pkg_repo = package_match
repository_ids.add(pkg_repo)
app = Application(self._entropy, self._entropy_ws,
package_match,
package_path=package_path)
accepted = self._application_request(
app, AppActions.INSTALL, simulate=simulate)
if not accepted:
break
accepted_count += 1
if accepted_count == 0:
self._entropy.rwsem().writer_acquire()
try:
for repository_id in repository_ids:
self._entropy.remove_repository(
repository_id)
finally:
self._entropy.rwsem().writer_release()
else:
for repository_id in repository_ids:
self._package_repositories.append(
repository_id)
def _application_request(self, app, app_action, simulate=False,
master=True):
"""
+18 -3
View File
@@ -821,11 +821,12 @@ class Application(object):
return self._licenses
def __init__(self, entropy_client, entropy_ws, package_match,
redraw_callback=None):
redraw_callback=None, package_path=None):
self._entropy = entropy_client
self._entropy_ws = entropy_ws
self._pkg_match = package_match
self._pkg_id, self._repo_id = package_match
self._path = package_path
self._redraw_callback = redraw_callback
@property
@@ -843,6 +844,14 @@ class Application(object):
finally:
self._entropy.rwsem().reader_release()
@property
def path(self):
"""
If this Application comes from a single package file,
return the package path.
"""
return self._path
def is_installed(self):
"""
Return if Application is currently installed.
@@ -1102,9 +1111,15 @@ class Application(object):
return app_install, app_remove
except DependenciesNotFound:
except DependenciesNotFound as err:
const_debug_write(
__name__,
"get_install_queue: DependenciesNotFound: %s" % (err,))
return None
except DependenciesCollision:
except DependenciesCollision as err:
const_debug_write(
__name__,
"get_install_queue: DependenciesCollision: %s" % (err,))
return None
finally:
+92 -44
View File
@@ -30,7 +30,8 @@ from rigo.paths import CONF_DIR
from rigo.enums import RigoViewStates, AppActions
from rigo.models.application import Application, ApplicationMetadata
from rigo.utils import escape_markup, prepare_markup
from rigo.ui.gtk3.widgets.notifications import OrphanedAppsNotificationBox
from rigo.ui.gtk3.widgets.notifications import \
NotificationBox
from entropy.cache import EntropyCacher
from entropy.const import etpConst, const_debug_write, \
@@ -92,6 +93,7 @@ class ApplicationsViewController(GObject.Object):
self._nf_box = nf_box
self._not_found_search_box = None
self._not_found_label = None
self._nc = None
self._cacher = EntropyCacher()
self._search_thread_mutex = Lock()
@@ -101,6 +103,12 @@ class ApplicationsViewController(GObject.Object):
self._search_writeback_mutex = Lock()
self._search_writeback_thread = None
def set_notification_controller(self, nc):
"""
Bind a NotificationViewController to this object.
"""
self._nc = nc
def _search_icon_release(self, search_entry, icon_pos, _other):
"""
Event associated to the Search bar icon click.
@@ -178,15 +186,17 @@ class ApplicationsViewController(GObject.Object):
finally:
self._entropy.rwsem().reader_release()
def __simulate_app_install(self, text):
def install(self, dependency, simulate=False):
"""
Try to match dependency to an Application and then install
it, if possible.
"""
const_debug_write(
__name__,
"__simulate_app_install: "
"%s" % (text,))
"install: %s" % (dependency,))
self._entropy.rwsem().reader_acquire()
try:
pkg_match = self._entropy.atom_match(text)
pkg_match = self._entropy.atom_match(dependency)
finally:
self._entropy.rwsem().reader_release()
@@ -194,60 +204,102 @@ class ApplicationsViewController(GObject.Object):
if pkg_id == -1:
const_debug_write(
__name__,
"__simulate_app_install: "
"no match for: %s" % (text,))
"install: "
"no match for: %s" % (dependency,))
def _notify():
msg = _("Application <b>%s</b> not found")
msg = msg % (dependency,)
box = NotificationBox(
prepare_markup(msg), Gtk.MessageType.ERROR,
context_id="AppInstallNotFoundContextId")
self._nc.append(box, timeout=10)
if self._nc is not None:
GLib.idle_add(_notify)
return
app = Application(
self._entropy, self._entropy_ws,
pkg_match)
self._service.application_request(
app, AppActions.INSTALL, simulate=True)
app, AppActions.INSTALL, simulate=simulate)
const_debug_write(
__name__,
"__simulate_app_install: "
"install: "
"application_request() sent for: %s, %s" % (
text, app,))
dependency, app,))
def __simulate_app_removal(self, text):
def install_package(self, package_path, simulate=False):
"""
Install Entropy Package file.
"""
const_debug_write(
__name__,
"install_package: %s" % (package_path,))
self._service.package_install_request(
package_path, simulate=simulate)
const_debug_write(
__name__,
"__simulate_app_removal: "
"%s" % (text,))
"install_package: "
"package_install_request() sent for: %s" % (
package_path,))
def remove(self, dependency, simulate=False):
"""
Try to match dependency to an Application and then remove
it, if possible.
"""
const_debug_write(
__name__,
"remove: %s" % (dependency,))
self._entropy.rwsem().reader_acquire()
try:
pkg_match = self._entropy.atom_match(text)
inst_repo = self._entropy.installed_repository()
pkg_repo = inst_repo.repository_id()
pkg_id, rc = inst_repo.atomMatch(dependency)
finally:
self._entropy.rwsem().reader_release()
pkg_id, pkg_repo = pkg_match
if pkg_id == -1:
const_debug_write(
__name__,
"__simulate_app_removal: "
"no match for: %s" % (text,))
"remove: "
"no match for: %s" % (dependency,))
def _notify():
msg = _("Application <b>%s</b> not found")
msg = msg % (dependency,)
box = NotificationBox(
prepare_markup(msg), Gtk.MessageType.ERROR,
context_id="AppRemoveNotFoundContextId")
self._nc.append(box, timeout=10)
if self._nc is not None:
GLib.idle_add(_notify)
return
app = Application(
self._entropy, self._entropy_ws,
pkg_match)
app = app.get_installed()
if app is None:
const_debug_write(
__name__,
"__simulate_app_removal: "
"not installed: %s" % (text,))
return
(pkg_id, pkg_repo))
self._service.application_request(
app, AppActions.REMOVE, simulate=True)
app, AppActions.REMOVE, simulate=simulate)
const_debug_write(
__name__,
"__simulate_app_removal: "
"remove: "
"application_request() sent for: %s, %s" % (
text, app,))
dependency, app,))
def upgrade(self, simulate=False):
"""
Launch a System Upgrade activity.
"""
const_debug_write(
__name__, "upgrade")
self._service.upgrade_system(simulate=simulate)
const_debug_write(
__name__, "upgrade:"
" upgrade_system() sent")
def __simulate_orphaned_apps(self, text):
@@ -272,38 +324,34 @@ class ApplicationsViewController(GObject.Object):
__name__,
"__simulate_orphaned_apps: completed")
def __simulate_system_upgrade(self):
const_debug_write(
__name__, "__simulate_system_upgrade")
self._service.upgrade_system(simulate=True)
const_debug_write(
__name__, "__simulate_system_upgrade:"
" upgrade_system() sent")
def __search_thread(self, text):
## special keywords hook
if text == "rigo:update":
self._update_repositories_safe()
return
if text == "rigo:upgrade":
self.upgrade()
return
elif text == "rigo:confupdate":
self._service.configuration_updates()
return
# debug, simulation
elif text == "rigo:vte":
GLib.idle_add(self.emit, "view-want-change",
RigoViewStates.WORK_VIEW_STATE,
None)
return
elif text == "rigo:confupdate":
self._service.configuration_updates()
return
elif text.startswith("rigo:simulate:i:"):
sim_str = text[len("rigo:simulate:i:"):].strip()
if sim_str:
self.__simulate_app_install(sim_str)
self.install(sim_str, simulate=True)
return
elif text.startswith("rigo:simulate:r:"):
sim_str = text[len("rigo:simulate:r:"):].strip()
if sim_str:
self.__simulate_app_removal(sim_str)
self.remove(sim_str, simulate=True)
return
elif text.startswith("rigo:simulate:o:"):
sim_str = text[len("rigo:simulate:o:"):].strip()
@@ -311,7 +359,7 @@ class ApplicationsViewController(GObject.Object):
self.__simulate_orphaned_apps(sim_str)
return
if text == "rigo:simulate:u":
self.__simulate_system_upgrade()
self.upgrade(simulate=True)
return
# serialize searches to avoid segfaults with sqlite3
+61 -1
View File
@@ -21,6 +21,7 @@ this program; if not, write to the Free Software Foundation, Inc.,
"""
import os
import argparse
# entropy.i18n will pick this up
os.environ['ETP_GETTEXT_DOMAIN'] = "rigo"
@@ -220,6 +221,7 @@ class Rigo(Gtk.Application):
self._activity_rwsem, self._entropy,
self._entropy_ws, self._service,
self._avc, self._notification)
self._avc.set_notification_controller(self._nc)
# Bottom NotificationBox controller.
# Bottom notifications are only used for
@@ -264,7 +266,7 @@ class Rigo(Gtk.Application):
If --dumper is in argv, a recurring thread dump
function will be spawned every 30 seconds.
"""
dumper_enable = "--no-dumper" not in sys.argv
dumper_enable = self._nsargs.dumper
if dumper_enable:
task = None
@@ -658,6 +660,34 @@ class Rigo(Gtk.Application):
Gtk.main_quit()
return
parser = argparse.ArgumentParser(
description=_("Rigo Application Browser"))
parser.add_argument(
"--package", type=file,
metavar="<path>", help="package path")
parser.add_argument(
"--install",
metavar="<dep string>", help="install given dependency")
parser.add_argument(
"--remove",
metavar="<dep string>", help="remove given dependency")
parser.add_argument(
"--dumper", help="enable the main thread dumper (debug)",
action="store_true", default=False)
parser.add_argument(
"--debug", help="enable Entropy Library debug mode",
action="store_true", default=False)
try:
self._nsargs = parser.parse_args(sys.argv[1:])
except IOError as err:
self._show_ok_dialog(
None,
escape_markup(_("Rigo")),
escape_markup("%s" % (err,)))
entropy.tools.kill_threads()
Gtk.main_quit()
return
self._thread_dumper()
self._config_view_c.setup()
self._app_view_c.setup()
@@ -667,6 +697,7 @@ class Rigo(Gtk.Application):
self._service.setup(acquired)
self._easter_eggs()
self._window.show()
self._start_managing()
def _easter_eggs(self):
"""
@@ -686,6 +717,35 @@ class Rigo(Gtk.Application):
box.add_destroy_button(_("Woot, thanks"))
self._nc.append(box)
def _start_managing(self):
"""
Start managing applications passed via argv.
"""
if self._nsargs.install:
dependency = self._nsargs.install
task = ParallelTask(
self._avc.install, dependency)
task.name = "AppInstall-%s" % (dependency,)
task.daemon = True
task.start()
if self._nsargs.remove:
dependency = self._nsargs.remove
task = ParallelTask(
self._avc.remove, dependency)
task.name = "AppRemove-%s" % (dependency,)
task.daemon = True
task.start()
if self._nsargs.package:
path = self._nsargs.package.name
self._nsargs.package.close() # no need, unfortunately
task = ParallelTask(
self._avc.install_package, path)
task.name = "AppInstallPackage-%s" % (path,)
task.daemon = True
task.start()
def run(self):
"""
Run Rigo ;-)