~ubuntu-branches/ubuntu/raring/software-center/raring-proposed

« back to all changes in this revision

Viewing changes to softwarecenter/ui/gtk3/widgets/recommendations.py

  • Committer: Package Import Robot
  • Author(s): Michael Vogt
  • Date: 2012-10-11 15:33:05 UTC
  • mfrom: (195.1.18 quantal)
  • Revision ID: package-import@ubuntu.com-20121011153305-fm5ln7if3rpzts4n
Tags: 5.4.1.1
* lp:~mvo/software-center/reinstall-previous-purchase-token-fix:
  - fix reinstall previous purchases that have a system-wide
    license key LP: #1065481
* lp:~mvo/software-center/lp1060106:
  - Add missing gettext init for utils/update-software-center-agent
    (LP: #1060106)

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
from softwarecenter.ui.gtk3.em import StockEms
26
26
from softwarecenter.ui.gtk3.widgets.containers import (FramedHeaderBox,
27
 
                                                       FlowableGrid)
 
27
                                                       TileGrid)
28
28
from softwarecenter.ui.gtk3.utils import get_parent_xid
29
29
from softwarecenter.db.categories import (RecommendedForYouCategory,
30
30
                                          AppRecommendationsCategory)
 
31
from softwarecenter.backend.installbackend import get_install_backend
31
32
from softwarecenter.backend.recagent import RecommenderAgent
32
 
from softwarecenter.backend.login_sso import get_sso_backend
 
33
from softwarecenter.backend.login import get_login_backend
33
34
from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend
34
 
from softwarecenter.enums import SOFTWARE_CENTER_NAME_KEYRING
 
35
from softwarecenter.enums import (
 
36
    LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT,
 
37
    DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT,
 
38
    SOFTWARE_CENTER_NAME_KEYRING,
 
39
    RecommenderFeedbackActions,
 
40
    TransactionTypes,
 
41
    )
35
42
from softwarecenter.utils import utf8
36
43
from softwarecenter.netstatus import network_state_is_connected
37
44
 
50
57
                                 ),
51
58
        }
52
59
 
53
 
    def __init__(self, catview):
 
60
    def __init__(self):
54
61
        FramedHeaderBox.__init__(self)
55
 
        # FIXME: we only need the catview for "add_titles_to_flowgrid"
56
 
        #        and "on_category_clicked" so we should be able to
57
 
        #        extract this to a "leaner" widget
58
 
        self.catview = catview
59
 
        self.catview.connect(
60
 
                    "application-activated", self._on_application_activated)
61
62
        self.recommender_agent = RecommenderAgent()
 
63
        # keep track of applications that have been viewed via a
 
64
        # recommendation so that we can detect when a recommended app
 
65
        # has been installed
 
66
        self.recommended_apps_viewed = set()
 
67
        self.backend = get_install_backend()
 
68
        self.backend.connect("transaction-started",
 
69
                             self._on_transaction_started)
 
70
        self.backend.connect("transaction-finished",
 
71
                             self._on_transaction_finished)
62
72
 
63
 
    def _on_application_activated(self, catview, app):
 
73
    def _on_application_activated(self, tile, app):
64
74
        self.emit("application-activated", app)
 
75
        # we only report on items if the user has opted-in to the
 
76
        # recommendations service
 
77
        if self.recommender_agent.is_opted_in():
 
78
            self.recommended_apps_viewed.add(app.pkgname)
 
79
            if network_state_is_connected():
 
80
                # let the recommendations service know that a
 
81
                # recommended item has been viewed (if it is
 
82
                # subsequently installed we will send an additional
 
83
                # signal to indicate that, in on_transaction_finished)
 
84
                # (there is no need to monitor for an error, etc., for this)
 
85
                self.recommender_agent.post_implicit_feedback(
 
86
                        app.pkgname,
 
87
                        RecommenderFeedbackActions.VIEWED)
 
88
 
 
89
    def _on_transaction_started(self, backend, pkgname, appname, trans_id,
 
90
                                trans_type):
 
91
        if (trans_type != TransactionTypes.INSTALL and
 
92
            pkgname in self.recommended_apps_viewed):
 
93
            # if the transaction is not an installation we don't want to
 
94
            # track it as a recommended item
 
95
            self.recommended_apps_viewed.remove(pkgname)
 
96
 
 
97
    def _on_transaction_finished(self, backend, result):
 
98
        if result.pkgname in self.recommended_apps_viewed:
 
99
            self.recommended_apps_viewed.remove(result.pkgname)
 
100
            if network_state_is_connected():
 
101
                # let the recommendations service know that a
 
102
                # recommended item has been successfully installed
 
103
                # (there is no need to monitor for an error, etc., for this)
 
104
                self.recommender_agent.post_implicit_feedback(
 
105
                        result.pkgname,
 
106
                        RecommenderFeedbackActions.INSTALLED)
65
107
 
66
108
    def _on_recommender_agent_error(self, agent, msg):
67
109
        LOG.warn("Error while accessing the recommender agent: %s" % msg)
68
 
        # TODO: temporary, instead we will display cached recommendations here
69
 
        self._hide_recommended_for_you_panel()
70
 
 
71
 
    def _hide_recommended_for_you_panel(self):
72
 
        # and hide the pane
 
110
        # this can happen if:
 
111
        #  - there is a real error from the agent
 
112
        #  - no cached data is available
 
113
        # hide the panel on an error
73
114
        self.hide()
74
115
 
75
116
 
78
119
    Panel for use in the category view that displays recommended apps for
79
120
    the given category
80
121
    """
81
 
    def __init__(self, catview, subcategory):
82
 
        RecommendationsPanel.__init__(self, catview)
 
122
 
 
123
    __gsignals__ = {
 
124
        "more-button-clicked": (GObject.SignalFlags.RUN_LAST,
 
125
                                None,
 
126
                                (GObject.TYPE_PYOBJECT, ),
 
127
                               ),
 
128
        }
 
129
 
 
130
    def __init__(self, db, properties_helper, subcategory):
 
131
        RecommendationsPanel.__init__(self)
 
132
        self.db = db
 
133
        self.properties_helper = properties_helper
83
134
        self.subcategory = subcategory
84
135
        if self.subcategory:
85
136
            self.set_header_label(GObject.markup_escape_text(utf8(
88
139
        if self.recommender_agent.is_opted_in():
89
140
            self._update_recommended_for_you_content()
90
141
        else:
91
 
            self._hide_recommended_for_you_panel()
 
142
            self.hide()
92
143
 
93
144
    def _update_recommended_for_you_content(self):
94
145
        # destroy the old content to ensure we don't see it twice
95
146
        if self.recommended_for_you_content:
96
147
            self.recommended_for_you_content.destroy()
97
148
        # add the new stuff
98
 
        self.recommended_for_you_content = FlowableGrid()
 
149
        self.recommended_for_you_content = TileGrid()
 
150
        self.recommended_for_you_content.connect(
 
151
                    "application-activated", self._on_application_activated)
99
152
        self.add(self.recommended_for_you_content)
100
153
        self.spinner_notebook.show_spinner(_(u"Receiving recommendations…"))
101
154
        # get the recommendations from the recommender agent
102
155
        self.recommended_for_you_cat = RecommendedForYouCategory(
 
156
                                            self.db,
103
157
                                            subcategory=self.subcategory)
104
158
        self.recommended_for_you_cat.connect(
105
159
                                    'needs-refresh',
109
163
 
110
164
    def _on_recommended_for_you_agent_refresh(self, cat):
111
165
        self.header_implements_more_button()
112
 
        docs = cat.get_documents(self.catview.db)
 
166
        self.more.connect("clicked",
 
167
                          self._on_more_button_clicked,
 
168
                          self.recommended_for_you_cat)
 
169
        docs = cat.get_documents(self.db)
113
170
        # display the recommendedations
114
171
        if len(docs) > 0:
115
 
            self.catview._add_tiles_to_flowgrid(docs,
116
 
                                        self.recommended_for_you_content, 12)
 
172
            self.recommended_for_you_content.add_tiles(
 
173
                    self.properties_helper,
 
174
                    docs,
 
175
                    LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT)
117
176
            self.recommended_for_you_content.show_all()
118
177
            self.spinner_notebook.hide_spinner()
119
 
            self.more.connect('clicked',
120
 
                              self.catview.on_category_clicked,
121
 
                              cat)
122
178
            self.header.queue_draw()
123
179
            self.show_all()
124
180
        else:
125
181
            # hide the panel if we have no recommendations to show
126
 
            self._hide_recommended_for_you_panel()
 
182
            self.hide()
 
183
 
 
184
    def _on_more_button_clicked(self, btn, category):
 
185
        self.emit("more-button-clicked", category)
127
186
 
128
187
 
129
188
class RecommendationsPanelLobby(RecommendationsPanelCategory):
144
203
        }
145
204
 
146
205
    TURN_ON_RECOMMENDATIONS_TEXT = _(u"Turn On Recommendations")
147
 
    # FIXME: This will be the default text once LP: #986437 is approved and
148
 
    #        ready to be merged/SRU'd
149
206
    RECOMMENDATIONS_OPT_IN_TEXT = _(u"To make recommendations, "
150
207
                 "Ubuntu Software Center "
151
208
                 "will occasionally send to Canonical a list "
152
209
                 "of software currently installed.")
 
210
    NO_NETWORK_RECOMMENDATIONS_TEXT = _(u"Recommendations will appear "
 
211
                                         "when next online.")
153
212
 
154
 
    def __init__(self, catview):
155
 
        RecommendationsPanel.__init__(self, catview)
 
213
    def __init__(self, db, properties_helper):
 
214
        RecommendationsPanel.__init__(self)
 
215
        self.db = db
 
216
        self.properties_helper = properties_helper
156
217
        self.subcategory = None
157
218
        self.set_header_label(_(u"Recommended For You"))
158
219
        self.recommended_for_you_content = None
 
220
        # .is_opted_in() means either "successfully opted-in" or
 
221
        #                             "requested opt-in" (but not done yet)
159
222
        if self.recommender_agent.is_opted_in():
160
 
            self._try_sso_login()
 
223
            if network_state_is_connected():
 
224
                self._try_sso_login()
 
225
            else:
 
226
                if self.recommender_agent.opt_in_requested:
 
227
                    # the user has opted in but has not yet completed the
 
228
                    # initial recommender profile upload, therefore there
 
229
                    # are no cached values available yet to display
 
230
                    self._show_no_network_view()
 
231
                else:
 
232
                    # display cached recommendations
 
233
                    self._update_recommended_for_you_content()
161
234
        else:
162
235
            self._show_opt_in_view()
163
236
 
164
237
    def _show_opt_in_view(self):
165
238
        # opt in box
166
 
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, StockEms.MEDIUM)
167
 
        vbox.set_border_width(StockEms.MEDIUM)
168
 
        self.opt_in_vbox = vbox  # for tests
169
 
        self.recommended_for_you_content = vbox  # hook it up to the rest
170
 
 
 
239
        self.recommended_for_you_content = Gtk.Box.new(
 
240
                Gtk.Orientation.VERTICAL,
 
241
                StockEms.MEDIUM)
 
242
        self.recommended_for_you_content.set_border_width(StockEms.MEDIUM)
171
243
        self.add(self.recommended_for_you_content)
172
244
 
173
245
        # opt in button
174
 
        button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT))
175
 
        button.connect("clicked", self._on_opt_in_button_clicked)
 
246
        self.opt_in_button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT))
 
247
        self.opt_in_button.connect("clicked", self._on_opt_in_button_clicked)
176
248
        hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)
177
 
        hbox.pack_start(button, False, False, 0)
178
 
        vbox.pack_start(hbox, False, False, 0)
179
 
        self.opt_in_button = button  # for tests
 
249
        hbox.pack_start(self.opt_in_button, False, False, 0)
 
250
        self.recommended_for_you_content.pack_start(hbox, False, False, 0)
180
251
 
181
252
        # opt in text
182
253
        text = _(self.RECOMMENDATIONS_OPT_IN_TEXT)
183
 
        label = Gtk.Label()
184
 
        label.set_use_markup(True)
185
 
        markup = '<small>%s</small>'
186
 
        label.set_name("subtle-label")
187
 
        label.set_markup(markup % text)
188
 
        label.set_alignment(0, 0.5)
189
 
        label.set_line_wrap(True)
190
 
        vbox.pack_start(label, False, False, 0)
 
254
        self.opt_in_label = self._create_opt_in_label(text)
 
255
        self.recommended_for_you_content.pack_start(self.opt_in_label,
 
256
                                                    False, False, 0)
 
257
 
 
258
    def _show_no_network_view(self):
 
259
        # display network not available message
 
260
        if not self.recommended_for_you_content:
 
261
            self.recommended_for_you_content = Gtk.Box.new(
 
262
                Gtk.Orientation.VERTICAL,
 
263
                StockEms.MEDIUM)
 
264
            self.recommended_for_you_content.set_border_width(StockEms.MEDIUM)
 
265
            self.add(self.recommended_for_you_content)
 
266
            text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT)
 
267
            self.opt_in_label = self._create_opt_in_label(text)
 
268
            self.recommended_for_you_content.pack_start(self.opt_in_label,
 
269
                                                        False, False, 0)
 
270
        else:
 
271
            self.opt_in_button.hide()
 
272
            text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT)
 
273
            self.opt_in_label.set_markup(self.opt_in_label_markup % text)
 
274
 
 
275
    def _create_opt_in_label(self, label_text):
 
276
        opt_in_label = Gtk.Label()
 
277
        opt_in_label.set_use_markup(True)
 
278
        self.opt_in_label_markup = '<small>%s</small>'
 
279
        opt_in_label.set_name("subtle-label")
 
280
        opt_in_label.set_markup(self.opt_in_label_markup % label_text)
 
281
        opt_in_label.set_alignment(0, 0.5)
 
282
        opt_in_label.set_line_wrap(True)
 
283
        return opt_in_label
191
284
 
192
285
    def _on_opt_in_button_clicked(self, button):
193
286
        self.opt_in_to_recommendations_service()
199
292
        # them here -- a spinner is shown for this process (the spec
200
293
        # wants a progress bar, but we don't have access to real-time
201
294
        # progress info)
202
 
        self._try_sso_login()
 
295
        if network_state_is_connected():
 
296
            self._try_sso_login()
 
297
        else:
 
298
            self._show_no_network_view()
 
299
            self.recommender_agent.recommender_opt_in_requested(True)
 
300
            self.emit("recommendations-opt-in")
203
301
 
204
302
    def opt_out_of_recommendations_service(self):
205
303
        # tell the backend that the user has opted out
219
317
        #        we simply reuse the opt-in text from the panel since we
220
318
        #        are well past string freeze
221
319
        self.spinner_notebook.show_spinner()
222
 
        self.sso = get_sso_backend(get_parent_xid(self),
 
320
        self.sso = get_login_backend(get_parent_xid(self),
223
321
                                   SOFTWARE_CENTER_NAME_KEYRING,
224
322
                                   self.RECOMMENDATIONS_OPT_IN_TEXT)
225
323
        self.sso.connect("login-successful", self._maybe_login_successful)
239
337
        # we are all squared up with SSO login, now we can proceed with the
240
338
        # recommendations display, or the profile upload if this is an
241
339
        # initial opt-in
242
 
        if self.recommender_agent.is_opted_in():
243
 
            self._update_recommended_for_you_content()
244
 
        else:
 
340
        if not self.recommender_agent.recommender_uuid:
245
341
            self._upload_user_profile_and_get_recommendations()
 
342
            if self.recommender_agent.recommender_opt_in_requested:
 
343
                self.recommender_agent.recommender_opt_in_requested(False)
 
344
        else:
 
345
            self._update_recommended_for_you_content()
246
346
 
247
347
    def _whoami_error(self, ssologin, e):
248
348
        self.spinner_notebook.hide_spinner()
253
353
        if not network_state_is_connected():
254
354
            # if there is an error in the SSO whois, first just check if we
255
355
            # have network access and if we do no, just hide the panel
256
 
            self._hide_recommended_for_you_panel()
 
356
            self._show_no_network_view()
257
357
        else:
258
358
            # an error that is not network related indicates that the user's
259
359
            # token has likely been revoked or invalidated on the server, for
282
382
                                  self._on_profile_submitted)
283
383
        self.recommender_agent.connect("error",
284
384
                                  self._on_profile_submitted_error)
285
 
        self.recommender_agent.post_submit_profile(self.catview.db)
 
385
        self.recommender_agent.post_submit_profile(self.db)
286
386
 
287
387
    def _on_profile_submitted(self, agent, profile):
288
388
        # after the user profile data has been uploaded, make the request
302
402
        # detect the very first profile upload as that indicates
303
403
        # the user's initial opt-in
304
404
        self._disconnect_recommender_listeners()
305
 
        self._hide_recommended_for_you_panel()
 
405
        self.hide()
306
406
 
307
407
    def _disconnect_recommender_listeners(self):
308
408
        try:
319
419
    Panel for use in the details view to display recommendations for a given
320
420
    application
321
421
    """
322
 
    def __init__(self, catview):
323
 
        RecommendationsPanel.__init__(self, catview)
 
422
    def __init__(self, db, properties_helper):
 
423
        RecommendationsPanel.__init__(self)
 
424
        self.db = db
 
425
        self.properties_helper = properties_helper
324
426
        self.set_header_label(_(u"People Also Installed"))
325
 
        self.app_recommendations_content = FlowableGrid()
 
427
        self.app_recommendations_content = TileGrid()
 
428
        self.app_recommendations_content.connect(
 
429
                    "application-activated", self._on_application_activated)
326
430
        self.add(self.app_recommendations_content)
327
431
 
328
432
    def set_pkgname(self, pkgname):
334
438
            self.app_recommendations_content.remove_all()
335
439
        self.spinner_notebook.show_spinner(_(u"Receiving recommendations…"))
336
440
        # get the recommendations from the recommender agent
337
 
        self.app_recommendations_cat = AppRecommendationsCategory(self.pkgname)
 
441
        self.app_recommendations_cat = AppRecommendationsCategory(
 
442
                self.db,
 
443
                self.pkgname)
338
444
        self.app_recommendations_cat.connect(
339
445
                                    'needs-refresh',
340
446
                                    self._on_app_recommendations_agent_refresh)
342
448
                                             self._on_recommender_agent_error)
343
449
 
344
450
    def _on_app_recommendations_agent_refresh(self, cat):
345
 
        docs = cat.get_documents(self.catview.db)
 
451
        docs = cat.get_documents(self.db)
346
452
        # display the recommendations
347
453
        if len(docs) > 0:
348
 
            self.catview._add_tiles_to_flowgrid(docs,
349
 
                                        self.app_recommendations_content, 3)
 
454
            self.app_recommendations_content.add_tiles(
 
455
                    self.properties_helper,
 
456
                    docs,
 
457
                    DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT)
350
458
            self.show_all()
351
459
            self.spinner_notebook.hide_spinner()
352
460
        else:
353
 
            self._hide_app_recommendations_panel()
354
 
 
355
 
    def _on_recommender_agent_error(self, agent, msg):
356
 
        LOG.warn("Error while accessing the recommender agent for the "
357
 
                 "details view recommendations: %s" % msg)
358
 
        # TODO: temporary, instead we will display cached recommendations here
359
 
        self._hide_app_recommendations_panel()
360
 
 
361
 
    def _hide_app_recommendations_panel(self):
362
 
        # and hide the pane
363
 
        self.hide()
 
461
            self.hide()
364
462
 
365
463
 
366
464
class RecommendationsOptInDialog(Gtk.MessageDialog):