Merge lp:~glatzor/software-center/portability into lp:software-center
- portability
- Merge into trunk
Proposed by
Sebastian Heinlein
Status: | Merged |
---|---|
Merged at revision: | 2540 |
Proposed branch: | lp:~glatzor/software-center/portability |
Merge into: | lp:software-center |
Diff against target: |
1637 lines (+778/-563) 16 files modified
data/ui/gtk3/SoftwareCenter.ui (+1/-1) softwarecenter/backend/channel_impl/aptchannels.py (+3/-2) softwarecenter/backend/oneconfhandler.py (+0/-206) softwarecenter/backend/oneconfhandler/__init__.py (+32/-0) softwarecenter/backend/oneconfhandler/core.py (+196/-0) softwarecenter/backend/piston/rnrclient.py (+1/-1) softwarecenter/backend/reviews/__init__.py (+41/-325) softwarecenter/backend/reviews/rnr.py (+376/-0) softwarecenter/db/application.py (+2/-1) softwarecenter/db/update.py (+1/-1) softwarecenter/distro/Debian.py (+69/-7) softwarecenter/distro/Ubuntu.py (+6/-0) softwarecenter/distro/__init__.py (+23/-9) softwarecenter/ui/gtk3/app.py (+22/-7) softwarecenter/ui/gtk3/panes/installedpane.py (+3/-2) softwarecenter/ui/gtk3/views/appdetailsview_gtk.py (+2/-1) |
To merge this branch: | bzr merge lp:~glatzor/software-center/portability |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt | Pending | ||
Review via email: mp+80448@code.launchpad.net |
Commit message
Description of the change
This branch allows to run software-center on non-Ubuntu systems, e.g. Debian! It makes some components optionally (oneconf, ratings, reviews, purchases ...)
To post a comment you must log in.
- 2553. By Sebastian Heinlein
-
Fix a copy and paste error in the Debian supported query
- 2554. By Sebastian Heinlein
-
Store the lsb_info in the Distro class
- 2555. By Sebastian Heinlein
-
Add maintenance status information for Debian packages
It is based on the suite/archive and comp of the Release file
- 2556. By Sebastian Heinlein
-
Remove obsolete get_maintenace_
status method - 2557. By Sebastian Heinlein
-
Mention the DFSG in the license information
- 2558. By Sebastian Heinlein
-
Don't hardcode Ubuntu for the component detection
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'data/ui/gtk3/SoftwareCenter.ui' |
2 | --- data/ui/gtk3/SoftwareCenter.ui 2011-10-08 17:15:20 +0000 |
3 | +++ data/ui/gtk3/SoftwareCenter.ui 2011-10-27 07:59:24 +0000 |
4 | @@ -470,7 +470,7 @@ |
5 | </object> |
6 | </child> |
7 | <child> |
8 | - <object class="GtkSeparatorMenuItem" id="menuitem3"> |
9 | + <object class="GtkSeparatorMenuItem" id="separator_developer"> |
10 | <property name="visible">True</property> |
11 | <property name="can_focus">False</property> |
12 | </object> |
13 | |
14 | === modified file 'softwarecenter/backend/channel_impl/aptchannels.py' |
15 | --- softwarecenter/backend/channel_impl/aptchannels.py 2011-10-11 18:44:41 +0000 |
16 | +++ softwarecenter/backend/channel_impl/aptchannels.py 2011-10-27 07:59:24 +0000 |
17 | @@ -266,7 +266,7 @@ |
18 | installed_only=installed_only)) |
19 | |
20 | # always display the partner channel, even if its source is not enabled |
21 | - if not partner_channel: |
22 | + if not partner_channel and distro_channel_name == "Ubuntu": |
23 | partner_channel = SoftwareChannel("Partner archive", |
24 | "Canonical", |
25 | "partner", |
26 | @@ -285,7 +285,8 @@ |
27 | channels.append(dist_channel) |
28 | if partner_channel is not None: |
29 | channels.append(partner_channel) |
30 | - channels.append(for_purchase_channel) |
31 | + if get_distro().PURCHASE_APP_URL: |
32 | + channels.append(for_purchase_channel) |
33 | if new_apps_channel is not None: |
34 | channels.append(new_apps_channel) |
35 | channels.extend(ppa_channels) |
36 | |
37 | === added directory 'softwarecenter/backend/oneconfhandler' |
38 | === removed file 'softwarecenter/backend/oneconfhandler.py' |
39 | --- softwarecenter/backend/oneconfhandler.py 2011-09-26 14:34:05 +0000 |
40 | +++ softwarecenter/backend/oneconfhandler.py 1970-01-01 00:00:00 +0000 |
41 | @@ -1,206 +0,0 @@ |
42 | -# -*- coding: utf-8 -*- |
43 | -# Copyright (C) 2011 Canonical |
44 | -# |
45 | -# Authors: |
46 | -# Didier Roche |
47 | -# |
48 | -# This program is free software; you can redistribute it and/or modify it under |
49 | -# the terms of the GNU General Public License as published by the Free Software |
50 | -# Foundation; version 3. |
51 | -# |
52 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
53 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
54 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
55 | -# details. |
56 | -# |
57 | -# You should have received a copy of the GNU General Public License along with |
58 | -# this program; if not, write to the Free Software Foundation, Inc., |
59 | -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
60 | - |
61 | - |
62 | -from oneconf.dbusconnect import DbusConnect |
63 | -from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY |
64 | - |
65 | -from softwarecenter.backend.login_sso import get_sso_backend |
66 | -from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend |
67 | -from softwarecenter.utils import clear_token_from_ubuntu_sso |
68 | - |
69 | -import datetime |
70 | -from gi.repository import GObject |
71 | -import logging |
72 | - |
73 | -from gettext import gettext as _ |
74 | - |
75 | -LOG = logging.getLogger(__name__) |
76 | - |
77 | -class OneConfHandler(GObject.GObject): |
78 | - |
79 | - __gsignals__ = { |
80 | - "show-oneconf-changed" : (GObject.SIGNAL_RUN_LAST, |
81 | - GObject.TYPE_NONE, |
82 | - (GObject.TYPE_PYOBJECT,), |
83 | - ), |
84 | - "last-time-sync-changed" : (GObject.SIGNAL_RUN_LAST, |
85 | - GObject.TYPE_NONE, |
86 | - (GObject.TYPE_PYOBJECT,), |
87 | - ), |
88 | - } |
89 | - |
90 | - |
91 | - def __init__(self, oneconfviewpickler): |
92 | - '''Controller of the installed pane''' |
93 | - |
94 | - LOG.debug("OneConf Handler init") |
95 | - super(OneConfHandler, self).__init__() |
96 | - |
97 | - # FIXME: should be an enum common to OneConf and here |
98 | - self.appname = "Ubuntu Software Center" |
99 | - |
100 | - # OneConf stuff |
101 | - self.oneconf = DbusConnect() |
102 | - self.oneconf.hosts_dbus_object.connect_to_signal('hostlist_changed', |
103 | - self.refresh_hosts) |
104 | - self.oneconf.hosts_dbus_object.connect_to_signal('packagelist_changed', |
105 | - self._on_store_packagelist_changed) |
106 | - self.oneconf.hosts_dbus_object.connect_to_signal('latestsync_changed', |
107 | - self.on_new_latest_oneconf_sync_timestamp) |
108 | - self.already_registered_hostids = [] |
109 | - self.is_current_registered = False |
110 | - |
111 | - self.oneconfviewpickler = oneconfviewpickler |
112 | - |
113 | - # refresh host list |
114 | - self._refreshing_hosts = False |
115 | - GObject.timeout_add_seconds(MIN_TIME_WITHOUT_ACTIVITY, self.get_latest_oneconf_sync) |
116 | - GObject.idle_add(self.refresh_hosts) |
117 | - |
118 | - def refresh_hosts(self): |
119 | - """refresh hosts list in the panel view""" |
120 | - LOG.debug('oneconf: refresh hosts') |
121 | - |
122 | - # this function can be called in different threads |
123 | - if self._refreshing_hosts: |
124 | - return |
125 | - self._refreshing_hosts = True |
126 | - |
127 | - #view_switcher = self.app.view_switcher |
128 | - #model = view_switcher.get_model() |
129 | - #previous_iter = model.installed_iter |
130 | - |
131 | - all_hosts = self.oneconf.get_all_hosts() |
132 | - for hostid in all_hosts: |
133 | - current, hostname, share_inventory = all_hosts[hostid] |
134 | - if not hostid in self.already_registered_hostids and not current: |
135 | - self.oneconfviewpickler.register_computer(hostid, hostname) |
136 | - self.already_registered_hostids.append(hostid) |
137 | - if current: |
138 | - is_current_registered = share_inventory |
139 | - |
140 | - # ensure we are logged to ubuntu sso to activate the view |
141 | - if self.is_current_registered != is_current_registered: |
142 | - self.sync_between_computers(is_current_registered) |
143 | - |
144 | - self._refreshing_hosts = False |
145 | - |
146 | - def get_latest_oneconf_sync(self): |
147 | - '''Get latest sync state in OneConf. |
148 | - |
149 | - This function is also the "ping" letting OneConf service alive''' |
150 | - LOG.debug("get latest sync state") |
151 | - timestamp = self.oneconf.get_last_sync_date() |
152 | - self.on_new_latest_oneconf_sync_timestamp(timestamp) |
153 | - return True |
154 | - |
155 | - def on_new_latest_oneconf_sync_timestamp(self, timestamp): |
156 | - '''Callback computing the right message for latest sync time''' |
157 | - try: |
158 | - last_sync = datetime.datetime.fromtimestamp(float(timestamp)) |
159 | - today = datetime.datetime.strptime(str(datetime.date.today()), '%Y-%m-%d') |
160 | - the_daybefore = today - datetime.timedelta(days=1) |
161 | - |
162 | - if last_sync > today: |
163 | - msg = _("Last sync %s") % last_sync.strftime('%H:%M') |
164 | - elif last_sync < today and last_sync > the_daybefore: |
165 | - msg = _("Last sync yesterday %s") % last_sync.strftime('%H:%M') |
166 | - else: |
167 | - msg = _("Last sync %s") % last_sync.strftime('%Y-%m-%d %H:%M') |
168 | - except (TypeError, ValueError): |
169 | - msg = _("To sync with another computer, choose “Sync Between Computers” from that computer.") |
170 | - self.emit("last-time-sync-changed", msg) |
171 | - |
172 | - def _share_inventory(self, share_inventory): |
173 | - '''set oneconf state and emit signal for installed view to show or not oneconf''' |
174 | - |
175 | - if share_inventory == self.is_current_registered: |
176 | - return |
177 | - self.is_current_registered = share_inventory |
178 | - LOG.debug("change share inventory state to %s", share_inventory) |
179 | - self.oneconf.set_share_inventory(share_inventory) |
180 | - self.get_latest_oneconf_sync() |
181 | - self.emit("show-oneconf-changed", share_inventory) |
182 | - |
183 | - def sync_between_computers(self, sync_on): |
184 | - '''toggle the sync on and off if needed between computers''' |
185 | - LOG.debug("Toggle sync between computers: %s", sync_on) |
186 | - |
187 | - if sync_on: |
188 | - self._try_login() |
189 | - else: |
190 | - self._share_inventory(False) |
191 | - |
192 | - def _on_store_packagelist_changed(self, hostid): |
193 | - '''pass the message to the view controller''' |
194 | - self.oneconfviewpickler.store_packagelist_changed(hostid) |
195 | - |
196 | - |
197 | - # SSO login part |
198 | - |
199 | - def _try_login(self): |
200 | - '''Try to get the credential or login on ubuntu sso''' |
201 | - logging.debug("OneConf login()") |
202 | - help_text = _("With multiple Ubuntu computers, you can publish their inventories online to compare the software installed on each\n" |
203 | - "No-one else will be able to see what you have installed.") |
204 | - self.sso = get_sso_backend(0, |
205 | - self.appname, help_text) |
206 | - self.sso.connect("login-successful", self._maybe_login_successful) |
207 | - self.sso.connect("login-canceled", self._login_canceled) |
208 | - self.sso.login_or_register() |
209 | - |
210 | - def _login_canceled(self, sso): |
211 | - self._share_inventory(False) |
212 | - |
213 | - def _maybe_login_successful(self, sso, oauth_result): |
214 | - """ called after we have the token, then we go and figure out our name """ |
215 | - logging.debug("_maybe_login_successful") |
216 | - token = oauth_result |
217 | - self.ssoapi = get_ubuntu_sso_backend(token) |
218 | - self.ssoapi.connect("whoami", self._whoami_done) |
219 | - self.ssoapi.connect("error", self._whoami_error) |
220 | - self.ssoapi.whoami() |
221 | - |
222 | - def _whoami_done(self, ssologin, result): |
223 | - logging.debug("_whoami_done") |
224 | - self._share_inventory(True) |
225 | - |
226 | - def _whoami_error(self, ssologin, e): |
227 | - logging.error("whoami error '%s'" % e) |
228 | - # HACK: clear the token from the keyring assuming that it expired |
229 | - # or got deauthorized by the user on the website |
230 | - # this really should be done by ubuntu-sso-client itself |
231 | - import lazr.restfulclient.errors |
232 | - errortype = lazr.restfulclient.errors.HTTPError |
233 | - if (type(e) == errortype): |
234 | - LOG.warn("authentication error, resetting token and retrying") |
235 | - clear_token_from_ubuntu_sso(self.appname) |
236 | - self._share_inventory(False) |
237 | - return |
238 | - |
239 | - |
240 | -# singleton |
241 | -oneconf_handler = None |
242 | -def get_oneconf_handler(oneconfviewpickler = None): |
243 | - global oneconf_handler |
244 | - if oneconf_handler is None and oneconfviewpickler: |
245 | - oneconf_handler = OneConfHandler(oneconfviewpickler) |
246 | - return oneconf_handler |
247 | - |
248 | |
249 | === added file 'softwarecenter/backend/oneconfhandler/__init__.py' |
250 | --- softwarecenter/backend/oneconfhandler/__init__.py 1970-01-01 00:00:00 +0000 |
251 | +++ softwarecenter/backend/oneconfhandler/__init__.py 2011-10-27 07:59:24 +0000 |
252 | @@ -0,0 +1,32 @@ |
253 | +# -*- coding: utf-8 -*- |
254 | +# Copyright (C) 2011 Canonical |
255 | +# |
256 | +# Authors: |
257 | +# Didier Roche |
258 | +# |
259 | +# This program is free software; you can redistribute it and/or modify it under |
260 | +# the terms of the GNU General Public License as published by the Free Software |
261 | +# Foundation; version 3. |
262 | +# |
263 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
264 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
265 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
266 | +# details. |
267 | +# |
268 | +# You should have received a copy of the GNU General Public License along with |
269 | +# this program; if not, write to the Free Software Foundation, Inc., |
270 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
271 | + |
272 | + |
273 | +# singleton |
274 | +oneconf_handler = None |
275 | +def get_oneconf_handler(oneconfviewpickler = None): |
276 | + global oneconf_handler |
277 | + try: |
278 | + from softwarecenter.backend.oneconfhandler.core import OnceConfHandler |
279 | + except ImportError: |
280 | + return None |
281 | + if oneconf_handler is None and oneconfviewpickler: |
282 | + oneconf_handler = OneConfHandler(oneconfviewpickler) |
283 | + return oneconf_handler |
284 | + |
285 | |
286 | === added file 'softwarecenter/backend/oneconfhandler/core.py' |
287 | --- softwarecenter/backend/oneconfhandler/core.py 1970-01-01 00:00:00 +0000 |
288 | +++ softwarecenter/backend/oneconfhandler/core.py 2011-10-27 07:59:24 +0000 |
289 | @@ -0,0 +1,196 @@ |
290 | +# -*- coding: utf-8 -*- |
291 | +# Copyright (C) 2011 Canonical |
292 | +# |
293 | +# Authors: |
294 | +# Didier Roche |
295 | +# |
296 | +# This program is free software; you can redistribute it and/or modify it under |
297 | +# the terms of the GNU General Public License as published by the Free Software |
298 | +# Foundation; version 3. |
299 | +# |
300 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
301 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
302 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
303 | +# details. |
304 | +# |
305 | +# You should have received a copy of the GNU General Public License along with |
306 | +# this program; if not, write to the Free Software Foundation, Inc., |
307 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
308 | + |
309 | + |
310 | +from oneconf.dbusconnect import DbusConnect |
311 | +from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY |
312 | + |
313 | +from softwarecenter.backend.login_sso import get_sso_backend |
314 | +from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend |
315 | +from softwarecenter.utils import clear_token_from_ubuntu_sso |
316 | + |
317 | +import datetime |
318 | +from gi.repository import GObject |
319 | +import logging |
320 | + |
321 | +from gettext import gettext as _ |
322 | + |
323 | +LOG = logging.getLogger(__name__) |
324 | + |
325 | +class OneConfHandler(GObject.GObject): |
326 | + |
327 | + __gsignals__ = { |
328 | + "show-oneconf-changed" : (GObject.SIGNAL_RUN_LAST, |
329 | + GObject.TYPE_NONE, |
330 | + (GObject.TYPE_PYOBJECT,), |
331 | + ), |
332 | + "last-time-sync-changed" : (GObject.SIGNAL_RUN_LAST, |
333 | + GObject.TYPE_NONE, |
334 | + (GObject.TYPE_PYOBJECT,), |
335 | + ), |
336 | + } |
337 | + |
338 | + |
339 | + def __init__(self, oneconfviewpickler): |
340 | + '''Controller of the installed pane''' |
341 | + |
342 | + LOG.debug("OneConf Handler init") |
343 | + super(OneConfHandler, self).__init__() |
344 | + |
345 | + # FIXME: should be an enum common to OneConf and here |
346 | + self.appname = "Ubuntu Software Center" |
347 | + |
348 | + # OneConf stuff |
349 | + self.oneconf = DbusConnect() |
350 | + self.oneconf.hosts_dbus_object.connect_to_signal('hostlist_changed', |
351 | + self.refresh_hosts) |
352 | + self.oneconf.hosts_dbus_object.connect_to_signal('packagelist_changed', |
353 | + self._on_store_packagelist_changed) |
354 | + self.oneconf.hosts_dbus_object.connect_to_signal('latestsync_changed', |
355 | + self.on_new_latest_oneconf_sync_timestamp) |
356 | + self.already_registered_hostids = [] |
357 | + self.is_current_registered = False |
358 | + |
359 | + self.oneconfviewpickler = oneconfviewpickler |
360 | + |
361 | + # refresh host list |
362 | + self._refreshing_hosts = False |
363 | + GObject.timeout_add_seconds(MIN_TIME_WITHOUT_ACTIVITY, self.get_latest_oneconf_sync) |
364 | + GObject.idle_add(self.refresh_hosts) |
365 | + |
366 | + def refresh_hosts(self): |
367 | + """refresh hosts list in the panel view""" |
368 | + LOG.debug('oneconf: refresh hosts') |
369 | + |
370 | + # this function can be called in different threads |
371 | + if self._refreshing_hosts: |
372 | + return |
373 | + self._refreshing_hosts = True |
374 | + |
375 | + #view_switcher = self.app.view_switcher |
376 | + #model = view_switcher.get_model() |
377 | + #previous_iter = model.installed_iter |
378 | + |
379 | + all_hosts = self.oneconf.get_all_hosts() |
380 | + for hostid in all_hosts: |
381 | + current, hostname, share_inventory = all_hosts[hostid] |
382 | + if not hostid in self.already_registered_hostids and not current: |
383 | + self.oneconfviewpickler.register_computer(hostid, hostname) |
384 | + self.already_registered_hostids.append(hostid) |
385 | + if current: |
386 | + is_current_registered = share_inventory |
387 | + |
388 | + # ensure we are logged to ubuntu sso to activate the view |
389 | + if self.is_current_registered != is_current_registered: |
390 | + self.sync_between_computers(is_current_registered) |
391 | + |
392 | + self._refreshing_hosts = False |
393 | + |
394 | + def get_latest_oneconf_sync(self): |
395 | + '''Get latest sync state in OneConf. |
396 | + |
397 | + This function is also the "ping" letting OneConf service alive''' |
398 | + LOG.debug("get latest sync state") |
399 | + timestamp = self.oneconf.get_last_sync_date() |
400 | + self.on_new_latest_oneconf_sync_timestamp(timestamp) |
401 | + return True |
402 | + |
403 | + def on_new_latest_oneconf_sync_timestamp(self, timestamp): |
404 | + '''Callback computing the right message for latest sync time''' |
405 | + try: |
406 | + last_sync = datetime.datetime.fromtimestamp(float(timestamp)) |
407 | + today = datetime.datetime.strptime(str(datetime.date.today()), '%Y-%m-%d') |
408 | + the_daybefore = today - datetime.timedelta(days=1) |
409 | + |
410 | + if last_sync > today: |
411 | + msg = _("Last sync %s") % last_sync.strftime('%H:%M') |
412 | + elif last_sync < today and last_sync > the_daybefore: |
413 | + msg = _("Last sync yesterday %s") % last_sync.strftime('%H:%M') |
414 | + else: |
415 | + msg = _("Last sync %s") % last_sync.strftime('%Y-%m-%d %H:%M') |
416 | + except (TypeError, ValueError): |
417 | + msg = _("To sync with another computer, choose “Sync Between Computers” from that computer.") |
418 | + self.emit("last-time-sync-changed", msg) |
419 | + |
420 | + def _share_inventory(self, share_inventory): |
421 | + '''set oneconf state and emit signal for installed view to show or not oneconf''' |
422 | + |
423 | + if share_inventory == self.is_current_registered: |
424 | + return |
425 | + self.is_current_registered = share_inventory |
426 | + LOG.debug("change share inventory state to %s", share_inventory) |
427 | + self.oneconf.set_share_inventory(share_inventory) |
428 | + self.get_latest_oneconf_sync() |
429 | + self.emit("show-oneconf-changed", share_inventory) |
430 | + |
431 | + def sync_between_computers(self, sync_on): |
432 | + '''toggle the sync on and off if needed between computers''' |
433 | + LOG.debug("Toggle sync between computers: %s", sync_on) |
434 | + |
435 | + if sync_on: |
436 | + self._try_login() |
437 | + else: |
438 | + self._share_inventory(False) |
439 | + |
440 | + def _on_store_packagelist_changed(self, hostid): |
441 | + '''pass the message to the view controller''' |
442 | + self.oneconfviewpickler.store_packagelist_changed(hostid) |
443 | + |
444 | + |
445 | + # SSO login part |
446 | + |
447 | + def _try_login(self): |
448 | + '''Try to get the credential or login on ubuntu sso''' |
449 | + logging.debug("OneConf login()") |
450 | + help_text = _("With multiple Ubuntu computers, you can publish their inventories online to compare the software installed on each\n" |
451 | + "No-one else will be able to see what you have installed.") |
452 | + self.sso = get_sso_backend(0, |
453 | + self.appname, help_text) |
454 | + self.sso.connect("login-successful", self._maybe_login_successful) |
455 | + self.sso.connect("login-canceled", self._login_canceled) |
456 | + self.sso.login_or_register() |
457 | + |
458 | + def _login_canceled(self, sso): |
459 | + self._share_inventory(False) |
460 | + |
461 | + def _maybe_login_successful(self, sso, oauth_result): |
462 | + """ called after we have the token, then we go and figure out our name """ |
463 | + logging.debug("_maybe_login_successful") |
464 | + token = oauth_result |
465 | + self.ssoapi = get_ubuntu_sso_backend(token) |
466 | + self.ssoapi.connect("whoami", self._whoami_done) |
467 | + self.ssoapi.connect("error", self._whoami_error) |
468 | + self.ssoapi.whoami() |
469 | + |
470 | + def _whoami_done(self, ssologin, result): |
471 | + logging.debug("_whoami_done") |
472 | + self._share_inventory(True) |
473 | + |
474 | + def _whoami_error(self, ssologin, e): |
475 | + logging.error("whoami error '%s'" % e) |
476 | + # HACK: clear the token from the keyring assuming that it expired |
477 | + # or got deauthorized by the user on the website |
478 | + # this really should be done by ubuntu-sso-client itself |
479 | + import lazr.restfulclient.errors |
480 | + errortype = lazr.restfulclient.errors.HTTPError |
481 | + if (type(e) == errortype): |
482 | + LOG.warn("authentication error, resetting token and retrying") |
483 | + clear_token_from_ubuntu_sso(self.appname) |
484 | + self._share_inventory(False) |
485 | + return |
486 | |
487 | === modified file 'softwarecenter/backend/piston/rnrclient.py' |
488 | --- softwarecenter/backend/piston/rnrclient.py 2011-08-09 08:47:43 +0000 |
489 | +++ softwarecenter/backend/piston/rnrclient.py 2011-10-27 07:59:24 +0000 |
490 | @@ -52,7 +52,7 @@ |
491 | LOG.error("need python-piston-mini client\n" |
492 | "available in natty or from:\n" |
493 | " ppa:software-store-developers/daily-build ") |
494 | - sys.exit(1) |
495 | + raise |
496 | |
497 | |
498 | if __name__ == "__main__": |
499 | |
500 | === added directory 'softwarecenter/backend/reviews' |
501 | === renamed file 'softwarecenter/backend/reviews.py' => 'softwarecenter/backend/reviews/__init__.py' |
502 | --- softwarecenter/backend/reviews.py 2011-10-13 13:39:21 +0000 |
503 | +++ softwarecenter/backend/reviews/__init__.py 2011-10-27 07:59:24 +0000 |
504 | @@ -53,8 +53,6 @@ |
505 | from StringIO import StringIO |
506 | from urllib import quote_plus |
507 | |
508 | -from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI |
509 | -from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails |
510 | from softwarecenter.db.categories import CategoriesParser |
511 | from softwarecenter.db.database import Application, StoreDatabase |
512 | import softwarecenter.distro |
513 | @@ -77,7 +75,7 @@ |
514 | |
515 | from softwarecenter.netstatus import network_state_is_connected |
516 | |
517 | -from spawn_helper import SpawnHelper |
518 | +from softwarecenter.backend.spawn_helper import SpawnHelper |
519 | |
520 | LOG = logging.getLogger(__name__) |
521 | |
522 | @@ -100,7 +98,7 @@ |
523 | USEFULNESS_CACHE = {} |
524 | |
525 | def __init__(self, try_server=False): |
526 | - self.rnrclient = RatingsAndReviewsAPI() |
527 | + #self.rnrclient = RatingsAndReviewsAPI() |
528 | fname = "usefulness.p" |
529 | self.USEFULNESS_CACHE_FILE = os.path.join(SOFTWARE_CENTER_CACHE_DIR, |
530 | fname) |
531 | @@ -438,43 +436,12 @@ |
532 | applist.append(db.get_pkgname(doc)) |
533 | return applist |
534 | |
535 | - # writing new reviews spawns external helper |
536 | - # FIXME: instead of the callback we should add proper gobject signals |
537 | def spawn_write_new_review_ui(self, translated_app, version, iconname, |
538 | origin, parent_xid, datadir, callback): |
539 | - """ this spawns the UI for writing a new review and |
540 | - adds it automatically to the reviews DB """ |
541 | - app = translated_app.get_untranslated_app(self.db) |
542 | - cmd = [os.path.join(datadir, RNRApps.SUBMIT_REVIEW), |
543 | - "--pkgname", app.pkgname, |
544 | - "--iconname", iconname, |
545 | - "--parent-xid", "%s" % parent_xid, |
546 | - "--version", version, |
547 | - "--origin", origin, |
548 | - "--datadir", datadir, |
549 | - ] |
550 | - if app.appname: |
551 | - # needs to be (utf8 encoded) str, otherwise call fails |
552 | - cmd += ["--appname", utf8(app.appname)] |
553 | - spawn_helper = SpawnHelper(format="json") |
554 | - spawn_helper.connect( |
555 | - "data-available", self._on_submit_review_data, app, callback) |
556 | - spawn_helper.run(cmd) |
557 | - |
558 | - def _on_submit_review_data(self, spawn_helper, review_json, app, callback): |
559 | - """ called when submit_review finished, when the review was send |
560 | - successfully the callback is triggered with the new reviews |
561 | + """Spawn the UI for writing a new review and adds it automatically |
562 | + to the reviews DB. |
563 | """ |
564 | - LOG.debug("_on_submit_review_data") |
565 | - # read stdout from submit_review |
566 | - review = ReviewDetails.from_dict(review_json) |
567 | - # FIXME: ideally this would be stored in ubuntu-sso-client |
568 | - # but it dosn't so we store it here |
569 | - save_person_to_config(review.reviewer_username) |
570 | - if not app in self._reviews: |
571 | - self._reviews[app] = [] |
572 | - self._reviews[app].insert(0, Review.from_piston_mini_client(review)) |
573 | - callback(app, self._reviews[app]) |
574 | + pass |
575 | |
576 | def spawn_report_abuse_ui(self, review_id, parent_xid, datadir, callback): |
577 | """ this spawns the UI for reporting a review as inappropriate |
578 | @@ -482,296 +449,20 @@ |
579 | operation is complete it will call callback with the updated |
580 | review list |
581 | """ |
582 | - cmd = [os.path.join(datadir, RNRApps.REPORT_REVIEW), |
583 | - "--review-id", review_id, |
584 | - "--parent-xid", "%s" % parent_xid, |
585 | - "--datadir", datadir, |
586 | - ] |
587 | - spawn_helper = SpawnHelper("json") |
588 | - spawn_helper.connect("exited", |
589 | - self._on_report_abuse_finished, |
590 | - review_id, callback) |
591 | - spawn_helper.run(cmd) |
592 | - |
593 | - def _on_report_abuse_finished(self, spawn_helper, exitcode, review_id, callback): |
594 | - """ called when report_absuse finished """ |
595 | - LOG.debug("hide id %s " % review_id) |
596 | - if exitcode == 0: |
597 | - for (app, reviews) in self._reviews.items(): |
598 | - for review in reviews: |
599 | - if str(review.id) == str(review_id): |
600 | - # remove the one we don't want to see anymore |
601 | - self._reviews[app].remove(review) |
602 | - callback(app, self._reviews[app], None, 'remove', review) |
603 | - break |
604 | - |
605 | + pass |
606 | |
607 | def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir, callback): |
608 | - cmd = [os.path.join(datadir, RNRApps.SUBMIT_USEFULNESS), |
609 | - "--review-id", "%s" % review_id, |
610 | - "--is-useful", "%s" % int(is_useful), |
611 | - "--parent-xid", "%s" % parent_xid, |
612 | - "--datadir", datadir, |
613 | - ] |
614 | - spawn_helper = SpawnHelper(format="none") |
615 | - spawn_helper.connect("exited", |
616 | - self._on_submit_usefulness_finished, |
617 | - review_id, is_useful, callback) |
618 | - spawn_helper.connect("error", |
619 | - self._on_submit_usefulness_error, |
620 | - review_id, callback) |
621 | - spawn_helper.run(cmd) |
622 | - |
623 | - def _on_submit_usefulness_finished(self, spawn_helper, res, review_id, is_useful, callback): |
624 | - """ called when report_usefulness finished """ |
625 | - # "Created", "Updated", "Not modified" - |
626 | - # once lp:~mvo/rnr-server/submit-usefulness-result-strings makes it |
627 | - response = spawn_helper._stdout |
628 | - if response == '"Not modified"': |
629 | - self._on_submit_usefulness_error(spawn_helper, response, review_id, callback) |
630 | - return |
631 | - |
632 | - LOG.debug("usefulness id %s " % review_id) |
633 | - useful_votes = UsefulnessCache() |
634 | - useful_votes.add_usefulness_vote(review_id, is_useful) |
635 | - for (app, reviews) in self._reviews.items(): |
636 | - for review in reviews: |
637 | - if str(review.id) == str(review_id): |
638 | - # update usefulness, older servers do not send |
639 | - # usefulness_{total,favorable} so we use getattr |
640 | - review.usefulness_total = getattr(review, "usefulness_total", 0) + 1 |
641 | - if is_useful: |
642 | - review.usefulness_favorable = getattr(review, "usefulness_favorable", 0) + 1 |
643 | - callback(app, self._reviews[app], useful_votes, 'replace', review) |
644 | - break |
645 | - |
646 | - def _on_submit_usefulness_error(self, spawn_helper, error_str, review_id, callback): |
647 | - LOG.warn("submit usefulness id=%s failed with error: %s" % |
648 | - (review_id, error_str)) |
649 | - for (app, reviews) in self._reviews.items(): |
650 | - for review in reviews: |
651 | - if str(review.id) == str(review_id): |
652 | - review.usefulness_submit_error = True |
653 | - callback(app, self._reviews[app], None, 'replace', review) |
654 | - break |
655 | + """Spawn a helper to submit a usefulness vote.""" |
656 | + pass |
657 | |
658 | def spawn_delete_review_ui(self, review_id, parent_xid, datadir, callback): |
659 | - cmd = [os.path.join(datadir, RNRApps.DELETE_REVIEW), |
660 | - "--review-id", "%s" % review_id, |
661 | - "--parent-xid", "%s" % parent_xid, |
662 | - "--datadir", datadir, |
663 | - ] |
664 | - spawn_helper = SpawnHelper(format="none") |
665 | - spawn_helper.connect("exited", |
666 | - self._on_delete_review_finished, |
667 | - review_id, callback) |
668 | - spawn_helper.connect("error", self._on_delete_review_error, |
669 | - review_id, callback) |
670 | - spawn_helper.run(cmd) |
671 | - |
672 | - def _on_delete_review_finished(self, spawn_helper, res, review_id, callback): |
673 | - """ called when delete_review finished""" |
674 | - LOG.debug("delete id %s " % review_id) |
675 | - for (app, reviews) in self._reviews.items(): |
676 | - for review in reviews: |
677 | - if str(review.id) == str(review_id): |
678 | - # remove the one we don't want to see anymore |
679 | - self._reviews[app].remove(review) |
680 | - callback(app, self._reviews[app], None, 'remove', review) |
681 | - break |
682 | - |
683 | - def _on_delete_review_error(self, spawn_helper, error_str, review_id, callback): |
684 | - """called if delete review errors""" |
685 | - LOG.warn("delete review id=%s failed with error: %s" % (review_id, error_str)) |
686 | - for (app, reviews) in self._reviews.items(): |
687 | - for review in reviews: |
688 | - if str(review.id) == str(review_id): |
689 | - review.delete_error = True |
690 | - callback(app, self._reviews[app], action='replace', |
691 | - single_review=review) |
692 | - break |
693 | - |
694 | - |
695 | + """Spawn a helper to delete a review.""" |
696 | + pass |
697 | + |
698 | def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, callback): |
699 | - """ this spawns the UI for writing a new review and |
700 | - adds it automatically to the reviews DB """ |
701 | - cmd = [os.path.join(datadir, RNRApps.MODIFY_REVIEW), |
702 | - "--parent-xid", "%s" % parent_xid, |
703 | - "--iconname", iconname, |
704 | - "--datadir", "%s" % datadir, |
705 | - "--review-id", "%s" % review_id, |
706 | - ] |
707 | - spawn_helper = SpawnHelper(format="json") |
708 | - spawn_helper.connect("data-available", |
709 | - self._on_modify_review_finished, |
710 | - review_id, callback) |
711 | - spawn_helper.connect("error", self._on_modify_review_error, |
712 | - review_id, callback) |
713 | - spawn_helper.run(cmd) |
714 | - |
715 | - def _on_modify_review_finished(self, spawn_helper, review_json, review_id, callback): |
716 | - """called when modify_review finished""" |
717 | - LOG.debug("_on_modify_review_finished") |
718 | - #review_json = spawn_helper._stdout |
719 | - mod_review = ReviewDetails.from_dict(review_json) |
720 | - for (app, reviews) in self._reviews.items(): |
721 | - for review in reviews: |
722 | - if str(review.id) == str(review_id): |
723 | - # remove the one we don't want to see anymore |
724 | - self._reviews[app].remove(review) |
725 | - new_review = Review.from_piston_mini_client(mod_review) |
726 | - self._reviews[app].insert(0, new_review) |
727 | - callback(app, self._reviews[app], action='replace', |
728 | - single_review=new_review) |
729 | - break |
730 | - |
731 | - def _on_modify_review_error(self, spawn_helper, error_str, review_id, callback): |
732 | - """called if modify review errors""" |
733 | - LOG.debug("modify review id=%s failed with error: %s" % (review_id, error_str)) |
734 | - for (app, reviews) in self._reviews.items(): |
735 | - for review in reviews: |
736 | - if str(review.id) == str(review_id): |
737 | - review.modify_error = True |
738 | - callback(app, self._reviews[app], action='replace', |
739 | - single_review=review) |
740 | - break |
741 | - |
742 | - |
743 | -# this code had several incernations: |
744 | -# - python threads, slow and full of latency (GIL) |
745 | -# - python multiprocesing, crashed when accessibility was turned on, |
746 | -# does not work in the quest session (#743020) |
747 | -# - GObject.spawn_async() looks good so far (using the SpawnHelper code) |
748 | -class ReviewLoaderSpawningRNRClient(ReviewLoader): |
749 | - """ loader that uses multiprocessing to call rnrclient and |
750 | - a glib timeout watcher that polls periodically for the |
751 | - data |
752 | - """ |
753 | - |
754 | - def __init__(self, cache, db, distro=None): |
755 | - super(ReviewLoaderSpawningRNRClient, self).__init__(cache, db, distro) |
756 | - cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") |
757 | - self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) |
758 | - cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") |
759 | - self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) |
760 | - self._reviews = {} |
761 | - |
762 | - def _update_rnrclient_offline_state(self): |
763 | - # this needs the lp:~mvo/piston-mini-client/offline-mode branch |
764 | - self.rnrclient._offline_mode = not network_state_is_connected() |
765 | - |
766 | - # reviews |
767 | - def get_reviews(self, translated_app, callback, page=1, |
768 | - language=None, sort=0): |
769 | - """ public api, triggers fetching a review and calls callback |
770 | - when its ready |
771 | - """ |
772 | - # its fine to use the translated appname here, we only submit the |
773 | - # pkgname to the server |
774 | - app = translated_app |
775 | - self._update_rnrclient_offline_state() |
776 | - sort_method = self._review_sort_methods[sort] |
777 | - if language is None: |
778 | - language = self.language |
779 | - # gather args for the helper |
780 | - try: |
781 | - origin = self.cache.get_origin(app.pkgname) |
782 | - except: |
783 | - # this can happen if e.g. the app has multiple origins, this |
784 | - # will be handled later |
785 | - origin = None |
786 | - # special case for not-enabled PPAs |
787 | - if not origin and self.db: |
788 | - details = app.get_details(self.db) |
789 | - ppa = details.ppaname |
790 | - if ppa: |
791 | - origin = "lp-ppa-%s" % ppa.replace("/", "-") |
792 | - # if there is no origin, there is nothing to do |
793 | - if not origin: |
794 | - callback(app, []) |
795 | - return |
796 | - distroseries = self.distro.get_codename() |
797 | - # run the command and add watcher |
798 | - cmd = [os.path.join(softwarecenter.paths.datadir, PistonHelpers.GET_REVIEWS), |
799 | - "--language", language, |
800 | - "--origin", origin, |
801 | - "--distroseries", distroseries, |
802 | - "--pkgname", str(app.pkgname), # ensure its str, not unicode |
803 | - "--page", str(page), |
804 | - "--sort", sort_method, |
805 | - ] |
806 | - spawn_helper = SpawnHelper() |
807 | - spawn_helper.connect( |
808 | - "data-available", self._on_reviews_helper_data, app, callback) |
809 | - spawn_helper.run(cmd) |
810 | - |
811 | - def _on_reviews_helper_data(self, spawn_helper, piston_reviews, app, callback): |
812 | - # convert into our review objects |
813 | - reviews = [] |
814 | - for r in piston_reviews: |
815 | - reviews.append(Review.from_piston_mini_client(r)) |
816 | - # add to our dicts and run callback |
817 | - self._reviews[app] = reviews |
818 | - callback(app, self._reviews[app]) |
819 | - return False |
820 | - |
821 | - # stats |
822 | - def refresh_review_stats(self, callback): |
823 | - """ public api, refresh the available statistics """ |
824 | - try: |
825 | - mtime = os.path.getmtime(self.REVIEW_STATS_CACHE_FILE) |
826 | - days_delta = int((time.time() - mtime) // (24*60*60)) |
827 | - days_delta += 1 |
828 | - except OSError: |
829 | - days_delta = 0 |
830 | - LOG.debug("refresh with days_delta: %s" % days_delta) |
831 | - #origin = "any" |
832 | - #distroseries = self.distro.get_codename() |
833 | - cmd = [os.path.join( |
834 | - softwarecenter.paths.datadir, PistonHelpers.GET_REVIEW_STATS), |
835 | - # FIXME: the server currently has bug (#757695) so we |
836 | - # can not turn this on just yet and need to use |
837 | - # the old "catch-all" review-stats for now |
838 | - #"--origin", origin, |
839 | - #"--distroseries", distroseries, |
840 | - ] |
841 | - if days_delta: |
842 | - cmd += ["--days-delta", str(days_delta)] |
843 | - spawn_helper = SpawnHelper() |
844 | - spawn_helper.connect("data-available", self._on_review_stats_data, callback) |
845 | - spawn_helper.run(cmd) |
846 | - |
847 | - def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback): |
848 | - """ process stdout from the helper """ |
849 | - review_stats = self.REVIEW_STATS_CACHE |
850 | - |
851 | - if self._cache_version_old and self._server_has_histogram(piston_review_stats): |
852 | - self.REVIEW_STATS_CACHE = {} |
853 | - self.save_review_stats_cache_file() |
854 | - self.refresh_review_stats(callback) |
855 | - return |
856 | - |
857 | - # convert to the format that s-c uses |
858 | - for r in piston_review_stats: |
859 | - s = ReviewStats(Application("", r.package_name)) |
860 | - s.ratings_average = float(r.ratings_average) |
861 | - s.ratings_total = float(r.ratings_total) |
862 | - if r.histogram: |
863 | - s.rating_spread = json.loads(r.histogram) |
864 | - else: |
865 | - s.rating_spread = [0,0,0,0,0] |
866 | - s.dampened_rating = calc_dr(s.rating_spread) |
867 | - review_stats[s.app] = s |
868 | - self.REVIEW_STATS_CACHE = review_stats |
869 | - callback(review_stats) |
870 | - self.save_review_stats_cache_file() |
871 | - |
872 | - def _server_has_histogram(self, piston_review_stats): |
873 | - '''check response from server to see if histogram is supported''' |
874 | - supported = getattr(piston_review_stats[0], "histogram", False) |
875 | - if not supported: |
876 | - return False |
877 | - return True |
878 | + """Spawn a helper to modify a review.""" |
879 | + pass |
880 | + |
881 | |
882 | class ReviewLoaderJsonAsync(ReviewLoader): |
883 | """ get json (or gzip compressed json) """ |
884 | @@ -871,7 +562,7 @@ |
885 | return random.choice(self.LOREM.split("\n\n")) |
886 | def _random_summary(self): |
887 | return random.choice(self.SUMMARIES) |
888 | - def get_reviews(self, application, callback, page=1, language=None): |
889 | + def get_reviews(self, application, callback, page=1, language=None, sort=0): |
890 | if not application in self._review_stats_cache: |
891 | self.get_review_stats(application) |
892 | stats = self._review_stats_cache[application] |
893 | @@ -1034,6 +725,26 @@ |
894 | et ea rebum stet clita kasd gubergren no sea takimata sanctus est lorem |
895 | ipsum dolor sit amet""" |
896 | |
897 | + |
898 | +class ReviewLoaderNull(ReviewLoader): |
899 | + |
900 | + """A dummy review loader which just returns empty results.""" |
901 | + |
902 | + def __init__(self, cache, db): |
903 | + self._review_stats_cache = {} |
904 | + self._reviews_cache = {} |
905 | + |
906 | + def get_reviews(self, application, callback, page=1, language=None, sort=0): |
907 | + callback(application, []) |
908 | + |
909 | + def get_review_stats(self, application): |
910 | + return None |
911 | + |
912 | + def refresh_review_stats(self, callback): |
913 | + review_stats = [] |
914 | + callback(review_stats) |
915 | + |
916 | + |
917 | review_loader = None |
918 | def get_review_loader(cache, db=None): |
919 | """ |
920 | @@ -1050,7 +761,12 @@ |
921 | elif "SOFTWARE_CENTER_GIO_REVIEWS" in os.environ: |
922 | review_loader = ReviewLoaderJsonAsync(cache, db) |
923 | else: |
924 | - review_loader = ReviewLoaderSpawningRNRClient(cache, db) |
925 | + try: |
926 | + from softwarecenter.backend.reviews.rnr import ReviewLoaderSpawningRNRClient |
927 | + except ImportError: |
928 | + review_loader = ReviewLoaderNull(cache, db) |
929 | + else: |
930 | + review_loader = ReviewLoaderSpawningRNRClient(cache, db) |
931 | return review_loader |
932 | |
933 | if __name__ == "__main__": |
934 | |
935 | === added file 'softwarecenter/backend/reviews/rnr.py' |
936 | --- softwarecenter/backend/reviews/rnr.py 1970-01-01 00:00:00 +0000 |
937 | +++ softwarecenter/backend/reviews/rnr.py 2011-10-27 07:59:24 +0000 |
938 | @@ -0,0 +1,376 @@ |
939 | +# -*- coding: utf-8 -*- |
940 | + |
941 | +# Copyright (C) 2009 Canonical |
942 | +# |
943 | +# Authors: |
944 | +# Michael Vogt |
945 | +# |
946 | +# This program is free software; you can redistribute it and/or modify it under |
947 | +# the terms of the GNU General Public License as published by the Free Software |
948 | +# Foundation; version 3. |
949 | +# |
950 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
951 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
952 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
953 | +# details. |
954 | +# |
955 | +# You should have received a copy of the GNU General Public License along with |
956 | +# this program; if not, write to the Free Software Foundation, Inc., |
957 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
958 | + |
959 | +import gzip |
960 | +import logging |
961 | +import os |
962 | +import json |
963 | +import time |
964 | + |
965 | +from softwarecenter.backend.spawn_helper import SpawnHelper |
966 | +from softwarecenter.backend.reviews import ReviewLoader, Review, ReviewStats |
967 | +from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI |
968 | +from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails |
969 | +from softwarecenter.db.database import Application |
970 | +import softwarecenter.distro |
971 | +from softwarecenter.netstatus import network_state_is_connected |
972 | +from softwarecenter.paths import (SOFTWARE_CENTER_CACHE_DIR, |
973 | + PistonHelpers, |
974 | + RNRApps, |
975 | + ) |
976 | +from softwarecenter.utils import calc_dr |
977 | + |
978 | +LOG = logging.getLogger(__name__) |
979 | + |
980 | + |
981 | +# this code had several incernations: |
982 | +# - python threads, slow and full of latency (GIL) |
983 | +# - python multiprocesing, crashed when accessibility was turned on, |
984 | +# does not work in the quest session (#743020) |
985 | +# - GObject.spawn_async() looks good so far (using the SpawnHelper code) |
986 | +class ReviewLoaderSpawningRNRClient(ReviewLoader): |
987 | + """ loader that uses multiprocessing to call rnrclient and |
988 | + a glib timeout watcher that polls periodically for the |
989 | + data |
990 | + """ |
991 | + |
992 | + def __init__(self, cache, db, distro=None): |
993 | + super(ReviewLoaderSpawningRNRClient, self).__init__(cache, db, distro) |
994 | + cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") |
995 | + self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) |
996 | + cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") |
997 | + self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) |
998 | + self._reviews = {} |
999 | + |
1000 | + def _update_rnrclient_offline_state(self): |
1001 | + # this needs the lp:~mvo/piston-mini-client/offline-mode branch |
1002 | + self.rnrclient._offline_mode = not network_state_is_connected() |
1003 | + |
1004 | + # reviews |
1005 | + def get_reviews(self, translated_app, callback, page=1, |
1006 | + language=None, sort=0): |
1007 | + """ public api, triggers fetching a review and calls callback |
1008 | + when its ready |
1009 | + """ |
1010 | + # its fine to use the translated appname here, we only submit the |
1011 | + # pkgname to the server |
1012 | + app = translated_app |
1013 | + self._update_rnrclient_offline_state() |
1014 | + sort_method = self._review_sort_methods[sort] |
1015 | + if language is None: |
1016 | + language = self.language |
1017 | + # gather args for the helper |
1018 | + try: |
1019 | + origin = self.cache.get_origin(app.pkgname) |
1020 | + except: |
1021 | + # this can happen if e.g. the app has multiple origins, this |
1022 | + # will be handled later |
1023 | + origin = None |
1024 | + # special case for not-enabled PPAs |
1025 | + if not origin and self.db: |
1026 | + details = app.get_details(self.db) |
1027 | + ppa = details.ppaname |
1028 | + if ppa: |
1029 | + origin = "lp-ppa-%s" % ppa.replace("/", "-") |
1030 | + # if there is no origin, there is nothing to do |
1031 | + if not origin: |
1032 | + callback(app, []) |
1033 | + return |
1034 | + distroseries = self.distro.get_codename() |
1035 | + # run the command and add watcher |
1036 | + cmd = [os.path.join(softwarecenter.paths.datadir, PistonHelpers.GET_REVIEWS), |
1037 | + "--language", language, |
1038 | + "--origin", origin, |
1039 | + "--distroseries", distroseries, |
1040 | + "--pkgname", str(app.pkgname), # ensure its str, not unicode |
1041 | + "--page", str(page), |
1042 | + "--sort", sort_method, |
1043 | + ] |
1044 | + spawn_helper = SpawnHelper() |
1045 | + spawn_helper.connect( |
1046 | + "data-available", self._on_reviews_helper_data, app, callback) |
1047 | + spawn_helper.run(cmd) |
1048 | + |
1049 | + def _on_reviews_helper_data(self, spawn_helper, piston_reviews, app, callback): |
1050 | + # convert into our review objects |
1051 | + reviews = [] |
1052 | + for r in piston_reviews: |
1053 | + reviews.append(Review.from_piston_mini_client(r)) |
1054 | + # add to our dicts and run callback |
1055 | + self._reviews[app] = reviews |
1056 | + callback(app, self._reviews[app]) |
1057 | + return False |
1058 | + |
1059 | + # stats |
1060 | + def refresh_review_stats(self, callback): |
1061 | + """ public api, refresh the available statistics """ |
1062 | + try: |
1063 | + mtime = os.path.getmtime(self.REVIEW_STATS_CACHE_FILE) |
1064 | + days_delta = int((time.time() - mtime) // (24*60*60)) |
1065 | + days_delta += 1 |
1066 | + except OSError: |
1067 | + days_delta = 0 |
1068 | + LOG.debug("refresh with days_delta: %s" % days_delta) |
1069 | + #origin = "any" |
1070 | + #distroseries = self.distro.get_codename() |
1071 | + cmd = [os.path.join( |
1072 | + softwarecenter.paths.datadir, PistonHelpers.GET_REVIEW_STATS), |
1073 | + # FIXME: the server currently has bug (#757695) so we |
1074 | + # can not turn this on just yet and need to use |
1075 | + # the old "catch-all" review-stats for now |
1076 | + #"--origin", origin, |
1077 | + #"--distroseries", distroseries, |
1078 | + ] |
1079 | + if days_delta: |
1080 | + cmd += ["--days-delta", str(days_delta)] |
1081 | + spawn_helper = SpawnHelper() |
1082 | + spawn_helper.connect("data-available", self._on_review_stats_data, callback) |
1083 | + spawn_helper.run(cmd) |
1084 | + |
1085 | + def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback): |
1086 | + """ process stdout from the helper """ |
1087 | + review_stats = self.REVIEW_STATS_CACHE |
1088 | + |
1089 | + if self._cache_version_old and self._server_has_histogram(piston_review_stats): |
1090 | + self.REVIEW_STATS_CACHE = {} |
1091 | + self.save_review_stats_cache_file() |
1092 | + self.refresh_review_stats(callback) |
1093 | + return |
1094 | + |
1095 | + # convert to the format that s-c uses |
1096 | + for r in piston_review_stats: |
1097 | + s = ReviewStats(Application("", r.package_name)) |
1098 | + s.ratings_average = float(r.ratings_average) |
1099 | + s.ratings_total = float(r.ratings_total) |
1100 | + if r.histogram: |
1101 | + s.rating_spread = json.loads(r.histogram) |
1102 | + else: |
1103 | + s.rating_spread = [0,0,0,0,0] |
1104 | + s.dampened_rating = calc_dr(s.rating_spread) |
1105 | + review_stats[s.app] = s |
1106 | + self.REVIEW_STATS_CACHE = review_stats |
1107 | + callback(review_stats) |
1108 | + self.save_review_stats_cache_file() |
1109 | + |
1110 | + def _server_has_histogram(self, piston_review_stats): |
1111 | + '''check response from server to see if histogram is supported''' |
1112 | + supported = getattr(piston_review_stats[0], "histogram", False) |
1113 | + if not supported: |
1114 | + return False |
1115 | + return True |
1116 | + |
1117 | + # writing new reviews spawns external helper |
1118 | + # FIXME: instead of the callback we should add proper gobject signals |
1119 | + def spawn_write_new_review_ui(self, translated_app, version, iconname, |
1120 | + origin, parent_xid, datadir, callback): |
1121 | + """ this spawns the UI for writing a new review and |
1122 | + adds it automatically to the reviews DB """ |
1123 | + app = translated_app.get_untranslated_app(self.db) |
1124 | + cmd = [os.path.join(datadir, RNRApps.SUBMIT_REVIEW), |
1125 | + "--pkgname", app.pkgname, |
1126 | + "--iconname", iconname, |
1127 | + "--parent-xid", "%s" % parent_xid, |
1128 | + "--version", version, |
1129 | + "--origin", origin, |
1130 | + "--datadir", datadir, |
1131 | + ] |
1132 | + if app.appname: |
1133 | + # needs to be (utf8 encoded) str, otherwise call fails |
1134 | + cmd += ["--appname", utf8(app.appname)] |
1135 | + spawn_helper = SpawnHelper(format="json") |
1136 | + spawn_helper.connect( |
1137 | + "data-available", self._on_submit_review_data, app, callback) |
1138 | + spawn_helper.run(cmd) |
1139 | + |
1140 | + def _on_submit_review_data(self, spawn_helper, review_json, app, callback): |
1141 | + """ called when submit_review finished, when the review was send |
1142 | + successfully the callback is triggered with the new reviews |
1143 | + """ |
1144 | + LOG.debug("_on_submit_review_data") |
1145 | + return |
1146 | + # read stdout from submit_review |
1147 | + review = ReviewDetails.from_dict(review_json) |
1148 | + # FIXME: ideally this would be stored in ubuntu-sso-client |
1149 | + # but it dosn't so we store it here |
1150 | + save_person_to_config(review.reviewer_username) |
1151 | + if not app in self._reviews: |
1152 | + self._reviews[app] = [] |
1153 | + self._reviews[app].insert(0, Review.from_piston_mini_client(review)) |
1154 | + callback(app, self._reviews[app]) |
1155 | + |
1156 | + def spawn_report_abuse_ui(self, review_id, parent_xid, datadir, callback): |
1157 | + """ this spawns the UI for reporting a review as inappropriate |
1158 | + and adds the review-id to the internal hide list. once the |
1159 | + operation is complete it will call callback with the updated |
1160 | + review list |
1161 | + """ |
1162 | + cmd = [os.path.join(datadir, RNRApps.REPORT_REVIEW), |
1163 | + "--review-id", review_id, |
1164 | + "--parent-xid", "%s" % parent_xid, |
1165 | + "--datadir", datadir, |
1166 | + ] |
1167 | + spawn_helper = SpawnHelper("json") |
1168 | + spawn_helper.connect("exited", |
1169 | + self._on_report_abuse_finished, |
1170 | + review_id, callback) |
1171 | + spawn_helper.run(cmd) |
1172 | + |
1173 | + def _on_report_abuse_finished(self, spawn_helper, exitcode, review_id, callback): |
1174 | + """ called when report_absuse finished """ |
1175 | + LOG.debug("hide id %s " % review_id) |
1176 | + if exitcode == 0: |
1177 | + for (app, reviews) in self._reviews.items(): |
1178 | + for review in reviews: |
1179 | + if str(review.id) == str(review_id): |
1180 | + # remove the one we don't want to see anymore |
1181 | + self._reviews[app].remove(review) |
1182 | + callback(app, self._reviews[app], None, 'remove', review) |
1183 | + break |
1184 | + |
1185 | + |
1186 | + def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir, callback): |
1187 | + cmd = [os.path.join(datadir, RNRApps.SUBMIT_USEFULNESS), |
1188 | + "--review-id", "%s" % review_id, |
1189 | + "--is-useful", "%s" % int(is_useful), |
1190 | + "--parent-xid", "%s" % parent_xid, |
1191 | + "--datadir", datadir, |
1192 | + ] |
1193 | + spawn_helper = SpawnHelper(format="none") |
1194 | + spawn_helper.connect("exited", |
1195 | + self._on_submit_usefulness_finished, |
1196 | + review_id, is_useful, callback) |
1197 | + spawn_helper.connect("error", |
1198 | + self._on_submit_usefulness_error, |
1199 | + review_id, callback) |
1200 | + spawn_helper.run(cmd) |
1201 | + |
1202 | + def _on_submit_usefulness_finished(self, spawn_helper, res, review_id, is_useful, callback): |
1203 | + """ called when report_usefulness finished """ |
1204 | + # "Created", "Updated", "Not modified" - |
1205 | + # once lp:~mvo/rnr-server/submit-usefulness-result-strings makes it |
1206 | + response = spawn_helper._stdout |
1207 | + if response == '"Not modified"': |
1208 | + self._on_submit_usefulness_error(spawn_helper, response, review_id, callback) |
1209 | + return |
1210 | + |
1211 | + LOG.debug("usefulness id %s " % review_id) |
1212 | + useful_votes = UsefulnessCache() |
1213 | + useful_votes.add_usefulness_vote(review_id, is_useful) |
1214 | + for (app, reviews) in self._reviews.items(): |
1215 | + for review in reviews: |
1216 | + if str(review.id) == str(review_id): |
1217 | + # update usefulness, older servers do not send |
1218 | + # usefulness_{total,favorable} so we use getattr |
1219 | + review.usefulness_total = getattr(review, "usefulness_total", 0) + 1 |
1220 | + if is_useful: |
1221 | + review.usefulness_favorable = getattr(review, "usefulness_favorable", 0) + 1 |
1222 | + callback(app, self._reviews[app], useful_votes, 'replace', review) |
1223 | + break |
1224 | + |
1225 | + def _on_submit_usefulness_error(self, spawn_helper, error_str, review_id, callback): |
1226 | + LOG.warn("submit usefulness id=%s failed with error: %s" % |
1227 | + (review_id, error_str)) |
1228 | + for (app, reviews) in self._reviews.items(): |
1229 | + for review in reviews: |
1230 | + if str(review.id) == str(review_id): |
1231 | + review.usefulness_submit_error = True |
1232 | + callback(app, self._reviews[app], None, 'replace', review) |
1233 | + break |
1234 | + |
1235 | + def spawn_delete_review_ui(self, review_id, parent_xid, datadir, callback): |
1236 | + cmd = [os.path.join(datadir, RNRApps.DELETE_REVIEW), |
1237 | + "--review-id", "%s" % review_id, |
1238 | + "--parent-xid", "%s" % parent_xid, |
1239 | + "--datadir", datadir, |
1240 | + ] |
1241 | + spawn_helper = SpawnHelper(format="none") |
1242 | + spawn_helper.connect("exited", |
1243 | + self._on_delete_review_finished, |
1244 | + review_id, callback) |
1245 | + spawn_helper.connect("error", self._on_delete_review_error, |
1246 | + review_id, callback) |
1247 | + spawn_helper.run(cmd) |
1248 | + |
1249 | + def _on_delete_review_finished(self, spawn_helper, res, review_id, callback): |
1250 | + """ called when delete_review finished""" |
1251 | + LOG.debug("delete id %s " % review_id) |
1252 | + for (app, reviews) in self._reviews.items(): |
1253 | + for review in reviews: |
1254 | + if str(review.id) == str(review_id): |
1255 | + # remove the one we don't want to see anymore |
1256 | + self._reviews[app].remove(review) |
1257 | + callback(app, self._reviews[app], None, 'remove', review) |
1258 | + break |
1259 | + |
1260 | + def _on_delete_review_error(self, spawn_helper, error_str, review_id, callback): |
1261 | + """called if delete review errors""" |
1262 | + LOG.warn("delete review id=%s failed with error: %s" % (review_id, error_str)) |
1263 | + for (app, reviews) in self._reviews.items(): |
1264 | + for review in reviews: |
1265 | + if str(review.id) == str(review_id): |
1266 | + review.delete_error = True |
1267 | + callback(app, self._reviews[app], action='replace', |
1268 | + single_review=review) |
1269 | + break |
1270 | + |
1271 | + def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, callback): |
1272 | + """ this spawns the UI for writing a new review and |
1273 | + adds it automatically to the reviews DB """ |
1274 | + cmd = [os.path.join(datadir, RNRApps.MODIFY_REVIEW), |
1275 | + "--parent-xid", "%s" % parent_xid, |
1276 | + "--iconname", iconname, |
1277 | + "--datadir", "%s" % datadir, |
1278 | + "--review-id", "%s" % review_id, |
1279 | + ] |
1280 | + spawn_helper = SpawnHelper(format="json") |
1281 | + spawn_helper.connect("data-available", |
1282 | + self._on_modify_review_finished, |
1283 | + review_id, callback) |
1284 | + spawn_helper.connect("error", self._on_modify_review_error, |
1285 | + review_id, callback) |
1286 | + spawn_helper.run(cmd) |
1287 | + |
1288 | + def _on_modify_review_finished(self, spawn_helper, review_json, review_id, callback): |
1289 | + """called when modify_review finished""" |
1290 | + LOG.debug("_on_modify_review_finished") |
1291 | + return |
1292 | + #review_json = spawn_helper._stdout |
1293 | + mod_review = ReviewDetails.from_dict(review_json) |
1294 | + for (app, reviews) in self._reviews.items(): |
1295 | + for review in reviews: |
1296 | + if str(review.id) == str(review_id): |
1297 | + # remove the one we don't want to see anymore |
1298 | + self._reviews[app].remove(review) |
1299 | + new_review = Review.from_piston_mini_client(mod_review) |
1300 | + self._reviews[app].insert(0, new_review) |
1301 | + callback(app, self._reviews[app], action='replace', |
1302 | + single_review=new_review) |
1303 | + break |
1304 | + |
1305 | + def _on_modify_review_error(self, spawn_helper, error_str, review_id, callback): |
1306 | + """called if modify review errors""" |
1307 | + LOG.debug("modify review id=%s failed with error: %s" % (review_id, error_str)) |
1308 | + for (app, reviews) in self._reviews.items(): |
1309 | + for review in reviews: |
1310 | + if str(review.id) == str(review_id): |
1311 | + review.modify_error = True |
1312 | + callback(app, self._reviews[app], action='replace', |
1313 | + single_review=review) |
1314 | + break |
1315 | |
1316 | === modified file 'softwarecenter/db/application.py' |
1317 | --- softwarecenter/db/application.py 2011-10-25 18:38:08 +0000 |
1318 | +++ softwarecenter/db/application.py 2011-10-27 07:59:24 +0000 |
1319 | @@ -252,7 +252,8 @@ |
1320 | # try apt first |
1321 | if self._pkg: |
1322 | for origin in self._pkg.candidate.origins: |
1323 | - if (origin.origin == "Ubuntu" and origin.trusted and origin.component): |
1324 | + if (origin.origin == get_distro().get_distro_channel_name() and |
1325 | + origin.trusted and origin.component): |
1326 | return origin.component |
1327 | # then xapian |
1328 | elif self._doc: |
1329 | |
1330 | === modified file 'softwarecenter/db/update.py' |
1331 | --- softwarecenter/db/update.py 2011-10-18 00:48:28 +0000 |
1332 | +++ softwarecenter/db/update.py 2011-10-27 07:59:24 +0000 |
1333 | @@ -401,7 +401,7 @@ |
1334 | except ImportError: |
1335 | return False |
1336 | if not listsdir: |
1337 | - listsdir = apt_pkg.Config.find_dir("Dir::State::lists") |
1338 | + listsdir = apt_pkg.config.find_dir("Dir::State::lists") |
1339 | context = GObject.main_context_default() |
1340 | for appinfo in glob("%s/*AppInfo" % listsdir): |
1341 | LOG.debug("processing %s" % appinfo) |
1342 | |
1343 | === modified file 'softwarecenter/distro/Debian.py' |
1344 | --- softwarecenter/distro/Debian.py 2011-09-21 15:15:28 +0000 |
1345 | +++ softwarecenter/distro/Debian.py 2011-10-27 07:59:24 +0000 |
1346 | @@ -31,6 +31,10 @@ |
1347 | SCREENSHOT_THUMB_URL = "http://screenshots.debian.net/thumbnail/%(pkgname)s" |
1348 | SCREENSHOT_LARGE_URL = "http://screenshots.debian.net/screenshot/%(pkgname)s" |
1349 | |
1350 | + REVIEWS_SERVER = "" |
1351 | + |
1352 | + DEVELOPER_URL = "http://www.debian.org/devel/" |
1353 | + |
1354 | def get_distro_channel_name(self): |
1355 | """ The name in the Release file """ |
1356 | return "Debian" |
1357 | @@ -68,19 +72,20 @@ |
1358 | |
1359 | def get_license_text(self, component): |
1360 | if component in ("main",): |
1361 | - return _("Open source") |
1362 | + return _("Meets the Debian Free Software Guidelines") |
1363 | elif component == "contrib": |
1364 | - return _("Open source, with proprietary parts") |
1365 | - elif component == "restricted": |
1366 | - return _("Proprietary") |
1367 | - |
1368 | - def get_maintenance_status(self, cache, appname, pkgname, component, channel): |
1369 | - return "" |
1370 | + return _("Meets the Debian Free Software Guidelines itself " |
1371 | + "but requires additional non-free software to work") |
1372 | + elif component == "non-free": |
1373 | + return _("Non-free since it is either restricted " |
1374 | + "in use, redistribution or modification.") |
1375 | |
1376 | def get_architecture(self): |
1377 | return apt.apt_pkg.config.find("Apt::Architecture") |
1378 | |
1379 | def get_foreign_architectures(self): |
1380 | + return [] |
1381 | + # Not yet in unstable |
1382 | import subprocess |
1383 | out = subprocess.Popen(['dpkg', '--print-foreign-architectures'], |
1384 | stdout=subprocess.PIPE).communicate()[0].rstrip('\n') |
1385 | @@ -88,6 +93,63 @@ |
1386 | return out.split(' ') |
1387 | return [] |
1388 | |
1389 | + def is_supported(self, cache, doc, pkgname): |
1390 | + # the doc does not by definition contain correct data regarding the |
1391 | + # section. Looking up in the cache seems just as fast/slow. |
1392 | + if pkgname in cache and cache[pkgname].candidate: |
1393 | + for origin in cache[pkgname].candidate.origins: |
1394 | + if (origin.origin == "Debian" and |
1395 | + origin.trusted and |
1396 | + origin.component == "main"): |
1397 | + return True |
1398 | + return False |
1399 | + |
1400 | + def get_maintenance_status(self, cache, appname, pkgname, component, channelname): |
1401 | + """Return the maintenance status of a package.""" |
1402 | + if not hasattr(cache, '_cache') or not pkgname: |
1403 | + return |
1404 | + try: |
1405 | + origins = cache[pkgname].candidate.origins |
1406 | + except KeyError, AttributeError: |
1407 | + return |
1408 | + else: |
1409 | + for origin in origins: |
1410 | + if (origin.origin == "Debian" and origin.trusted): |
1411 | + pkg_comp = origin.component |
1412 | + pkg_archive = origin.archive |
1413 | + break |
1414 | + else: |
1415 | + return |
1416 | + if pkg_comp in ("contrib", "non-free"): |
1417 | + if pkg_archive == "oldstable": |
1418 | + return _("Debian does not provide critical updates.") |
1419 | + else: |
1420 | + return _("Debian does not provide critical updates. " |
1421 | + "Some updates may be provided by the developers " |
1422 | + "of %s and redistributed by Debian.") % appname |
1423 | + elif pkg_comp == "main": |
1424 | + if pkg_archive == "stable": |
1425 | + return _("Debian provides critical updates for %s.") % appname |
1426 | + elif pkg_archive == "oldstable": |
1427 | + return _("Debian only provides updates for %s during " |
1428 | + "a transition phase. " |
1429 | + "Please consider upgrading to a later stable " |
1430 | + "release of Debian.") % appname |
1431 | + elif pkg_archive == "testing": |
1432 | + return _("Debian provides critical updates for %s. But " |
1433 | + "updates could be delayed or skipped.") % appname |
1434 | + elif pkg_archive == "unstable": |
1435 | + return _("Debian doens't provides critical updates " |
1436 | + "for %s") % appname |
1437 | + return |
1438 | + |
1439 | + def get_supported_query(self): |
1440 | + import xapian |
1441 | + query1 = xapian.Query("XOL"+"Debian") |
1442 | + query2 = xapian.Query("XOC"+"main") |
1443 | + return xapian.Query(xapian.Query.OP_AND, query1, query2) |
1444 | + |
1445 | + |
1446 | if __name__ == "__main__": |
1447 | cache = apt.Cache() |
1448 | print(cache.get_maintenance_status(cache, "synaptic app", "synaptic", "main", None)) |
1449 | |
1450 | === modified file 'softwarecenter/distro/Ubuntu.py' |
1451 | --- softwarecenter/distro/Ubuntu.py 2011-10-05 00:58:57 +0000 |
1452 | +++ softwarecenter/distro/Ubuntu.py 2011-10-27 07:59:24 +0000 |
1453 | @@ -56,6 +56,9 @@ |
1454 | # FIXME: does that make sense?!? |
1455 | REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats" |
1456 | |
1457 | + # Starting point for Ubuntu app developers |
1458 | + DEVELOPER_URL = "http://developer.ubuntu.com/" |
1459 | + |
1460 | def get_app_name(self): |
1461 | return _("Ubuntu Software Center") |
1462 | |
1463 | @@ -128,6 +131,9 @@ |
1464 | query2 = xapian.Query(xapian.Query.OP_OR, query2a, query2b) |
1465 | return xapian.Query(xapian.Query.OP_AND, query1, query2) |
1466 | |
1467 | + def get_supported_filter_name(self): |
1468 | + return _("Canonical-Maintained Software") |
1469 | + |
1470 | def get_maintenance_status(self, cache, appname, pkgname, component, channelname): |
1471 | # try to figure out the support dates of the release and make |
1472 | # sure to look only for stuff in "Ubuntu" and "distro_codename" |
1473 | |
1474 | === modified file 'softwarecenter/distro/__init__.py' |
1475 | --- softwarecenter/distro/__init__.py 2011-10-25 18:38:08 +0000 |
1476 | +++ softwarecenter/distro/__init__.py 2011-10-27 07:59:24 +0000 |
1477 | @@ -19,12 +19,15 @@ |
1478 | |
1479 | import logging |
1480 | import os |
1481 | -import subprocess |
1482 | |
1483 | from gettext import gettext as _ |
1484 | +import lsb_release |
1485 | |
1486 | from softwarecenter.utils import UnimplementedError, utf8 |
1487 | |
1488 | +log = logging.getLogger(__name__) |
1489 | + |
1490 | + |
1491 | class Distro(object): |
1492 | """ abstract base class for a distribution """ |
1493 | |
1494 | @@ -37,6 +40,16 @@ |
1495 | REVIEW_SUMMARY_STARS_BASE_PATH = "/usr/share/software-center/images/review-summary" |
1496 | REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://localhost:8000" |
1497 | |
1498 | + # You need to set this var to enable purchases |
1499 | + PURCHASE_APP_URL = "" |
1500 | + |
1501 | + # Point developers to a web page |
1502 | + DEVELOPER_URL = "" |
1503 | + |
1504 | + def __init__(self, lsb_info): |
1505 | + """Return a new generic Distro instance.""" |
1506 | + self.lsb_info = lsb_info |
1507 | + |
1508 | def get_app_name(self): |
1509 | """ |
1510 | The name of the application (as displayed in the main window and |
1511 | @@ -66,9 +79,8 @@ |
1512 | return os.environ["SOFTWARE_CENTER_DISTRO_CODENAME"] |
1513 | # normal behavior |
1514 | if not hasattr(self, "_distro_code_name"): |
1515 | - self._distro_code_name = subprocess.Popen( |
1516 | - ["lsb_release","-c","-s"], |
1517 | - stdout=subprocess.PIPE).communicate()[0].strip() |
1518 | + self._distro_code_name = \ |
1519 | + lsb_release.get_distro_information()["CODENAME"] |
1520 | return self._distro_code_name |
1521 | |
1522 | def get_maintenance_status(self, cache, appname, pkgname, component, channelname): |
1523 | @@ -89,6 +101,9 @@ |
1524 | import xapian |
1525 | return xapian.Query() |
1526 | |
1527 | + def get_supported_filter_name(self): |
1528 | + return _("Supported Software") |
1529 | + |
1530 | def get_install_warning_text(self, cache, pkg, appname, depends): |
1531 | primary = utf8(_("To install %s, these items must be removed:")) % utf8(appname) |
1532 | button_text = _("Install Anyway") |
1533 | @@ -140,15 +155,14 @@ |
1534 | |
1535 | |
1536 | def _get_distro(): |
1537 | - distro_id = subprocess.Popen(["lsb_release","-i","-s"], |
1538 | - stdout=subprocess.PIPE).communicate()[0] |
1539 | - distro_id = distro_id.strip().replace(' ', '') |
1540 | - logging.getLogger("softwarecenter.distro").debug("get_distro: '%s'" % distro_id) |
1541 | + lsb_info = lsb_release.get_distro_information() |
1542 | + distro_id = lsb_info["ID"] |
1543 | + log.debug("get_distro: '%s'", distro_id) |
1544 | # start with a import, this gives us only a softwarecenter module |
1545 | module = __import__(distro_id, globals(), locals(), [], -1) |
1546 | # get the right class and instanciate it |
1547 | distro_class = getattr(module, distro_id) |
1548 | - instance = distro_class() |
1549 | + instance = distro_class(lsb_info) |
1550 | return instance |
1551 | |
1552 | def get_distro(): |
1553 | |
1554 | === modified file 'softwarecenter/ui/gtk3/app.py' |
1555 | --- softwarecenter/ui/gtk3/app.py 2011-10-20 01:52:42 +0000 |
1556 | +++ softwarecenter/ui/gtk3/app.py 2011-10-27 07:59:24 +0000 |
1557 | @@ -342,10 +342,28 @@ |
1558 | self.config = get_config() |
1559 | self.restore_state() |
1560 | |
1561 | + # Adapt menu entries |
1562 | + supported_menuitem = self.builder.get_object("menuitem_view_supported_only") |
1563 | + supported_menuitem.set_label(self.distro.get_supported_filter_name()) |
1564 | + file_menu = self.builder.get_object("menu1") |
1565 | + |
1566 | + if not self.distro.DEVELOPER_URL: |
1567 | + help_menu = self.builder.get_object("menu_help") |
1568 | + developer_separator = self.builder.get_object("separator_developer") |
1569 | + help_menu.remove(developer_separator) |
1570 | + developer_menuitem = self.builder.get_object("menuitem_developer") |
1571 | + help_menu.remove(developer_menuitem) |
1572 | + |
1573 | + # Check if oneconf is available |
1574 | + och = get_oneconf_handler() |
1575 | + if not och: |
1576 | + file_menu.remove(self.builder.get_object("menuitem_sync_between_computers")) |
1577 | + |
1578 | # run s-c-agent update |
1579 | - if options.disable_buy: |
1580 | - file_menu = self.builder.get_object("menu1") |
1581 | + if options.disable_buy or not self.distro.PURCHASE_APP_URL: |
1582 | file_menu.remove(self.builder.get_object("menuitem_reinstall_purchases")) |
1583 | + if not (options.enable_lp or och): |
1584 | + file_menu.remove(self.builder.get_object("separator_login")) |
1585 | else: |
1586 | sc_agent_update = os.path.join( |
1587 | self.datadir, "update-software-center-agent") |
1588 | @@ -355,9 +373,6 @@ |
1589 | GObject.child_watch_add( |
1590 | pid, self._on_update_software_center_agent_finished) |
1591 | |
1592 | - if options.disable_buy and not options.enable_lp: |
1593 | - file_menu.remove(self.builder.get_object("separator_login")) |
1594 | - |
1595 | # TODO: Remove the following two lines once we have remove repository |
1596 | # support in aptdaemon (see LP: #723911) |
1597 | file_menu = self.builder.get_object("menu1") |
1598 | @@ -1031,8 +1046,8 @@ |
1599 | GObject.timeout_add_seconds(1, lambda p: p.poll() == None, p) |
1600 | |
1601 | def on_menuitem_developer_activate(self, menuitem): |
1602 | - webbrowser.open("http://developer.ubuntu.com/") |
1603 | - |
1604 | + webbrowser.open(self.distro.DEVELOPER_URL) |
1605 | + |
1606 | def _ask_and_repair_broken_cache(self): |
1607 | # wait until the window window is available |
1608 | if self.window_main.props.visible == False: |
1609 | |
1610 | === modified file 'softwarecenter/ui/gtk3/panes/installedpane.py' |
1611 | --- softwarecenter/ui/gtk3/panes/installedpane.py 2011-10-06 02:35:27 +0000 |
1612 | +++ softwarecenter/ui/gtk3/panes/installedpane.py 2011-10-27 07:59:24 +0000 |
1613 | @@ -132,8 +132,9 @@ |
1614 | |
1615 | # Start OneConf |
1616 | self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler) |
1617 | - self.oneconf_handler.connect('show-oneconf-changed', self._show_oneconf_changed) |
1618 | - self.oneconf_handler.connect('last-time-sync-changed', self._last_time_sync_oneconf_changed) |
1619 | + if self.oneconf_handler: |
1620 | + self.oneconf_handler.connect('show-oneconf-changed', self._show_oneconf_changed) |
1621 | + self.oneconf_handler.connect('last-time-sync-changed', self._last_time_sync_oneconf_changed) |
1622 | |
1623 | # OneConf pane |
1624 | self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL) |
1625 | |
1626 | === modified file 'softwarecenter/ui/gtk3/views/appdetailsview_gtk.py' |
1627 | --- softwarecenter/ui/gtk3/views/appdetailsview_gtk.py 2011-10-20 16:08:23 +0000 |
1628 | +++ softwarecenter/ui/gtk3/views/appdetailsview_gtk.py 2011-10-27 07:59:24 +0000 |
1629 | @@ -1051,7 +1051,8 @@ |
1630 | self.reviews.connect("more-reviews-clicked", self._on_more_reviews_clicked) |
1631 | self.reviews.connect("different-review-language-clicked", self._on_reviews_in_different_language_clicked) |
1632 | self.reviews.connect("review-sort-changed", self._on_review_sort_method_changed) |
1633 | - vb.pack_start(self.reviews, False, False, 0) |
1634 | + if get_distro().REVIEWS_SERVER: |
1635 | + vb.pack_start(self.reviews, False, False, 0) |
1636 | |
1637 | self.show_all() |
1638 |