Пример #1
0
        def _diff_handler(path):
            try:
                if path == "":
                    sem_data['res'] = False
                    return

                if not const_file_writable(path):
                    sem_data['res'] = False
                    return

                proc = open_editor(path)
                if proc is None:
                    self._ignore_remove(path)
                    sem_data['res'] = False
                    return

                task = ParallelTask(proc.wait)
                task.name = "Diff-%s" % (self, )
                task.daemon = True
                task.start()
                sem_data['res'] = True

            except Exception as exc:
                sem_data['exc'] = exc
            finally:
                sem_data['sem'].release()
Пример #2
0
    def _on_stars_clicked(self, widget, app=None):
        """
        Stars clicked, user wants to vote.
        """
        if app is None:
            app = self._last_app
            if app is None:
                # wtf
                return

        def _sender(app, vote):
            if not app.is_webservice_available():
                GLib.idle_add(self._notify_webservice_na, app,
                              self.VOTE_NOTIFICATION_CONTEXT_ID)
                return
            ws_user = app.get_webservice_username()
            if ws_user is not None:
                GLib.idle_add(self._notify_vote_submit, app, ws_user, vote)
            else:
                GLib.idle_add(self._notify_login_request, app, vote,
                              self._on_stars_login_success,
                              self._on_stars_login_failed,
                              self.VOTE_NOTIFICATION_CONTEXT_ID)

        vote = int(self._stars.get_rating())  # is float
        task = ParallelTask(_sender, app, vote)
        task.name = "AppViewSendVote"
        task.start()
Пример #3
0
 def launch_package_manager(self, *data, **kwargs):
     args = ["/usr/bin/rigo"]
     if kwargs.get("other_args"):
         args += kwargs["other_args"]
     task = ParallelTask(subprocess.call, args)
     task.daemon = True
     task.start()
Пример #4
0
    def _on_stars_clicked(self, widget, app=None):
        """
        Stars clicked, user wants to vote.
        """
        if app is None:
            app = self._last_app
            if app is None:
                # wtf
                return

        def _sender(app, vote):
            if not app.is_webservice_available():
                GLib.idle_add(self._notify_webservice_na, app,
                              self.VOTE_NOTIFICATION_CONTEXT_ID)
                return
            ws_user = app.get_webservice_username()
            if ws_user is not None:
                GLib.idle_add(self._notify_vote_submit, app, ws_user, vote)
            else:
                GLib.idle_add(self._notify_login_request, app, vote,
                              self._on_stars_login_success,
                              self._on_stars_login_failed,
                              self.VOTE_NOTIFICATION_CONTEXT_ID)

        vote = int(self._stars.get_rating()) # is float
        task = ParallelTask(_sender, app, vote)
        task.name = "AppViewSendVote"
        task.start()
Пример #5
0
    def on_dbBackupButton_clicked(self, widget):

        def _run_backup(callback):
            cl_repo_name = etpConst.get(
                'clientdbid',
                getattr(InstalledPackagesRepository, "NAME", None))
            with self._privileges:
                with self._async_event_execution_lock:
                    status, err_msg = self._entropy.backup_repository(
                        cl_repo_name,
                        os.path.dirname(etpConst['etpdatabaseclientfilepath']))
                    gobject.idle_add(callback, status, err_msg)

        def _backup_complete(status, err_msg):
            self.ui_lock(False)
            self.end_working()
            if not status:
                okDialog( self.ui.main, "%s: %s" % (_("Error during backup"),
                    err_msg,) )
                return
            okDialog(self.ui.main, "%s" % (_("Backup complete"),))
            self.fill_pref_db_backup_page()
            self.dbBackupView.queue_draw()

        self.start_working()
        self.ui_lock(True)
        with self._privileges:
            # make sure to commit any transaction before backing-up
            self._entropy.installed_repository().commit()
            t = ParallelTask(_run_backup, _backup_complete)
            t.start()
Пример #6
0
 def launch_package_manager(self, *data, **kwargs):
     args = ["/usr/bin/rigo"]
     if kwargs.get("other_args"):
         args += kwargs["other_args"]
     task = ParallelTask(subprocess.call, args)
     task.daemon = True
     task.start()
Пример #7
0
        def _diff_handler(path):
            try:
                if path == "":
                    sem_data['res'] = False
                    return

                if not const_file_writable(path):
                    sem_data['res'] = False
                    return

                proc = open_editor(path)
                if proc is None:
                    self._ignore_remove(path)
                    sem_data['res'] = False
                    return

                task = ParallelTask(proc.wait)
                task.name = "Diff-%s" % (self,)
                task.daemon = True
                task.start()
                sem_data['res'] = True

            except Exception as exc:
                sem_data['exc'] = exc
            finally:
                sem_data['sem'].release()
Пример #8
0
 def _vote_submit(self, app, username, vote):
     """
     Do the actual vote submit.
     """
     task = ParallelTask(self._vote_submit_thread_body, app, username, vote)
     task.name = "VoteSubmitThreadBody"
     task.daemon = True
     task.start()
Пример #9
0
 def _vote_submit(self, app, username, vote):
     """
     Do the actual vote submit.
     """
     task = ParallelTask(self._vote_submit_thread_body, app, username, vote)
     task.name = "VoteSubmitThreadBody"
     task.daemon = True
     task.start()
Пример #10
0
 def setup(self):
     """
     Reimplemented from NotificationViewController.
     """
     th = ParallelTask(self.__check_connectivity)
     th.daemon = True
     th.name = "CheckConnectivity"
     th.start()
Пример #11
0
 def setup(self):
     """
     Reimplemented from NotificationViewController.
     """
     th = ParallelTask(self.__check_connectivity)
     th.daemon = True
     th.name = "CheckConnectivity"
     th.start()
Пример #12
0
 def _comment_submit(self, app, username, text):
     """
     Actual Comment submit to Web Service.
     Here we arrive from the MainThread.
     """
     task = ParallelTask(self._comment_submit_thread_body, app, username, text)
     task.name = "CommentSubmitThreadBody"
     task.daemon = True
     task.start()
Пример #13
0
 def _on_remove_comment(self, widget):
     """
     We are requested to remove this comment, spawn the request.
     """
     self.hide()
     task = ParallelTask(self._remove_comment)
     task.name = "RemoveComment{%s}" % (self._comment,)
     task.daemon = True
     task.start()
     return True
Пример #14
0
def open_url(url):
    """
    Open the given URL using xdg-open
    """
    def _open_url(url):
        subprocess.call(["xdg-open", url])
    task = ParallelTask(_open_url, url)
    task.name = "UrlOpen"
    task.daemon = True
    task.start()
Пример #15
0
 def _on_remove_comment(self, widget):
     """
     We are requested to remove this comment, spawn the request.
     """
     self.hide()
     task = ParallelTask(self._remove_comment)
     task.name = "RemoveComment{%s}" % (self._comment, )
     task.daemon = True
     task.start()
     return True
Пример #16
0
def open_url(url):
    """
    Open the given URL using xdg-open
    """
    def _open_url(url):
        subprocess.call(["xdg-open", url])
    task = ParallelTask(_open_url, url)
    task.name = "UrlOpen"
    task.daemon = True
    task.start()
Пример #17
0
 def _comment_submit(self, app, username, text):
     """
     Actual Comment submit to Web Service.
     Here we arrive from the MainThread.
     """
     task = ParallelTask(self._comment_submit_thread_body, app, username,
                         text)
     task.name = "CommentSubmitThreadBody"
     task.daemon = True
     task.start()
Пример #18
0
 def _search(self, old_text, _force=False):
     cur_text = self._search_entry.get_text()
     if (cur_text == old_text and cur_text) or _force:
         search_text = copy.copy(old_text)
         search_text = const_convert_to_unicode(
             search_text, enctype=etpConst['conf_encoding'])
         if _force:
             self._search_entry.set_text(search_text)
         th = ParallelTask(self.__search_thread, search_text)
         th.name = "SearchThread"
         th.start()
Пример #19
0
 def _search(self, old_text, _force=False):
     cur_text = self._search_entry.get_text()
     if (cur_text == old_text and cur_text) or _force:
         search_text = copy.copy(old_text)
         search_text = const_convert_to_unicode(
             search_text, enctype=etpConst['conf_encoding'])
         if _force:
             self._search_entry.set_text(search_text)
         th = ParallelTask(self.__search_thread, search_text)
         th.name = "SearchThread"
         th.start()
Пример #20
0
 def _on_license_activate(self, widget, uri):
     """
     License link clicked.
     """
     license_apps = self._licenses.get(uri)
     if not license_apps:
         return True
     task = ParallelTask(self._show_license, uri, license_apps)
     task.name = "ShowLicense"
     task.daemon = True
     task.start()
     return True
Пример #21
0
 def _on_application_activated(self, avc, app):
     """
     Event received from Gtk widgets requesting us to load package
     information. Once we're done loading the shit, we just emit
     'application-show' and let others do the UI switch.
     """
     self._visible = True
     self._last_app = app
     task = ParallelTask(self.__application_activate, app)
     task.name = "ApplicationActivate"
     task.daemon = True
     task.start()
Пример #22
0
 def _on_application_activated(self, avc, app):
     """
     Event received from Gtk widgets requesting us to load package
     information. Once we're done loading the shit, we just emit
     'application-show' and let others do the UI switch.
     """
     self._visible = True
     self._last_app = app
     task = ParallelTask(self.__application_activate, app)
     task.name = "ApplicationActivate"
     task.daemon = True
     task.start()
Пример #23
0
 def _on_license_activate(self, widget, uri):
     """
     License link clicked.
     """
     license_apps = self._licenses.get(uri)
     if not license_apps:
         return True
     task = ParallelTask(self._show_license, uri, license_apps)
     task.name = "ShowLicense"
     task.daemon = True
     task.start()
     return True
Пример #24
0
 def _on_reload_state(self, srv, app, daemon_action, app_outcome=None):
     """
     Reload Application state due to a transaction event.
     """
     if not self._visible:
         return
     last_app = self._last_app
     if last_app is not None:
         app = last_app
     task = ParallelTask(self._reload_application_state, app)
     task.daemon = True
     task.name = "OnReloadAppState"
     task.start()
Пример #25
0
 def _on_reload_state(self, srv, app, daemon_action, app_outcome=None):
     """
     Reload Application state due to a transaction event.
     """
     if not self._visible:
         return
     last_app = self._last_app
     if last_app is not None:
         app = last_app
     task = ParallelTask(self._reload_application_state, app)
     task.daemon = True
     task.name = "OnReloadAppState"
     task.start()
Пример #26
0
    def add_downloads(self):
        """
        Add downloads stats for package.
        """
        try:
            repository_id = self._get_repository_id()
            self._validate_repository_id(repository_id)
        except AttributeError:
            return self._generic_invalid_request()

        try:
            package_names = self._get_package_names()
        except AttributeError:
            return self._generic_invalid_request()

        # validate branch
        branch = (request.params.get('branch') or "").strip()
        if not branch:
            return self._generic_invalid_request()
        if not entropy.tools.validate_branch_name(branch):
            return self._generic_invalid_request()

        # validate release_string
        release_string = (request.params.get('release_string') or "").strip()
        if not release_string:
            return self._generic_invalid_request()
        if not entropy.tools.is_valid_string(release_string):
            return self._generic_invalid_request()

        # validate hw_hash
        hw_hash = (request.params.get('hw_hash') or "").strip()
        if not hw_hash:
            return self._generic_invalid_request()
        if not entropy.tools.is_valid_string(hw_hash):
            return self._generic_invalid_request()

        ip_addr = self._get_ip_address(request)

        task = ParallelTask(
            self._add_downloads,
            package_names, branch,
            release_string, hw_hash, ip_addr)
        task.name = "AddDownloadsThread"
        task.daemon = True
        task.start()

        response = self._api_base_response(
            WebService.WEB_SERVICE_RESPONSE_CODE_OK)
        response['r'] = True
        return self._service_render(response)
Пример #27
0
 def get_ugc_package_vote_delayed(self):
     if self.pkgset:
         return -1
     atom = self.get_name()
     if not atom:
         return None
     pkg_key = entropy.dep.dep_getkey(atom)
     try:
         vote_raw = self._get_vote_cache_2(pkg_key)
     except KeyError:
         vote_raw = None
         # schedule a new task
         th = ParallelTask(self._get_vote_raw, pkg_key)
         th.start()
     return vote_raw
Пример #28
0
    def add_downloads(self):
        """
        Add downloads stats for package.
        """
        try:
            repository_id = self._get_repository_id()
            self._validate_repository_id(repository_id)
        except AttributeError:
            return self._generic_invalid_request()

        try:
            package_names = self._get_package_names()
        except AttributeError:
            return self._generic_invalid_request()

        # validate branch
        branch = (request.params.get('branch') or "").strip()
        if not branch:
            return self._generic_invalid_request()
        if not entropy.tools.validate_branch_name(branch):
            return self._generic_invalid_request()

        # validate release_string
        release_string = (request.params.get('release_string') or "").strip()
        if not release_string:
            return self._generic_invalid_request()
        if not entropy.tools.is_valid_string(release_string):
            return self._generic_invalid_request()

        # validate hw_hash
        hw_hash = (request.params.get('hw_hash') or "").strip()
        if not hw_hash:
            return self._generic_invalid_request()
        if not entropy.tools.is_valid_string(hw_hash):
            return self._generic_invalid_request()

        ip_addr = self._get_ip_address(request)

        task = ParallelTask(self._add_downloads, package_names, branch,
                            release_string, hw_hash, ip_addr)
        task.name = "AddDownloadsThread"
        task.daemon = True
        task.start()

        response = self._api_base_response(
            WebService.WEB_SERVICE_RESPONSE_CODE_OK)
        response['r'] = True
        return self._service_render(response)
Пример #29
0
    def _on_source_edit(self, widget, path, cu):
        """
        Source File Edit request.
        """
        const_debug_write(__name__, "Confc: _on_source_edit: %s" % (cu,))
        def _edit():
            if not cu.edit():
                def _notify():
                    msg = "%s: <i>%s</i>" % (
                        _("Cannot <b>edit</b> configuration file"),
                        cu.source(),)
                    self._notify_error(msg)
                GLib.idle_add(_notify)

        task = ParallelTask(_edit)
        task.name = "OnSourceEdit"
        task.daemon = True
        task.start()
Пример #30
0
    def load(self):
        """
        Request a content (re)load.
        """
        const_debug_write(__name__, "GroupVC: load() called")

        def _load():
            groups = self._service.groups()
            objs = []
            for identifier, data in groups.items():
                group = Group(self._avc, identifier, data["name"], data["description"], data["categories"])
                objs.append(group)
            self.set_many_safe(objs)

        task = ParallelTask(_load)
        task.name = "OnGroupLoadRequest"
        task.daemon = True
        task.start()
Пример #31
0
    def load(self):
        """
        Request a content (re)load.
        """
        const_debug_write(__name__, "RepoVC: load() called")

        def _load():
            repositories = self._service.list_repositories()
            objs = []
            for repository_id, description, enabled in repositories:
                obj = Repository(repository_id, description, enabled)
                objs.append(obj)
            self.set_many_safe(objs)

        task = ParallelTask(_load)
        task.name = "OnRepositoryLoadRequest"
        task.daemon = True
        task.start()
Пример #32
0
    def load(self):
        """
        Request a content (re)load.
        """
        const_debug_write(__name__, "RepoVC: load() called")

        def _load():
            repositories = self._service.list_repositories()
            objs = []
            for repository_id, description, enabled in repositories:
                obj = Repository(repository_id, description, enabled)
                objs.append(obj)
            self.set_many_safe(objs)

        task = ParallelTask(_load)
        task.name = "OnRepositoryLoadRequest"
        task.daemon = True
        task.start()
Пример #33
0
    def _on_show_diff(self, widget, path, cu):
        """
        Diff request.
        """
        const_debug_write(__name__, "Confc: _on_show_diff: %s" % (cu,))
        def _diff():
            if not cu.diff():
                def _notify():
                    msg = "%s: <i>%s</i>" % (
                        _("Cannot <b>show</b> configuration "
                          "files difference"),
                        cu.source(),)
                    self._notify_error(msg)
                GLib.idle_add(_notify)

        task = ParallelTask(_diff)
        task.name = "OnShowDiff"
        task.daemon = True
        task.start()
Пример #34
0
    def load(self):
        """
        Request a content (re)load.
        """
        const_debug_write(__name__, "GroupVC: load() called")

        def _load():
            groups = self._service.groups()
            objs = []
            for identifier, data in groups.items():
                group = Group(self._avc, identifier, data['name'],
                              data['description'], data['categories'])
                objs.append(group)
            self.set_many_safe(objs)

        task = ParallelTask(_load)
        task.name = "OnGroupLoadRequest"
        task.daemon = True
        task.start()
Пример #35
0
    def _on_show_diff(self, widget, path, cu):
        """
        Diff request.
        """
        const_debug_write(__name__, "Confc: _on_show_diff: %s" % (cu,))

        def _diff():
            if not cu.diff():

                def _notify():
                    msg = "%s: <i>%s</i>" % (_("Cannot <b>show</b> configuration " "files difference"), cu.source())
                    self._notify_error(msg)

                GLib.idle_add(_notify)

        task = ParallelTask(_diff)
        task.name = "OnShowDiff"
        task.daemon = True
        task.start()
Пример #36
0
    def _on_source_merge(self, widget, path, cu):
        """
        Source file merge request.
        """
        const_debug_write(__name__, "Confc: _on_source_merge: %s" % (cu,))
        def _merge():
            if not cu.merge():
                def _notify():
                    msg = "%s: <i>%s</i>" % (
                        _("Cannot <b>merge</b> configuration file"),
                        cu.source(),)
                    self._notify_error(msg)
                GLib.idle_add(_notify)
            else:
                GLib.idle_add(self._remove_path, path)

        task = ParallelTask(_merge)
        task.name = "OnSourceMerge"
        task.daemon = True
        task.start()
Пример #37
0
    def _login(self, button):
        """
        Try to login to Entropy Web Services.
        """
        username = self._username_entry.get_text()
        password = self._password_entry.get_text()

        try:
            username = const_convert_to_unicode(
                username, enctype=etpConst['conf_encoding'])
            password = const_convert_to_unicode(
                password, enctype=etpConst['conf_encoding'])
        except UnicodeDecodeError as err:
            const_debug_write(
                __name__, "LoginNotificationBox._login: %s" % (repr(err), ))
            return

        task = ParallelTask(self._login_thread_body, username, password)
        task.name = "LoginNotificationThreadBody"
        task.daemon = True
        task.start()
Пример #38
0
    def _start_managing(self):
        """
        Start managing applications passed via argv.
        """
        managing = False

        if self._nsargs.install:
            dependency = self._nsargs.install
            task = ParallelTask(
                self._avc.install, dependency)
            task.name = "AppInstall-%s" % (dependency,)
            task.daemon = True
            task.start()
            managing = True

        if self._nsargs.remove:
            dependency = self._nsargs.remove
            task = ParallelTask(
                self._avc.remove, dependency)
            task.name = "AppRemove-%s" % (dependency,)
            task.daemon = True
            task.start()
            managing = True

        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()
            managing = True

        return managing
Пример #39
0
    def on_dbRestoreButton_clicked(self, widget):
        model, myiter = self.dbBackupView.get_selection().get_selected()
        if myiter is None:
            return
        dbpath = model.get_value(myiter, 0)

        def _run_restore(callback):
            cl_repo_name = etpConst.get(
                'clientdbid',
                getattr(InstalledPackagesRepository, "NAME", None))

            with self._async_event_execution_lock:
                with self._privileges:
                    status, err_msg = self._entropy.restore_repository(dbpath,
                        etpConst['etpdatabaseclientfilepath'],
                        cl_repo_name)
                    gobject.idle_add(callback, status, err_msg)

        def _restore_done(status, err_msg):
            self.ui_lock(False)
            self.end_working()
            self._entropy.reopen_installed_repository()
            self.reset_cache_status()
            self.show_packages()
            if not status:
                okDialog(self.ui.main, "%s: %s" % (_("Error during restore"),
                    err_msg,))
                return
            self.fill_pref_db_backup_page()
            self.dbBackupView.queue_draw()
            okDialog(self.ui.main, "%s" % (_("Restore complete"),))

        self.start_working()
        self.ui_lock(True)
        with self._privileges:
            # make sure to commit any transaction before restoring
            self._entropy.installed_repository().commit()
            t = ParallelTask(_run_restore, _restore_done)
            t.start()
Пример #40
0
    def _on_send_comment(self, widget, app=None):
        """
        Send comment to Web Service.
        """
        if app is None:
            app = self._last_app
            if app is None:
                # we're hiding
                return

        text = self._app_comment_text_buffer.get_text(
            self._app_comment_text_buffer.get_start_iter(), self._app_comment_text_buffer.get_end_iter(), False
        )
        if not text.strip():
            return
        # make it utf-8
        text = const_convert_to_unicode(text, enctype=etpConst["conf_encoding"])

        def _sender(app, text):
            if not app.is_webservice_available():
                GLib.idle_add(self._notify_webservice_na, app, self.COMMENT_NOTIFICATION_CONTEXT_ID)
                return
            ws_user = app.get_webservice_username()
            if ws_user is not None:
                GLib.idle_add(self._notify_comment_submit, app, ws_user, text)
            else:
                GLib.idle_add(
                    self._notify_login_request,
                    app,
                    text,
                    self._on_comment_login_success,
                    self._on_comment_login_failed,
                    self.COMMENT_NOTIFICATION_CONTEXT_ID,
                )

        task = ParallelTask(_sender, app, text)
        task.name = "AppViewSendComment"
        task.start()
Пример #41
0
    def _login(self, button):
        """
        Try to login to Entropy Web Services.
        """
        username = self._username_entry.get_text()
        password = self._password_entry.get_text()

        try:
            username = const_convert_to_unicode(
                username, enctype=etpConst['conf_encoding'])
            password = const_convert_to_unicode(
                password, enctype=etpConst['conf_encoding'])
        except UnicodeDecodeError as err:
            const_debug_write(
                __name__,
                "LoginNotificationBox._login: %s" % (repr(err),))
            return

        task = ParallelTask(
            self._login_thread_body, username, password)
        task.name = "LoginNotificationThreadBody"
        task.daemon = True
        task.start()
Пример #42
0
    def _on_send_comment(self, widget, app=None):
        """
        Send comment to Web Service.
        """
        if app is None:
            app = self._last_app
            if app is None:
                # we're hiding
                return

        text = self._app_comment_text_buffer.get_text(
            self._app_comment_text_buffer.get_start_iter(),
            self._app_comment_text_buffer.get_end_iter(), False)
        if not text.strip():
            return
        # make it utf-8
        text = const_convert_to_unicode(text,
                                        enctype=etpConst['conf_encoding'])

        def _sender(app, text):
            if not app.is_webservice_available():
                GLib.idle_add(self._notify_webservice_na, app,
                              self.COMMENT_NOTIFICATION_CONTEXT_ID)
                return
            ws_user = app.get_webservice_username()
            if ws_user is not None:
                GLib.idle_add(self._notify_comment_submit, app, ws_user, text)
            else:
                GLib.idle_add(self._notify_login_request, app, text,
                              self._on_comment_login_success,
                              self._on_comment_login_failed,
                              self.COMMENT_NOTIFICATION_CONTEXT_ID)

        task = ParallelTask(_sender, app, text)
        task.name = "AppViewSendComment"
        task.start()
Пример #43
0
    def _start_managing(self):
        """
        Start managing applications passed via argv.
        """
        managing = False

        if self._nsargs.install:
            dependency = self._nsargs.install
            task = ParallelTask(self._avc.install, dependency)
            task.name = "AppInstall-%s" % (dependency, )
            task.daemon = True
            task.start()
            managing = True

        if self._nsargs.remove:
            dependency = self._nsargs.remove
            task = ParallelTask(self._avc.remove, dependency)
            task.name = "AppRemove-%s" % (dependency, )
            task.daemon = True
            task.start()
            managing = True

        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()
            managing = True

        if self._nsargs.upgrade:
            task = ParallelTask(self._avc.upgrade)
            task.name = "SystemUpgrade"
            task.daemon = True
            task.start()
            managing = True

        return managing
Пример #44
0
    def __cacher(self, run_until_empty=False, sync=False, _loop=False):
        """
        This is where the actual asynchronous copy takes
        place. __cacher runs on a different threads and
        all the operations done by this are atomic and
        thread-safe. It just loops over and over until
        __alive becomes False.
        """
        try:
            if self.__inside_with_stmt != 0:
                return
        except AttributeError:
            # interpreter shutdown
            pass

        # make sure our set delay is respected
        try:
            self.__cache_writer.set_delay(EntropyCacher.WRITEBACK_TIMEOUT)
        except AttributeError:
            # can be None
            pass

        # sleep if there's nothing to do
        if _loop:
            try:
                # CANBLOCK
                self.__worker_sem.acquire()
                # we just consumed one acquire()
                # that was dedicated to actual data,
                # put it back
                self.__worker_sem.release()
            except AttributeError:
                pass

        def _commit_data(_massive_data):
            for (key, cache_dir), data in _massive_data:
                d_o = entropy.dump.dumpobj
                if d_o is not None:
                    d_o(key, data, dump_dir=cache_dir)

        while self.__alive or run_until_empty:

            if const_debug_enabled():
                const_debug_write(
                    __name__,
                    "EntropyCacher.__cacher: loop: %s, alive: %s, empty: %s" %
                    (
                        _loop,
                        self.__alive,
                        run_until_empty,
                    ))

            with self.__enter_context_lock:
                massive_data = []
                try:
                    massive_data_count = EntropyCacher._OBJS_WRITTEN_AT_ONCE
                except AttributeError:  # interpreter shutdown
                    break
                while massive_data_count > 0:

                    if _loop:
                        # extracted an item from worker_sem
                        # call down() on the semaphore without caring
                        # can't sleep here because we're in a critical region
                        # holding __enter_context_lock
                        self.__worker_sem.acquire(False)

                    massive_data_count -= 1
                    try:
                        data = self.__cache_buffer.pop()
                    except (
                            ValueError,
                            TypeError,
                    ):
                        # TypeError is when objects are being destroyed
                        break  # stack empty
                    massive_data.append(data)

                if not massive_data:
                    break

                task = ParallelTask(_commit_data, massive_data)
                task.name = "EntropyCacherCommitter"
                task.daemon = not sync
                task.start()
                if sync:
                    task.join()

                if const_debug_enabled():
                    const_debug_write(
                        __name__,
                        "EntropyCacher.__cacher [%s], writing %s objs" % (
                            task,
                            len(massive_data),
                        ))

                if EntropyCacher.STASHING_CACHE:
                    for (key, cache_dir), data in massive_data:
                        try:
                            del self.__stashing_cache[(key, cache_dir)]
                        except (
                                AttributeError,
                                KeyError,
                        ):
                            continue
                del massive_data[:]
                del massive_data
Пример #45
0
    def __init__(self):
        self._current_state_lock = False
        self._current_state = RigoViewStates.STATIC_VIEW_STATE
        self._state_transitions = {
            RigoViewStates.BROWSER_VIEW_STATE: (
                self._enter_browser_state,
                self._exit_browser_state),
            RigoViewStates.STATIC_VIEW_STATE: (
                self._enter_static_state,
                self._exit_static_state),
            RigoViewStates.APPLICATION_VIEW_STATE: (
                self._enter_application_state,
                self._exit_application_state),
            RigoViewStates.WORK_VIEW_STATE: (
                self._enter_work_state,
                self._exit_work_state),
            RigoViewStates.CONFUPDATES_VIEW_STATE: (
                self._enter_confupdates_state,
                self._exit_confupdates_state),
            RigoViewStates.NOTICEBOARD_VIEW_STATE: (
                self._enter_noticeboard_state,
                self._exit_noticeboard_state),
            RigoViewStates.PREFERENCES_VIEW_STATE: (
                self._enter_preferences_state,
                self._exit_preferences_state),
            RigoViewStates.REPOSITORY_VIEW_STATE: (
                self._enter_repository_state,
                self._exit_repository_state),
            RigoViewStates.GROUPS_VIEW_STATE: (
                self._enter_groups_state,
                self._exit_groups_state)
        }
        self._state_metadata = {
            RigoViewStates.BROWSER_VIEW_STATE: {
                "title": _("Search"),
                },
            RigoViewStates.STATIC_VIEW_STATE: {
                "title": _("Rigo Application Browser"),
                },
            RigoViewStates.APPLICATION_VIEW_STATE: {
                "title": _("Application"),
                },
            RigoViewStates.WORK_VIEW_STATE: {
                "title": _("Working Hard"),
                },
            RigoViewStates.CONFUPDATES_VIEW_STATE: {
                "title": _("Wake Up"),
                },
            RigoViewStates.NOTICEBOARD_VIEW_STATE: {
                "title": _("Important Stuff"),
                },
            RigoViewStates.PREFERENCES_VIEW_STATE: {
                "title": _("Breaking Stuff"),
                },
            RigoViewStates.REPOSITORY_VIEW_STATE: {
                "title": _("Repository Stuff"),
                },
            RigoViewStates.GROUPS_VIEW_STATE: {
                "title": _("Application Groups"),
                },
        }
        self._state_mutex = Lock()

        icons = get_sc_icon_theme(DATA_DIR)

        self._activity_rwsem = ReadersWritersSemaphore()

        # This relies on the fact that the installed packages repository
        # is lazily loaded (thus, schema update code is).
        self._entropy = Client()
        self._entropy_ws = EntropyWebService(self._entropy)

        preload_task = ParallelTask(self._entropy_ws.preload)
        preload_task.name = "PreloadEntropyWebService"
        preload_task.daemon = True
        preload_task.start()

        self._service = RigoServiceController(
            self, self._activity_rwsem,
            self._entropy, self._entropy_ws)

        app_handler = Rigo.RigoHandler(self, self._service)

        self._builder = Gtk.Builder()
        self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui"))
        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")
        self._scrolled_view = self._builder.get_object("appsViewScrolledWindow")
        self._app_view = self._builder.get_object("appViewScrollWin")
        self._app_view.set_name("rigo-view")
        self._app_view_port = self._builder.get_object("appViewVport")
        self._app_view_port.set_name("rigo-view")
        self._not_found_box = self._builder.get_object("appsViewNotFoundVbox")

        self._config_scrolled_view = self._builder.get_object(
            "configViewScrolledWindow")
        self._config_view = self._builder.get_object("configViewVbox")
        self._config_view.set_name("rigo-view")

        self._repo_scrolled_view = self._builder.get_object(
            "repoViewScrolledWindow")
        self._repo_view = self._builder.get_object("repoViewVbox")
        self._repo_view.set_name("rigo-view")

        self._notice_scrolled_view = self._builder.get_object(
            "noticeViewScrolledWindow")
        self._notice_view = self._builder.get_object("noticeViewVbox")
        self._notice_view.set_name("rigo-view")

        self._pref_scrolled_view = self._builder.get_object(
            "preferencesViewScrolledWindow")
        self._pref_view = self._builder.get_object("preferencesViewVbox")
        self._pref_view.set_name("rigo-view")

        self._group_scrolled_view = self._builder.get_object(
            "groupViewScrolledWindow")
        self._group_view = self._builder.get_object("groupViewVbox")
        self._group_view.set_name("rigo-view")

        self._search_entry = self._builder.get_object("searchEntry")
        self._search_entry_completion = self._builder.get_object(
            "searchEntryCompletion")
        self._search_entry_store = self._builder.get_object(
            "searchEntryStore")
        self._static_view = self._builder.get_object("staticViewVbox")
        self._notification = self._builder.get_object("notificationBox")
        self._bottom_notification = \
            self._builder.get_object("bottomNotificationBox")
        self._work_view = self._builder.get_object("workViewVbox")
        self._work_view.set_name("rigo-view")

        self._pref_button = self._builder.get_object(
            "prefButton")
        def _pref_button_activate(widget):
            self._change_view_state(
                RigoViewStates.PREFERENCES_VIEW_STATE)
        self._pref_button.connect(
            "clicked", _pref_button_activate)

        # Preferences model, view and controller
        self._pref_store = PreferencesListStore()
        self._view_pref = PreferencesTreeView(
            icons, PreferencesListStore.ICON_SIZE)
        self._pref_scrolled_view.add(self._view_pref)
        def _pref_queue_draw(*args):
            self._view_pref.queue_draw()
        self._pref_store.connect("redraw-request", _pref_queue_draw)
        self._pref_view_c = PreferenceViewController(
            self._pref_store, self._view_pref)

        self._app_view_c = ApplicationViewController(
            self._entropy, self._entropy_ws, self._pref_view_c,
            self._service, self._builder)

        self._view = AppTreeView(
            self._entropy, self._service, self._app_view_c, icons,
            True, AppListStore.ICON_SIZE, store=None)
        self._scrolled_view.add(self._view)
        self._view.set_scrolled_view(self._scrolled_view)

        self._app_store = AppListStore(
            self._entropy, self._entropy_ws,
            self._service, self._view, icons)
        def _queue_draw(*args):
            self._view.queue_draw()
        self._app_store.connect("redraw-request", _queue_draw)

        self._app_view_c.set_store(self._app_store)
        self._app_view_c.connect("application-show",
            self._on_application_show)

        # Configuration file updates model, view and controller
        self._config_store = ConfigUpdatesListStore()
        self._view_config = ConfigUpdatesTreeView(
            icons, ConfigUpdatesListStore.ICON_SIZE)
        self._config_scrolled_view.add(self._view_config)
        def _config_queue_draw(*args):
            self._view_config.queue_draw()
        self._config_store.connect("redraw-request", _config_queue_draw)
        self._config_view_c = ConfigUpdatesViewController(
            self._entropy, self._config_store, self._view_config)
        self._config_view_c.connect(
            "view-cleared", self._on_view_cleared)

        self._service.set_configuration_controller(self._config_view_c)

        # Repository model, view and controller
        self._repo_store = RepositoryListStore()
        self._view_repo = RepositoryTreeView(
            icons, RepositoryListStore.ICON_SIZE)
        self._repo_scrolled_view.add(self._view_repo)
        def _repo_queue_draw(*args):
            self._view_repo.queue_draw()
        self._repo_store.connect("redraw-request", _repo_queue_draw)
        self._repo_view_c = RepositoryViewController(
            self._pref_view_c, self._service, self._repo_store,
            self._view_repo)

        # NoticeBoard model, view and controller
        self._notice_store = NoticeBoardListStore()
        self._view_notice = NoticeBoardTreeView(
            icons, NoticeBoardListStore.ICON_SIZE)
        self._notice_scrolled_view.add(self._view_notice)
        def _notice_queue_draw(*args):
            self._view_notice.queue_draw()
        self._notice_store.connect("redraw-request", _notice_queue_draw)
        self._notice_view_c = NoticeBoardViewController(
            self._notice_store, self._view_notice)

        self._service.set_noticeboard_controller(self._notice_view_c)

        # Group model, view and controller
        self._group_store = GroupListStore()
        self._view_group = GroupTreeView(
            icons, GroupListStore.ICON_SIZE)
        self._group_scrolled_view.add(self._view_group)
        def _group_queue_draw(*args):
            self._view_group.queue_draw()
        self._group_store.connect("redraw-request", _group_queue_draw)
        self._group_view_c = GroupViewController(
            self._service, self._group_store,
            self._view_group, self._pref_view_c)

        self._welcome_box = WelcomeBox()

        settings = Gtk.Settings.get_default()
        settings.set_property("gtk-error-bell", False)
        # wire up the css provider to reconfigure on theme-changes
        self._window.connect("style-updated",
                                 self._on_style_updated,
                                 init_sc_css_provider,
                                 settings,
                                 Gdk.Screen.get_default(),
                                 DATA_DIR)

        # Force the initialization of the css provider asap.
        # This fixes a glitch with GTK 3.10
        init_sc_css_provider(
            self._window,
            settings,
            Gdk.Screen.get_default(),
            DATA_DIR)

        self._nc = UpperNotificationViewController(
            self._entropy, self._entropy_ws, self._notification)
        # Bottom NotificationBox controller.
        # Bottom notifications are only used for
        # providing Activity control to User during
        # the Activity itself.
        self._bottom_nc = BottomNotificationViewController(
            self._window, self._bottom_notification,
            self._pref_button)

        self._avc = ApplicationsViewController(
            self._activity_rwsem,
            self._entropy, self._entropy_ws,
            self._nc, self._bottom_nc, self._service,
            self._pref_view_c, icons, self._not_found_box,
            self._search_entry, self._search_entry_completion,
            self._search_entry_store, 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._service.set_bottom_notification_controller(
            self._bottom_nc)

        self._app_view_c.set_notification_controller(self._nc)
        self._app_view_c.set_applications_controller(self._avc)

        self._config_view_c.set_notification_controller(self._nc)
        self._config_view_c.set_applications_controller(self._avc)

        self._repo_view_c.set_notification_controller(self._nc)
        self._repo_view_c.set_applications_controller(self._avc)

        self._notice_view_c.set_notification_controller(self._nc)
        self._notice_view_c.set_applications_controller(self._avc)

        self._group_view_c.set_applications_controller(self._avc)

        self._service.set_applications_controller(self._avc)
        self._service.set_application_controller(self._app_view_c)
        self._service.set_notification_controller(self._nc)

        self._service.connect("start-working", self._on_start_working)
        self._service.connect("repositories-updated",
                              self._on_repo_updated)
        self._service.connect("applications-managed",
                              self._on_applications_managed)

        self._work_view_c = WorkViewController(
            icons, self._service, self._work_view)
        self._service.set_work_controller(self._work_view_c)

        self._bottom_nc.connect("show-work-view", self._on_show_work_view)
        self._bottom_nc.connect("work-interrupt", self._on_work_interrupt)
Пример #46
0
    def _permissions_setup(self):
        """
        Check execution privileges and spawn the Rigo UI.
        """
        if not entropy.tools.is_user_in_entropy_group():
            # otherwise the lock handling would potentially
            # fail.
            self._show_ok_dialog(
                None,
                escape_markup(_("Not authorized")),
                escape_markup(_("You are not authorized to run Rigo")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        if not self._service.service_available():
            self._show_ok_dialog(
                None,
                escape_markup(_("Rigo")),
                escape_markup(_("RigoDaemon service is not available")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        supported_apis = self._service.supported_apis()
        daemon_api = self._service.api()
        if daemon_api not in supported_apis:
            self._show_ok_dialog(
                None,
                escape_markup(_("Rigo")),
                escape_markup(
                    _("API mismatch, please update Rigo and RigoDaemon")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        lock = EntropyResourcesLock(output=self._entropy)
        # always execute this from the MainThread, since the lock uses TLS
        acquired = lock.try_acquire_shared()
        is_exclusive = False
        if not acquired:
            # check whether RigoDaemon is running in excluive mode
            # and ignore non-atomicity here (failing with error
            # is acceptable)
            if not self._service.exclusive():
                self._show_ok_dialog(
                    None,
                    escape_markup(_("Rigo")),
                    escape_markup(_("Another Application Manager is active")))
                entropy.tools.kill_threads()
                Gtk.main_quit()
                return
            is_exclusive = True
            # otherwise we can go ahead and handle our state later

        # 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 != DaemonActivityStates.AVAILABLE:
            msg = ""
            show_dialog = True

            if activity == DaemonActivityStates.NOT_AVAILABLE:
                msg = _("Background Service is currently not available")

            elif activity == DaemonActivityStates.UPDATING_REPOSITORIES:
                show_dialog = False
                task = ParallelTask(
                    self._service._update_repositories,
                    [], False, master=False)
                task.daemon = True
                task.name = "UpdateRepositoriesUnlocked"
                task.start()

            elif activity == DaemonActivityStates.MANAGING_APPLICATIONS:
                show_dialog = False
                task = ParallelTask(
                    self._service._application_request,
                    None, None, master=False)
                task.daemon = True
                task.name = "ApplicationRequestUnlocked"
                task.start()

            elif activity == DaemonActivityStates.UPGRADING_SYSTEM:
                show_dialog = False
                task = ParallelTask(
                    self._service._upgrade_system,
                    False, master=False)
                task.daemon = True
                task.name = "UpgradeSystemUnlocked"
                task.start()

            elif activity == DaemonActivityStates.INTERNAL_ROUTINES:
                msg = _("Background Service is currently busy")
            else:
                msg = _("Background Service is incompatible with Rigo")

            if show_dialog:
                self._show_ok_dialog(
                    None,
                    escape_markup(_("Rigo")),
                    escape_markup(msg))
                entropy.tools.kill_threads()
                Gtk.main_quit()
                return

        elif is_exclusive:
            msg = _("Background Service is currently unavailable")
            # no lock acquired, cannot continue the initialization
            self._show_ok_dialog(
                None,
                escape_markup(_("Rigo")),
                escape_markup(msg))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        parser = argparse.ArgumentParser(
            description=_("Rigo Application Browser"))
        parser.add_argument(
            "package", nargs='?', 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(
            "--upgrade", help="upgrade the system",
            action="store_true", default=False)
        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._pref_view_c.setup()
        self._group_view_c.setup()
        self._config_view_c.setup()
        self._repo_view_c.setup()
        self._notice_view_c.setup()
        self._app_view_c.setup()
        self._avc.setup()
        self._nc.setup()
        self._work_view_c.setup()
        self._service.setup(acquired)
        self._easter_eggs()
        self._window.show()
        managing = self._start_managing()
        if not managing:
            self._change_view_state(RigoViewStates.GROUPS_VIEW_STATE)
            self._service.hello()
Пример #47
0
    def __init__(self):
        self._current_state_lock = False
        self._current_state = RigoViewStates.STATIC_VIEW_STATE
        self._state_transitions = {
            RigoViewStates.BROWSER_VIEW_STATE: (self._enter_browser_state,
                                                self._exit_browser_state),
            RigoViewStates.STATIC_VIEW_STATE: (self._enter_static_state,
                                               self._exit_static_state),
            RigoViewStates.APPLICATION_VIEW_STATE:
            (self._enter_application_state, self._exit_application_state),
            RigoViewStates.WORK_VIEW_STATE: (self._enter_work_state,
                                             self._exit_work_state),
            RigoViewStates.CONFUPDATES_VIEW_STATE:
            (self._enter_confupdates_state, self._exit_confupdates_state),
            RigoViewStates.NOTICEBOARD_VIEW_STATE:
            (self._enter_noticeboard_state, self._exit_noticeboard_state),
            RigoViewStates.PREFERENCES_VIEW_STATE:
            (self._enter_preferences_state, self._exit_preferences_state),
            RigoViewStates.REPOSITORY_VIEW_STATE:
            (self._enter_repository_state, self._exit_repository_state),
            RigoViewStates.GROUPS_VIEW_STATE: (self._enter_groups_state,
                                               self._exit_groups_state)
        }
        self._state_metadata = {
            RigoViewStates.BROWSER_VIEW_STATE: {
                "title": _("Search"),
            },
            RigoViewStates.STATIC_VIEW_STATE: {
                "title": _("Rigo Application Browser"),
            },
            RigoViewStates.APPLICATION_VIEW_STATE: {
                "title": _("Application"),
            },
            RigoViewStates.WORK_VIEW_STATE: {
                "title": _("Working Hard"),
            },
            RigoViewStates.CONFUPDATES_VIEW_STATE: {
                "title": _("Wake Up"),
            },
            RigoViewStates.NOTICEBOARD_VIEW_STATE: {
                "title": _("Important Stuff"),
            },
            RigoViewStates.PREFERENCES_VIEW_STATE: {
                "title": _("Breaking Stuff"),
            },
            RigoViewStates.REPOSITORY_VIEW_STATE: {
                "title": _("Repository Stuff"),
            },
            RigoViewStates.GROUPS_VIEW_STATE: {
                "title": _("Application Groups"),
            },
        }
        self._state_mutex = Lock()

        icons = get_sc_icon_theme(DATA_DIR)

        self._activity_rwsem = ReadersWritersSemaphore()

        # This relies on the fact that the installed packages repository
        # is lazily loaded (thus, schema update code is).
        self._entropy = Client()
        self._entropy_ws = EntropyWebService(self._entropy)

        preload_task = ParallelTask(self._entropy_ws.preload)
        preload_task.name = "PreloadEntropyWebService"
        preload_task.daemon = True
        preload_task.start()

        self._service = RigoServiceController(self, self._activity_rwsem,
                                              self._entropy, self._entropy_ws)

        app_handler = Rigo.RigoHandler(self, self._service)

        self._builder = Gtk.Builder()
        self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui"))
        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")
        self._scrolled_view = self._builder.get_object(
            "appsViewScrolledWindow")
        self._app_view = self._builder.get_object("appViewScrollWin")
        self._app_view.set_name("rigo-view")
        self._app_view_port = self._builder.get_object("appViewVport")
        self._app_view_port.set_name("rigo-view")
        self._not_found_box = self._builder.get_object("appsViewNotFoundVbox")

        self._config_scrolled_view = self._builder.get_object(
            "configViewScrolledWindow")
        self._config_view = self._builder.get_object("configViewVbox")
        self._config_view.set_name("rigo-view")

        self._repo_scrolled_view = self._builder.get_object(
            "repoViewScrolledWindow")
        self._repo_view = self._builder.get_object("repoViewVbox")
        self._repo_view.set_name("rigo-view")

        self._notice_scrolled_view = self._builder.get_object(
            "noticeViewScrolledWindow")
        self._notice_view = self._builder.get_object("noticeViewVbox")
        self._notice_view.set_name("rigo-view")

        self._pref_scrolled_view = self._builder.get_object(
            "preferencesViewScrolledWindow")
        self._pref_view = self._builder.get_object("preferencesViewVbox")
        self._pref_view.set_name("rigo-view")

        self._group_scrolled_view = self._builder.get_object(
            "groupViewScrolledWindow")
        self._group_view = self._builder.get_object("groupViewVbox")
        self._group_view.set_name("rigo-view")

        self._search_entry = self._builder.get_object("searchEntry")
        self._search_entry_completion = self._builder.get_object(
            "searchEntryCompletion")
        self._search_entry_store = self._builder.get_object("searchEntryStore")
        self._static_view = self._builder.get_object("staticViewVbox")
        self._notification = self._builder.get_object("notificationBox")
        self._bottom_notification = \
            self._builder.get_object("bottomNotificationBox")
        self._work_view = self._builder.get_object("workViewVbox")
        self._work_view.set_name("rigo-view")

        self._pref_button = self._builder.get_object("prefButton")

        def _pref_button_activate(widget):
            self._change_view_state(RigoViewStates.PREFERENCES_VIEW_STATE)

        self._pref_button.connect("clicked", _pref_button_activate)

        # Preferences model, view and controller
        self._pref_store = PreferencesListStore()
        self._view_pref = PreferencesTreeView(icons,
                                              PreferencesListStore.ICON_SIZE)
        self._pref_scrolled_view.add(self._view_pref)

        def _pref_queue_draw(*args):
            self._view_pref.queue_draw()

        self._pref_store.connect("redraw-request", _pref_queue_draw)
        self._pref_view_c = PreferenceViewController(self._pref_store,
                                                     self._view_pref)

        self._app_view_c = ApplicationViewController(self._entropy,
                                                     self._entropy_ws,
                                                     self._pref_view_c,
                                                     self._service,
                                                     self._builder)

        self._view = AppTreeView(self._entropy,
                                 self._service,
                                 self._app_view_c,
                                 icons,
                                 True,
                                 AppListStore.ICON_SIZE,
                                 store=None)
        self._scrolled_view.add(self._view)
        self._view.set_scrolled_view(self._scrolled_view)

        self._app_store = AppListStore(self._entropy, self._entropy_ws,
                                       self._service, self._view, icons)

        def _queue_draw(*args):
            self._view.queue_draw()

        self._app_store.connect("redraw-request", _queue_draw)

        self._app_view_c.set_store(self._app_store)
        self._app_view_c.connect("application-show", self._on_application_show)

        # Configuration file updates model, view and controller
        self._config_store = ConfigUpdatesListStore()
        self._view_config = ConfigUpdatesTreeView(
            icons, ConfigUpdatesListStore.ICON_SIZE)
        self._config_scrolled_view.add(self._view_config)

        def _config_queue_draw(*args):
            self._view_config.queue_draw()

        self._config_store.connect("redraw-request", _config_queue_draw)
        self._config_view_c = ConfigUpdatesViewController(
            self._entropy, self._config_store, self._view_config)
        self._config_view_c.connect("view-cleared", self._on_view_cleared)

        self._service.set_configuration_controller(self._config_view_c)

        # Repository model, view and controller
        self._repo_store = RepositoryListStore()
        self._view_repo = RepositoryTreeView(icons,
                                             RepositoryListStore.ICON_SIZE)
        self._repo_scrolled_view.add(self._view_repo)

        def _repo_queue_draw(*args):
            self._view_repo.queue_draw()

        self._repo_store.connect("redraw-request", _repo_queue_draw)
        self._repo_view_c = RepositoryViewController(self._pref_view_c,
                                                     self._service,
                                                     self._repo_store,
                                                     self._view_repo)

        # NoticeBoard model, view and controller
        self._notice_store = NoticeBoardListStore()
        self._view_notice = NoticeBoardTreeView(icons,
                                                NoticeBoardListStore.ICON_SIZE)
        self._notice_scrolled_view.add(self._view_notice)

        def _notice_queue_draw(*args):
            self._view_notice.queue_draw()

        self._notice_store.connect("redraw-request", _notice_queue_draw)
        self._notice_view_c = NoticeBoardViewController(
            self._notice_store, self._view_notice)

        self._service.set_noticeboard_controller(self._notice_view_c)

        # Group model, view and controller
        self._group_store = GroupListStore()
        self._view_group = GroupTreeView(icons, GroupListStore.ICON_SIZE)
        self._group_scrolled_view.add(self._view_group)

        def _group_queue_draw(*args):
            self._view_group.queue_draw()

        self._group_store.connect("redraw-request", _group_queue_draw)
        self._group_view_c = GroupViewController(self._service,
                                                 self._group_store,
                                                 self._view_group,
                                                 self._pref_view_c)

        self._welcome_box = WelcomeBox()

        settings = Gtk.Settings.get_default()
        settings.set_property("gtk-error-bell", False)
        # wire up the css provider to reconfigure on theme-changes
        self._window.connect("style-updated", self._on_style_updated,
                             init_sc_css_provider, settings,
                             Gdk.Screen.get_default(), DATA_DIR)

        # Force the initialization of the css provider asap.
        # This fixes a glitch with GTK 3.10
        init_sc_css_provider(self._window, settings, Gdk.Screen.get_default(),
                             DATA_DIR)

        self._nc = UpperNotificationViewController(self._entropy,
                                                   self._entropy_ws,
                                                   self._notification)
        # Bottom NotificationBox controller.
        # Bottom notifications are only used for
        # providing Activity control to User during
        # the Activity itself.
        self._bottom_nc = BottomNotificationViewController(
            self._window, self._bottom_notification, self._pref_button)

        self._avc = ApplicationsViewController(
            self._activity_rwsem, self._entropy, self._entropy_ws, self._nc,
            self._bottom_nc, self._service, self._pref_view_c, icons,
            self._not_found_box, self._search_entry,
            self._search_entry_completion, self._search_entry_store,
            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._service.set_bottom_notification_controller(self._bottom_nc)

        self._app_view_c.set_notification_controller(self._nc)
        self._app_view_c.set_applications_controller(self._avc)

        self._config_view_c.set_notification_controller(self._nc)
        self._config_view_c.set_applications_controller(self._avc)

        self._repo_view_c.set_notification_controller(self._nc)
        self._repo_view_c.set_applications_controller(self._avc)

        self._notice_view_c.set_notification_controller(self._nc)
        self._notice_view_c.set_applications_controller(self._avc)

        self._group_view_c.set_applications_controller(self._avc)

        self._service.set_applications_controller(self._avc)
        self._service.set_application_controller(self._app_view_c)
        self._service.set_notification_controller(self._nc)

        self._service.connect("start-working", self._on_start_working)
        self._service.connect("repositories-updated", self._on_repo_updated)
        self._service.connect("applications-managed",
                              self._on_applications_managed)

        self._work_view_c = WorkViewController(icons, self._service,
                                               self._work_view)
        self._service.set_work_controller(self._work_view_c)

        self._bottom_nc.connect("show-work-view", self._on_show_work_view)
        self._bottom_nc.connect("work-interrupt", self._on_work_interrupt)
Пример #48
0
    def _install_action(self, entropy_client, deps, recursive,
                        pretend, ask, verbose, quiet, empty,
                        config_files, deep, fetch, bdeps,
                        onlydeps, relaxed, multifetch, packages,
                        package_matches=None):
        """
        Solo Install action implementation.

        Packages passed in the packages argument (as opposed to
        package_matches) will be marked as installed by user.
        """
        inst_repo = entropy_client.installed_repository()
        action_factory = entropy_client.PackageActionFactory()
        packages_by_user = set()

        with inst_repo.shared():

            self._advise_repository_update(entropy_client)
            if self._check_critical_updates:
                self._advise_packages_update(entropy_client)

            if package_matches is None:
                packages = self._match_packages_for_installation(
                    entropy_client, onlydeps, packages)
                if not packages:
                    return 1, False
                packages_by_user = set(packages)
            else:
                packages = package_matches

            run_queue, removal_queue = self._generate_install_queue(
                entropy_client, packages, deps, empty, deep, relaxed,
                onlydeps, bdeps, recursive)
            if (run_queue is None) or (removal_queue is None):
                return 1, False
            elif not (run_queue or removal_queue):
                entropy_client.output(
                    "%s." % (blue(_("Nothing to do")),),
                    level="warning", header=darkgreen(" @@ "))
                return 0, True

            self._show_install_queue(
                entropy_client, inst_repo,
                run_queue, removal_queue, ask, pretend, quiet, verbose)

            installed_pkg_sources = self._get_installed_packages_sources(
                entropy_client, inst_repo, run_queue)

        if ask:
            rc = entropy_client.ask_question(
                "     %s" % (_("Would you like to continue ?"),))
            if rc == _("No"):
                return 1, False

        if pretend:
            return 0, True # yes, tell user

        if self._interactive:
            exit_st = self._accept_license(
                entropy_client, inst_repo, run_queue)
            if exit_st != 0:
                return 1, False

        ugc_thread = None
        down_data = {}
        exit_st = self._download_packages(
            entropy_client, run_queue, down_data, multifetch)
        if exit_st == 0:
            ugc_thread = ParallelTask(
                self._signal_ugc, entropy_client, down_data)
            ugc_thread.name = "UgcThread"
            ugc_thread.start()

        elif exit_st != 0:
            return 1, False

        # is --fetch on? then quit.
        if fetch:
            if ugc_thread is not None:
                ugc_thread.join()
            entropy_client.output(
                "%s." % (
                    blue(_("Download complete")),),
                header=darkred(" @@ "))
            return 0, False

        notification_lock = UpdatesNotificationResourceLock(
            output=entropy_client)
        total = len(run_queue)

        notif_acquired = False
        try:
            # this is a best effort, we will not sleep if the lock
            # is not acquired because we may get blocked for an eternity
            # (well, for a very long time) in this scenario:
            # 1. RigoDaemon is running some action queue
            # 2. Another thread in RigoDaemon is stuck on the activity
            #    mutex with the notification lock held.
            # 3. We cannot move on here because of 2.
            # Nothing bad will happen if we just ignore the acquisition
            # state.
            notif_acquired = notification_lock.try_acquire_shared()

            metaopts = {
                'removeconfig': config_files,
                # This can be used by PackageAction based classes
                # to know what's the overall package schedule for
                # both upgrade and install actions. This way, we
                # can better handle conflicts.
                'install_queue' : run_queue,
            }

            for count, pkg_match in enumerate(run_queue, 1):

                source_id = installed_pkg_sources.get(pkg_match, None)

                if not onlydeps and pkg_match in packages_by_user:
                    metaopts['install_source'] = \
                        etpConst['install_sources']['user']
                elif source_id is not None:
                    # Retain the information.
                    # Install action can upgrade packages, their source
                    # should not be changed to automatic_dependency.
                    metaopts['install_source'] = source_id
                else:
                    metaopts['install_source'] = \
                        etpConst['install_sources']['automatic_dependency']

                package_id, repository_id = pkg_match
                atom = entropy_client.open_repository(
                    repository_id).retrieveAtom(package_id)

                pkg = None
                try:
                    pkg = action_factory.get(
                        action_factory.INSTALL_ACTION,
                        pkg_match, opts=metaopts)

                    xterm_header = "equo (%s) :: %d of %d ::" % (
                        _("install"), count, total)

                    pkg.set_xterm_header(xterm_header)

                    entropy_client.output(
                        purple(atom),
                        count=(count, total),
                        header=darkgreen(" +++ ") + ">>> ")

                    exit_st = pkg.start()
                    if exit_st != 0:
                        if ugc_thread is not None:
                            ugc_thread.join()
                        return 1, True

                finally:
                    if pkg is not None:
                        pkg.finalize()

        finally:
            if notif_acquired:
                notification_lock.release()

        if ugc_thread is not None:
            ugc_thread.join()

        entropy_client.output(
            "%s." % (
                blue(_("Installation complete")),),
            header=darkred(" @@ "))
        return 0, True
Пример #49
0
    def download(self):
        """
        Start downloading URL given at construction time.

        @return: dict containing UrlFetcher.get_id() as key
            and download status as value, which can be either one of:
            UrlFetcher.GENERIC_FETCH_ERROR means error.
            UrlFetcher.TIMEOUT_FETCH_ERROR means timeout error.
            UrlFetcher.GENERIC_FETCH_WARN means warning,
                downloaded fine but unable to calculate the md5 hash.
        @rtype: dict
        """
        self._init_vars()

        speed_limit = 0
        dsl = self.__system_settings['repositories']['transfer_limit']
        if isinstance(dsl, int) and self._url_path_list:
            speed_limit = dsl/len(self._url_path_list)

        class MyFetcher(self.__url_fetcher):

            def __init__(self, klass, multiple, *args, **kwargs):
                klass.__init__(self, *args, **kwargs)
                self.__multiple_fetcher = multiple

            def update(self):
                return self.__multiple_fetcher.update()

            def _push_progress_to_output(self, *args):
                return

            def handle_statistics(self, *args, **kwargs):
                return self.__multiple_fetcher.handle_statistics(*args,
                    **kwargs)

        th_id = 0
        for url, path_to_save in self._url_path_list:
            th_id += 1
            downloader = MyFetcher(
                self.__url_fetcher, self, url, path_to_save,
                checksum = self.__checksum, show_speed = self.__show_speed,
                resume = self.__resume,
                abort_check_func = self.__abort_check_func,
                disallow_redirect = self.__disallow_redirect,
                thread_stop_func = self.__handle_threads_stop,
                speed_limit = speed_limit,
                timeout = self.__timeout,
                download_context_func = self.__download_context_func,
                pre_download_hook = self.__pre_download_hook,
                post_download_hook = self.__post_download_hook
            )
            downloader.set_id(th_id)

            def do_download(ds, dth_id, downloader):
                ds[dth_id] = downloader.download()

            t = ParallelTask(do_download, self.__download_statuses, th_id,
                downloader)
            t.name = "UrlFetcher{%s}" % (url,)
            t.daemon = True
            self.__thread_pool[th_id] = t
            t.start()

        self._push_progress_to_output(force = True)
        self.__show_download_files_info()
        self.__show_progress = True

        # wait until all the threads are done
        # do not block the main thread
        # but rather use timeout and check
        try:
            while True:
                _all_joined = True
                for th_id, th in self.__thread_pool.items():
                    th.join(0.3)
                    if th.is_alive():
                        # timeout then
                        _all_joined = False
                if _all_joined:
                    break
        except (SystemExit, KeyboardInterrupt):
            self.__stop_threads = True
            raise

        if len(self._url_path_list) != len(self.__download_statuses):
            # there has been an error (exception)
            # complete download_statuses with error info
            for th_id, th in self.__thread_pool.items():
                if th_id not in self.__download_statuses:
                    self.__download_statuses[th_id] = \
                        UrlFetcher.GENERIC_FETCH_ERROR

        return self.__download_statuses
Пример #50
0
 def load_url(self, url):
     task = ParallelTask(
         subprocess.call, ['xdg-open', url])
     task.daemon = True
     task.start()
Пример #51
0
 def load_url(self, url):
     task = ParallelTask(
         subprocess.call, ['xdg-open', url])
     task.daemon = True
     task.start()
Пример #52
0
    class MetadataDownloader(GObject.Object):
        """
        Automated Application comments downloader.
        """

        def __init__(self, app, avc, callback, app_downloader_method):
            self._app = app
            self._avc = avc
            self._offset = 0
            self._callback = callback
            self._task = ParallelTask(self._download)
            self._app_downloader = app_downloader_method

        def start(self):
            """
            Start downloading comments and send them to callback.
            Loop over until we have more of them to download.
            """
            self._offset = 0
            self._task.start()

        def _download_callback(self, document_list):
            """
            Callback called by download_<something>() once data
            is arrived from web service.
            document_list can be None!
            """
            has_more = 0
            if document_list is not None:
                has_more = document_list.has_more()
            # stash more data?
            if has_more and (document_list is not None):
                self._offset += len(document_list)
                # download() will be called externally

            if const_debug_enabled():
                const_debug_write(
                    __name__,
                    "MetadataDownloader._download_callback: %s, more: %s" % (
                        document_list, has_more))
                if document_list is not None:
                    const_debug_write(
                        __name__,
                        "MetadataDownloader._download_callback: "
                            "has_more: %s, offset: %s" % (
                            document_list.has_more(),
                            document_list.offset()))

            self._callback(self, self._app, document_list, has_more)

        def reset_offset(self):
            """
            Reset Metadata download offset to 0.
            """
            self._offset = 0

        def get_offset(self):
            """
            Get current Metadata download offset.
            """
            return self._offset

        def enqueue_download(self):
            """
            Enqueue a new download, starting from current offset
            """
            self._task = ParallelTask(self._download)
            self._task.start()

        def _download(self):
            """
            Thread body of the initial Metadata downloader.
            """
            self._app_downloader(self._download_callback,
                                 offset=self._offset)
Пример #53
0
    def download(self):
        """
        Start downloading URL given at construction time.

        @return: dict containing UrlFetcher.get_id() as key
            and download status as value, which can be either one of:
            UrlFetcher.GENERIC_FETCH_ERROR means error.
            UrlFetcher.TIMEOUT_FETCH_ERROR means timeout error.
            UrlFetcher.GENERIC_FETCH_WARN means warning,
                downloaded fine but unable to calculate the md5 hash.
        @rtype: dict
        """
        self._init_vars()

        speed_limit = 0
        dsl = self.__system_settings['repositories']['transfer_limit']
        if isinstance(dsl, int) and self._url_path_list:
            speed_limit = dsl/len(self._url_path_list)

        class MyFetcher(self.__url_fetcher):

            def __init__(self, klass, multiple, *args, **kwargs):
                klass.__init__(self, *args, **kwargs)
                self.__multiple_fetcher = multiple

            def update(self):
                return self.__multiple_fetcher.update()

            def _push_progress_to_output(self, *args):
                return

            def handle_statistics(self, *args, **kwargs):
                return self.__multiple_fetcher.handle_statistics(*args,
                    **kwargs)

        th_id = 0
        for url, path_to_save in self._url_path_list:
            th_id += 1
            downloader = MyFetcher(
                self.__url_fetcher, self, url, path_to_save,
                checksum = self.__checksum, show_speed = self.__show_speed,
                resume = self.__resume,
                abort_check_func = self.__abort_check_func,
                disallow_redirect = self.__disallow_redirect,
                thread_stop_func = self.__handle_threads_stop,
                speed_limit = speed_limit,
                timeout = self.__timeout,
                download_context_func = self.__download_context_func,
                pre_download_hook = self.__pre_download_hook,
                post_download_hook = self.__post_download_hook
            )
            downloader.set_id(th_id)

            def do_download(ds, dth_id, downloader):
                ds[dth_id] = downloader.download()

            t = ParallelTask(do_download, self.__download_statuses, th_id,
                downloader)
            t.name = "UrlFetcher{%s}" % (url,)
            t.daemon = True
            self.__thread_pool[th_id] = t
            t.start()

        self._push_progress_to_output(force = True)
        self.__show_download_files_info()
        self.__show_progress = True

        # wait until all the threads are done
        # do not block the main thread
        # but rather use timeout and check
        try:
            while True:
                _all_joined = True
                for th_id, th in self.__thread_pool.items():
                    th.join(0.3)
                    if th.is_alive():
                        # timeout then
                        _all_joined = False
                if _all_joined:
                    break
        except (SystemExit, KeyboardInterrupt):
            self.__stop_threads = True
            raise

        if len(self._url_path_list) != len(self.__download_statuses):
            # there has been an error (exception)
            # complete download_statuses with error info
            for th_id, th in self.__thread_pool.items():
                if th_id not in self.__download_statuses:
                    self.__download_statuses[th_id] = \
                        UrlFetcher.GENERIC_FETCH_ERROR

        return self.__download_statuses
Пример #54
0
    def _permissions_setup(self):
        """
        Check execution privileges and spawn the Rigo UI.
        """
        if not entropy.tools.is_user_in_entropy_group():
            # otherwise the lock handling would potentially
            # fail.
            self._show_ok_dialog(
                None, escape_markup(_("Not authorized")),
                escape_markup(_("You are not authorized to run Rigo")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        if not self._service.service_available():
            self._show_ok_dialog(
                None, escape_markup(_("Rigo")),
                escape_markup(_("RigoDaemon service is not available")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        supported_apis = self._service.supported_apis()
        daemon_api = self._service.api()
        if daemon_api not in supported_apis:
            self._show_ok_dialog(
                None, escape_markup(_("Rigo")),
                escape_markup(
                    _("API mismatch, please update Rigo and RigoDaemon")))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        lock = EntropyResourcesLock(output=self._entropy)
        # always execute this from the MainThread, since the lock uses TLS
        acquired = lock.try_acquire_shared()
        is_exclusive = False
        if not acquired:
            # check whether RigoDaemon is running in excluive mode
            # and ignore non-atomicity here (failing with error
            # is acceptable)
            if not self._service.exclusive():
                self._show_ok_dialog(
                    None, escape_markup(_("Rigo")),
                    escape_markup(_("Another Application Manager is active")))
                entropy.tools.kill_threads()
                Gtk.main_quit()
                return
            is_exclusive = True
            # otherwise we can go ahead and handle our state later

        # 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 != DaemonActivityStates.AVAILABLE:
            msg = ""
            show_dialog = True

            if activity == DaemonActivityStates.NOT_AVAILABLE:
                msg = _("Background Service is currently not available")

            elif activity == DaemonActivityStates.UPDATING_REPOSITORIES:
                show_dialog = False
                task = ParallelTask(self._service._update_repositories, [],
                                    False,
                                    master=False)
                task.daemon = True
                task.name = "UpdateRepositoriesUnlocked"
                task.start()

            elif activity == DaemonActivityStates.MANAGING_APPLICATIONS:
                show_dialog = False
                task = ParallelTask(self._service._application_request,
                                    None,
                                    None,
                                    master=False)
                task.daemon = True
                task.name = "ApplicationRequestUnlocked"
                task.start()

            elif activity == DaemonActivityStates.UPGRADING_SYSTEM:
                show_dialog = False
                task = ParallelTask(self._service._upgrade_system,
                                    False,
                                    master=False)
                task.daemon = True
                task.name = "UpgradeSystemUnlocked"
                task.start()

            elif activity == DaemonActivityStates.INTERNAL_ROUTINES:
                msg = _("Background Service is currently busy")
            else:
                msg = _("Background Service is incompatible with Rigo")

            if show_dialog:
                self._show_ok_dialog(None, escape_markup(_("Rigo")),
                                     escape_markup(msg))
                entropy.tools.kill_threads()
                Gtk.main_quit()
                return

        elif is_exclusive:
            msg = _("Background Service is currently unavailable")
            # no lock acquired, cannot continue the initialization
            self._show_ok_dialog(None, escape_markup(_("Rigo")),
                                 escape_markup(msg))
            entropy.tools.kill_threads()
            Gtk.main_quit()
            return

        parser = argparse.ArgumentParser(
            description=_("Rigo Application Browser"))
        parser.add_argument("package",
                            nargs='?',
                            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("--upgrade",
                            help="upgrade the system",
                            action="store_true",
                            default=False)
        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._pref_view_c.setup()
        self._group_view_c.setup()
        self._config_view_c.setup()
        self._repo_view_c.setup()
        self._notice_view_c.setup()
        self._app_view_c.setup()
        self._avc.setup()
        self._nc.setup()
        self._work_view_c.setup()
        self._service.setup(acquired)
        self._easter_eggs()
        self._window.show()
        managing = self._start_managing()
        if not managing:
            self._change_view_state(RigoViewStates.GROUPS_VIEW_STATE)
            self._service.hello()
Пример #55
0
    class MetadataDownloader(GObject.Object):
        """
        Automated Application comments downloader.
        """
        def __init__(self, app, avc, callback, app_downloader_method):
            self._app = app
            self._avc = avc
            self._offset = 0
            self._callback = callback
            self._task = ParallelTask(self._download)
            self._app_downloader = app_downloader_method

        def start(self):
            """
            Start downloading comments and send them to callback.
            Loop over until we have more of them to download.
            """
            self._offset = 0
            self._task.start()

        def _download_callback(self, document_list):
            """
            Callback called by download_<something>() once data
            is arrived from web service.
            document_list can be None!
            """
            has_more = 0
            if document_list is not None:
                has_more = document_list.has_more()
            # stash more data?
            if has_more and (document_list is not None):
                self._offset += len(document_list)
                # download() will be called externally

            if const_debug_enabled():
                const_debug_write(
                    __name__,
                    "MetadataDownloader._download_callback: %s, more: %s" %
                    (document_list, has_more))
                if document_list is not None:
                    const_debug_write(
                        __name__, "MetadataDownloader._download_callback: "
                        "has_more: %s, offset: %s" %
                        (document_list.has_more(), document_list.offset()))

            self._callback(self, self._app, document_list, has_more)

        def reset_offset(self):
            """
            Reset Metadata download offset to 0.
            """
            self._offset = 0

        def get_offset(self):
            """
            Get current Metadata download offset.
            """
            return self._offset

        def enqueue_download(self):
            """
            Enqueue a new download, starting from current offset
            """
            self._task = ParallelTask(self._download)
            self._task.start()

        def _download(self):
            """
            Thread body of the initial Metadata downloader.
            """
            self._app_downloader(self._download_callback, offset=self._offset)