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