#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2012 Canonical # # Authors: # Michael Vogt # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; version 3. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from gi.repository import GObject import logging import hashlib import softwarecenter.paths from .spawn_helper import SpawnHelper from softwarecenter.config import get_config from softwarecenter.db.utils import get_installed_apps_list from softwarecenter.utils import get_recommender_uuid LOG = logging.getLogger(__name__) class RecommenderAgent(GObject.GObject): __gsignals__ = { "server-status": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "profile": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "submit-profile-finished": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "submit-anon-profile-finished": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "recommend-me": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "recommend-app": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "recommend-all-apps": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "recommend-top": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "submit-implicit-feedback-finished": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "error": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (str,), ), } def __init__(self, xid=None): GObject.GObject.__init__(self) self.xid = xid self.config = get_config() def query_server_status(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_server_status_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "server_status") def _calc_profile_id(self, profile): """ Return a profile id (md5 hash of a profile) for the given profile """ return hashlib.md5(str(profile)).hexdigest() @property def opt_in_requested(self): return self.config.recommender_opt_in_requested @property def recommender_uuid(self): return self.config.recommender_uuid @property def recommender_profile_id(self): return self.config.recommender_profile_id def _set_recommender_profile_id(self, profile_id): self.config.recommender_profile_id = profile_id def _set_recommender_uuid(self, uuid): self.config.recommender_uuid = uuid def recommender_opt_in_requested(self, opt_in_requested): self.config.recommender_opt_in_requested = opt_in_requested def post_submit_profile(self, db): """ This will post the user's profile to the recommender server and also generate the UUID for the user if that is not there yet """ recommender_uuid = self.recommender_uuid if not recommender_uuid: # generate a new uuid, but do not save it yet, this will # be done later in _on_submit_profile_data recommender_uuid = get_recommender_uuid() installed_pkglist = [app.pkgname for app in get_installed_apps_list(db)] profile = self._generate_submit_profile_data(recommender_uuid, installed_pkglist) # compare profiles to see if there has been a change, and if there # has, do the profile update current_recommender_profile_id = self._calc_profile_id(profile) if current_recommender_profile_id != self.recommender_profile_id: LOG.info("Submitting recommendations profile to the server") self._set_recommender_profile_id(current_recommender_profile_id) # build the command and upload the profile spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_profile_data, recommender_uuid) spawner.connect( "error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "submit_profile", data=profile) def post_submit_anon_profile(self, uuid, installed_packages, extra): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_anon_profile_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "submit_anon_profile", uuid=uuid, installed_packages=installed_packages, extra=extra) def query_profile(self, pkgnames): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_profile_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "profile", pkgnames=pkgnames) def query_recommend_me(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_recommend_me_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_me", uuid=self.recommender_uuid) def query_recommend_app(self, pkgname): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_app_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_app", pkgname=pkgname) def query_recommend_all_apps(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_all_apps_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_all_apps") def query_recommend_top(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_top_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_top") def post_implicit_feedback(self, pkgname, action): # build the command LOG.debug("called post_implicit_feedback with %s, '%s'" % (pkgname, action)) spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_implicit_feedback_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "implicit_feedback", pkgname=pkgname, action=action) def is_opted_in(self): """ Return True is the user is currently opted-in to the recommender service """ if self.recommender_uuid or self.opt_in_requested: return True else: return False def opt_out(self): self.config.recommender_uuid = "" self.config.recommender_profile_id = "" self.config.recommender_opt_in_requested = False def _on_server_status_data(self, spawner, piston_server_status): self.emit("server-status", piston_server_status) def _on_profile_data(self, spawner, piston_profile): self.emit("profile", piston_profile) def _on_submit_profile_data(self, spawner, piston_submit_profile, recommender_uuid): self._set_recommender_uuid(recommender_uuid) self.emit("submit-profile-finished", piston_submit_profile) def _on_submit_anon_profile_data(self, spawner, piston_submit_anon_profile): self.emit("submit-anon-profile-finished", piston_submit_anon_profile) def _on_recommend_me_data(self, spawner, piston_me_apps): self.emit("recommend-me", piston_me_apps) def _on_recommend_app_data(self, spawner, piston_app): self.emit("recommend-app", piston_app) def _on_recommend_all_apps_data(self, spawner, piston_all_apps): self.emit("recommend-all-apps", piston_all_apps) def _on_recommend_top_data(self, spawner, piston_top_apps): self.emit("recommend-top", piston_top_apps) def _on_submit_implicit_feedback_data(self, spawner, piston_submit_implicit_feedback): self.emit("submit-implicit-feedback-finished", piston_submit_implicit_feedback) def _generate_submit_profile_data(self, recommender_uuid, package_list): submit_profile_data = [{ 'uuid': recommender_uuid, 'package_list': package_list }] return submit_profile_data if __name__ == "__main__": from gi.repository import Gtk def _recommend_top(agent, top_apps): print ("_recommend_top: %s" % top_apps) def _recommend_me(agent, top_apps): print ("_recommend_me: %s" % top_apps) def _error(agent, msg): print ("got a error: %s" % msg) Gtk.main_quit() # test specific stuff logging.basicConfig() softwarecenter.paths.datadir = "./data" agent = RecommenderAgent() agent.connect("recommend-top", _recommend_top) agent.connect("recommend-me", _recommend_me) agent.connect("error", _error) agent.query_recommend_top() agent.query_recommend_me() Gtk.main()