Merge lp:~mvo/software-center/expunge-cache-5.0 into lp:software-center

Proposed by Michael Vogt
Status: Superseded
Proposed branch: lp:~mvo/software-center/expunge-cache-5.0
Merge into: lp:software-center
Diff against target: 1104 lines (+786/-86) (has conflicts)
13 files modified
debian/changelog (+42/-2)
softwarecenter/backend/scagent.py (+7/-0)
softwarecenter/db/update.py (+163/-0)
softwarecenter/ui/gtk3/app.py (+30/-0)
softwarecenter/ui/gtk3/panes/availablepane.py (+12/-3)
softwarecenter/ui/gtk3/panes/installedpane.py (+17/-0)
softwarecenter/ui/gtk3/panes/softwarepane.py (+90/-1)
softwarecenter/ui/gtk3/views/purchaseview.py (+17/-0)
softwarecenter/ui/gtk3/widgets/buttons.py (+5/-0)
test/test_database.py (+4/-0)
test/test_utils.py (+39/-0)
utils/expunge-cache.py (+166/-80)
utils/piston-helpers/piston_get_scagent_available_apps.py.OTHER (+194/-0)
Text conflict in debian/changelog
Text conflict in softwarecenter/backend/scagent.py
Text conflict in softwarecenter/db/update.py
Text conflict in softwarecenter/ui/gtk3/app.py
Text conflict in softwarecenter/ui/gtk3/panes/availablepane.py
Text conflict in softwarecenter/ui/gtk3/panes/installedpane.py
Text conflict in softwarecenter/ui/gtk3/panes/softwarepane.py
Text conflict in softwarecenter/ui/gtk3/views/purchaseview.py
Text conflict in softwarecenter/ui/gtk3/widgets/buttons.py
Text conflict in test/test_database.py
Text conflict in utils/expunge-cache.py
Contents conflict in utils/piston-helpers/piston_get_scagent_available_apps.py
To merge this branch: bzr merge lp:~mvo/software-center/expunge-cache-5.0
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Review via email: mp+95966@code.launchpad.net

This proposal has been superseded by a proposal from 2012-03-06.

Description of the change

Trivial addition to be more gently on the CPU with os.nice()

To post a comment you must log in.
Revision history for this message
Gary Lasker (gary-lasker) wrote :

Just a note that I merged the lp:~mvo/software-center/expunge-cache branch, which is the one targeting trunk. Thanks!

review: Approve

Unmerged revisions

2491. By Michael Vogt

add os.nice()

2490. By Michael Vogt

merged expunge-cache branch for 5.0

2489. By Gary Lasker

    testing (LP: #918746)

2488. By Michael Vogt

* lp:~gary-lasker/software-center/staging-certs-2-for-5.0:
  - add SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK to allow QA easier
    testing

2487. By Michael Vogt

grab exhibits for the current series only (LP: #899257)

2486. By Michael Vogt

* lp:~mvo/software-center/fix-server-pagination:
  - reset server side review "page" when switching to a different
    app

2485. By Michael Vogt

* lp:~gary-lasker/software-center/fix-lp913756-for-5.0:
  - do not offer to add an icon to the Unity launcher for packages
    that do not have an Exec entry in their corresponding desktop
    file, e.g. ubuntu-restricted-extras, wine (LP: #913756)

2484. By Michael Vogt

releasing version 5.0.4

2483. By Gary Lasker

    download icons directly using the provided URL (LP: #914054)

2482. By Michael Vogt

test fixes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2012-03-01 19:53:28 +0000
3+++ debian/changelog 2012-03-05 18:23:26 +0000
4@@ -1,3 +1,4 @@
5+<<<<<<< TREE
6 software-center (5.1.11) precise; urgency=low
7
8 [ Michael Vogt ]
9@@ -553,11 +554,50 @@
10
11 software-center (5.0.3.2) UNRELEASED; urgency=low
12
13+=======
14+software-center (5.0.5) UNRELEASED; urgency=low
15+
16+ [ Gary Lasker ]
17+ * lp:~gary-lasker/software-center/fix-lp913756-for-5.0:
18+ - do not offer to add an icon to the Unity launcher for packages
19+ that do not have an Exec entry in their corresponding desktop
20+ file, e.g. ubuntu-restricted-extras, wine (LP: #913756)
21+ * lp:~gary-lasker/software-center/staging-certs-2-for-5.0:
22+ - add SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK to allow QA easier
23+ testing (LP: #918746)
24+
25+ [ Michael Vogt ]
26+ * lp:~mvo/software-center/fix-server-pagination:
27+ - reset server side review "page" when switching to a different
28+ app
29+
30+ [ Kiwinote ]
31+ * grab exhibits for the current series only (LP: #899257)
32+
33+ -- Gary Lasker <gary.lasker@canonical.com> Thu, 19 Jan 2012 10:54:47 -0500
34+
35+software-center (5.0.4) oneiric-proposed; urgency=low
36+
37+ [ Gary Lasker ]
38+>>>>>>> MERGE-SOURCE
39 * lp:~gary-lasker/software-center/fix-lp891499-for-5.0:
40 - be more robust about problems reading the cataloged_times file
41 as problems here can hang the UI (LP: #891499)
42-
43- -- Gary Lasker <gary.lasker@canonical.com> Mon, 28 Nov 2011 14:22:24 -0500
44+
45+ [ Gabor Kelemen ]
46+ * lp:~kelemeng/software-center/bug869935:
47+ - Update help translations from Launchpad (LP: #869935)
48+ * lp:~kelemeng/software-center/bug880757:
49+ - Mark strings containing the “ character as Unicode, to fix
50+ their translations. LP: #880757
51+
52+ [ Michael Vogt ]
53+ * fix cache opening to improve startup time
54+ * lp:~mvo/software-center/icon-data-for-5.0:
55+ - remove the need for inline icon data from the agent, instead
56+ download icons directly using the provided URL (LP: #914054)
57+
58+ -- Michael Vogt <michael.vogt@ubuntu.com> Tue, 10 Jan 2012 09:52:09 +0100
59
60 software-center (5.0.3.1) oneiric-proposed; urgency=low
61
62
63=== modified file 'setup.py'
64=== modified file 'softwarecenter/backend/scagent.py'
65--- softwarecenter/backend/scagent.py 2012-01-17 15:56:14 +0000
66+++ softwarecenter/backend/scagent.py 2012-03-05 18:23:26 +0000
67@@ -107,6 +107,13 @@
68 self.emit("available-for-me", piston_available_for_me)
69
70 def query_exhibits(self):
71+<<<<<<< TREE
72+=======
73+ cmd = self.HELPER_CMD[:]
74+ cmd.append("exhibits")
75+ cmd.append(get_language())
76+ cmd.append(self.distro.get_codename())
77+>>>>>>> MERGE-SOURCE
78 spawner = SpawnHelper()
79 spawner.parent_xid = self.xid
80 spawner.ignore_cache = self.ignore_cache
81
82=== modified file 'softwarecenter/db/update.py'
83--- softwarecenter/db/update.py 2012-02-28 22:35:19 +0000
84+++ softwarecenter/db/update.py 2012-03-05 18:23:26 +0000
85@@ -672,6 +672,11 @@
86 while context.pending():
87 context.iteration()
88 try:
89+<<<<<<< TREE
90+=======
91+ # magic channel
92+ entry.channel = AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME
93+>>>>>>> MERGE-SOURCE
94 # now the normal parser
95 parser = SCAApplicationParser(entry)
96 index_app_info_from_parser(parser, db, cache)
97@@ -932,8 +937,166 @@
98 seen.add(name)
99
100 index_name(doc, name, term_generator)
101+<<<<<<< TREE
102
103 pkgname = doc.get_value(XapianValues.PKGNAME)
104+=======
105+ doc.add_value(XapianValues.APPNAME_UNTRANSLATED, untranslated_name)
106+
107+ # check if we should ignore this file
108+ if parser.has_option_desktop("X-AppInstall-Ignore"):
109+ ignore = parser.get_desktop("X-AppInstall-Ignore")
110+ if ignore.strip().lower() == "true":
111+ LOG.debug("X-AppInstall-Ignore found for '%s'" % parser.desktopf)
112+ return
113+ # architecture
114+ pkgname_extension = ''
115+ if parser.has_option_desktop("X-AppInstall-Architectures"):
116+ arches = parser.get_desktop("X-AppInstall-Architectures")
117+ doc.add_value(XapianValues.ARCHIVE_ARCH, arches)
118+ native_archs = get_current_arch() in arches.split(',')
119+ foreign_archs = list(set(arches.split(',')) & set(get_foreign_architectures()))
120+ if not (native_archs or foreign_archs): return
121+ if not native_archs and foreign_archs:
122+ pkgname_extension = ':' + foreign_archs[0]
123+ # package name
124+ pkgname = parser.get_desktop("X-AppInstall-Package") + pkgname_extension
125+ doc.add_term("AP"+pkgname)
126+ if '-' in pkgname:
127+ # we need this to work around xapian oddness
128+ doc.add_term(pkgname.replace('-','_'))
129+ doc.add_value(XapianValues.PKGNAME, pkgname)
130+ doc.add_value(XapianValues.DESKTOP_FILE, parser.desktopf)
131+ # display name
132+ if "display_name" in axi_values:
133+ doc.add_value(axi_values["display_name"], name)
134+ # cataloged_times
135+ if "catalogedtime" in axi_values:
136+ if pkgname in cataloged_times:
137+ doc.add_value(axi_values["catalogedtime"],
138+ xapian.sortable_serialise(cataloged_times[pkgname]))
139+ else:
140+ # also catalog apps not found in axi (e.g. for-purchase apps)
141+ doc.add_value(axi_values["catalogedtime"],
142+ xapian.sortable_serialise(time.time()))
143+ # pocket (main, restricted, ...)
144+ if parser.has_option_desktop("X-AppInstall-Section"):
145+ archive_section = parser.get_desktop("X-AppInstall-Section")
146+ doc.add_term("AS"+archive_section)
147+ doc.add_value(XapianValues.ARCHIVE_SECTION, archive_section)
148+ # section (mail, base, ..)
149+ if pkgname in cache and cache[pkgname].candidate:
150+ section = cache[pkgname].section
151+ doc.add_term("AE"+section)
152+ # channel (third party stuff)
153+ if parser.has_option_desktop("X-AppInstall-Channel"):
154+ archive_channel = parser.get_desktop("X-AppInstall-Channel")
155+ doc.add_term("AH"+archive_channel)
156+ doc.add_value(XapianValues.ARCHIVE_CHANNEL, archive_channel)
157+ # signing key (third party)
158+ if parser.has_option_desktop("X-AppInstall-Signing-Key-Id"):
159+ keyid = parser.get_desktop("X-AppInstall-Signing-Key-Id")
160+ doc.add_value(XapianValues.ARCHIVE_SIGNING_KEY_ID, keyid)
161+ # license (third party)
162+ if parser.has_option_desktop("X-AppInstall-License"):
163+ license = parser.get_desktop("X-AppInstall-License")
164+ doc.add_value(XapianValues.LICENSE, license)
165+ # purchased date
166+ if parser.has_option_desktop("X-AppInstall-Purchased-Date"):
167+ date = parser.get_desktop("X-AppInstall-Purchased-Date")
168+ # strip the subseconds from the end of the date string
169+ doc.add_value(XapianValues.PURCHASED_DATE, str(date).split(".")[0])
170+ # deb-line (third party)
171+ if parser.has_option_desktop("X-AppInstall-Deb-Line"):
172+ debline = parser.get_desktop("X-AppInstall-Deb-Line")
173+ doc.add_value(XapianValues.ARCHIVE_DEB_LINE, debline)
174+ # license key (third party)
175+ if parser.has_option_desktop("X-AppInstall-License-Key"):
176+ key = parser.get_desktop("X-AppInstall-License-Key")
177+ doc.add_value(XapianValues.LICENSE_KEY, key)
178+ # license keypath (third party)
179+ if parser.has_option_desktop("X-AppInstall-License-Key-Path"):
180+ path = parser.get_desktop("X-AppInstall-License-Key-Path")
181+ doc.add_value(XapianValues.LICENSE_KEY_PATH, path)
182+ # PPA (third party stuff)
183+ if parser.has_option_desktop("X-AppInstall-PPA"):
184+ archive_ppa = parser.get_desktop("X-AppInstall-PPA")
185+ doc.add_value(XapianValues.ARCHIVE_PPA, archive_ppa)
186+ # add archive origin data here so that its available even if
187+ # the PPA is not (yet) enabled
188+ doc.add_term("XOO"+"lp-ppa-%s" % archive_ppa.replace("/", "-"))
189+ # screenshot (for third party)
190+ if parser.has_option_desktop("X-AppInstall-Screenshot-Url"):
191+ url = parser.get_desktop("X-AppInstall-Screenshot-Url")
192+ doc.add_value(XapianValues.SCREENSHOT_URL, url)
193+ # thumbnail (for third party)
194+ if parser.has_option_desktop("X-AppInstall-Thumbnail-Url"):
195+ url = parser.get_desktop("X-AppInstall-Thumbnail-Url")
196+ doc.add_value(XapianValues.THUMBNAIL_URL, url)
197+ # icon (for third party)
198+ if parser.has_option_desktop("X-AppInstall-Icon-Url"):
199+ url = parser.get_desktop("X-AppInstall-Icon-Url")
200+ doc.add_value(XapianValues.ICON_URL, url)
201+ if not parser.has_option_desktop("X-AppInstall-Icon"):
202+ # prefix pkgname to avoid name clashes
203+ doc.add_value(XapianValues.ICON, "%s-icon-%s" % (
204+ pkgname, os.path.basename(url)))
205+
206+ # price (pay stuff)
207+ if parser.has_option_desktop("X-AppInstall-Price"):
208+ price = parser.get_desktop("X-AppInstall-Price")
209+ doc.add_value(XapianValues.PRICE, price)
210+ # since this is a commercial app, indicate it in the component value
211+ doc.add_value(XapianValues.ARCHIVE_SECTION, "commercial")
212+ # icon
213+ if parser.has_option_desktop("Icon"):
214+ icon = parser.get_desktop("Icon")
215+ doc.add_value(XapianValues.ICON, icon)
216+ # write out categories
217+ for cat in parser.get_desktop_categories():
218+ doc.add_term("AC"+cat.lower())
219+ categories_string = ";".join(parser.get_desktop_categories())
220+ doc.add_value(XapianValues.CATEGORIES, categories_string)
221+ for mime in parser.get_desktop_mimetypes():
222+ doc.add_term("AM"+mime.lower())
223+ # get type (to distinguish between apps and packages
224+ if parser.has_option_desktop("Type"):
225+ type = parser.get_desktop("Type")
226+ doc.add_term("AT"+type.lower())
227+ # check gettext domain
228+ if parser.has_option_desktop("X-Ubuntu-Gettext-Domain"):
229+ domain = parser.get_desktop("X-Ubuntu-Gettext-Domain")
230+ doc.add_value(XapianValues.GETTEXT_DOMAIN, domain)
231+ # Description (software-center extension)
232+ if parser.has_option_desktop("X-AppInstall-Description"):
233+ descr = parser.get_desktop("X-AppInstall-Description")
234+ doc.add_value(XapianValues.SC_DESCRIPTION, descr)
235+ # popcon
236+ # FIXME: popularity not only based on popcon but also
237+ # on archive section, third party app etc
238+ if parser.has_option_desktop("X-AppInstall-Popcon"):
239+ popcon = float(parser.get_desktop("X-AppInstall-Popcon"))
240+ # sort_by_value uses string compare, so we need to pad here
241+ doc.add_value(XapianValues.POPCON,
242+ xapian.sortable_serialise(popcon))
243+ global popcon_max
244+ popcon_max = max(popcon_max, popcon)
245+
246+ # comment goes into the summary data if there is one,
247+ # other wise we try GenericName and if nothing else,
248+ # the summary of the package
249+ if parser.has_option_desktop("Comment"):
250+ s = parser.get_desktop("Comment")
251+ doc.add_value(XapianValues.SUMMARY, s)
252+ elif parser.has_option_desktop("GenericName"):
253+ s = parser.get_desktop("GenericName")
254+ if s != name:
255+ doc.add_value(XapianValues.SUMMARY, s)
256+ elif pkgname in cache and cache[pkgname].candidate:
257+ s = cache[pkgname].candidate.summary
258+ doc.add_value(XapianValues.SUMMARY, s)
259+
260+>>>>>>> MERGE-SOURCE
261 # add packagename as meta-data too
262 term_generator.index_text_without_positions(pkgname, WEIGHT_APT_PKGNAME)
263
264
265=== modified file 'softwarecenter/ui/gtk3/app.py'
266--- softwarecenter/ui/gtk3/app.py 2012-02-29 17:23:24 +0000
267+++ softwarecenter/ui/gtk3/app.py 2012-03-05 18:23:26 +0000
268@@ -387,6 +387,7 @@
269 if not (options.enable_lp or och):
270 file_menu.remove(self.builder.get_object("separator_login"))
271 else:
272+<<<<<<< TREE
273 # running the agent will trigger a db reload so we do it later
274 GObject.timeout_add_seconds(30, self._run_software_center_agent)
275
276@@ -394,6 +395,22 @@
277 # keep the cache clean
278 GObject.timeout_add_seconds(15, self._run_expunge_cache_helper)
279
280+=======
281+ sc_agent_update = os.path.join(
282+ self.datadir, "update-software-center-agent")
283+ (pid, stdin, stdout, stderr) = GObject.spawn_async(
284+ [sc_agent_update, "--datadir", datadir],
285+ flags=GObject.SPAWN_DO_NOT_REAP_CHILD)
286+ GObject.child_watch_add(
287+ pid, self._on_update_software_center_agent_finished)
288+
289+ if options.disable_buy and not options.enable_lp:
290+ file_menu.remove(self.builder.get_object("separator_login"))
291+
292+ # keep the cache clean
293+ GObject.timeout_add_seconds(45, self._run_expunge_cache_helper)
294+
295+>>>>>>> MERGE-SOURCE
296 # TODO: Remove the following two lines once we have remove repository
297 # support in aptdaemon (see LP: #723911)
298 file_menu = self.builder.get_object("menu1")
299@@ -413,6 +430,7 @@
300
301
302 # helper
303+<<<<<<< TREE
304 def _run_software_center_agent(self):
305 """ helper that triggers the update-software-center-agent helper """
306 sc_agent_update = os.path.join(
307@@ -433,6 +451,18 @@
308 softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR,
309 ])
310
311+=======
312+ def _run_expunge_cache_helper(self):
313+ """ helper that expires the piston-mini-client cache """
314+ sc_expunge_cache = os.path.join(
315+ self.datadir, "expunge-cache.py")
316+ (pid, stdin, stdout, stderr) = GObject.spawn_async(
317+ [sc_expunge_cache,
318+ "--by-unsuccessful-http-states",
319+ softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR,
320+ ])
321+
322+>>>>>>> MERGE-SOURCE
323 def _rebuild_and_reopen_local_db(self, pathname):
324 """ helper that rebuilds a db and reopens it """
325 from softwarecenter.db.update import rebuild_database
326
327=== modified file 'softwarecenter/ui/gtk3/models/appstore2.py'
328=== modified file 'softwarecenter/ui/gtk3/panes/availablepane.py'
329--- softwarecenter/ui/gtk3/panes/availablepane.py 2012-02-28 13:14:06 +0000
330+++ softwarecenter/ui/gtk3/panes/availablepane.py 2012-03-05 18:23:26 +0000
331@@ -134,9 +134,18 @@
332 #~ self.app_view._append_appcount(appcount)
333 #~ liststore.connect('appcount-changed', on_appcount_changed)
334 self.app_view.set_model(liststore)
335- liststore.connect(
336- "needs-refresh", lambda helper, pkgname: self.app_view.queue_draw())
337-
338+<<<<<<< TREE
339+ liststore.connect(
340+ "needs-refresh", lambda helper, pkgname: self.app_view.queue_draw())
341+
342+=======
343+ # setup purchase stuff
344+ self.app_details_view.connect("purchase-requested",
345+ self.on_purchase_requested)
346+ liststore.connect(
347+ "needs-refresh", lambda helper, pkgname: self.app_view.queue_draw())
348+
349+>>>>>>> MERGE-SOURCE
350 # purchase view
351 self.purchase_view = PurchaseView()
352 app_manager = get_appmanager()
353
354=== modified file 'softwarecenter/ui/gtk3/panes/installedpane.py'
355--- softwarecenter/ui/gtk3/panes/installedpane.py 2012-02-28 13:51:20 +0000
356+++ softwarecenter/ui/gtk3/panes/installedpane.py 2012-03-05 18:23:26 +0000
357@@ -156,9 +156,14 @@
358 oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
359 oneconfpropertymenu = Gtk.Menu()
360 self.oneconfproperty = MenuButton(oneconfpropertymenu, Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, Gtk.IconSize.BUTTON))
361+<<<<<<< TREE
362 self.stopsync_label = _(u"Stop Syncing “%s”")
363 stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label % platform.node())
364 stop_oneconf_share_menuitem.connect("activate", self._on_stop_oneconf_hostshare_clicked)
365+=======
366+ stop_oneconf_share_menuitem = Gtk.MenuItem(label=_(u"Stop Syncing “%s”") % platform.node())
367+ stop_oneconf_share_menuitem.connect("activate", self._on_stop_showing_oneconf_clicked)
368+>>>>>>> MERGE-SOURCE
369 stop_oneconf_share_menuitem.show()
370 oneconfpropertymenu.append(stop_oneconf_share_menuitem)
371 self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1)
372@@ -433,9 +438,15 @@
373 L = len(enq.matches)
374
375 if L:
376+<<<<<<< TREE
377 cat_title = utf8(ngettext(u'%(amount)s item on “%(machine)s” not on this computer',
378 u'%(amount)s items on “%(machine)s” not on this computer',
379 L)) % { 'amount' : L, 'machine': utf8(self.current_hostname)}
380+=======
381+ cat_title = ngettext(u'%(amount)s item on “%(machine)s” not on this computer',
382+ u'%(amount)s items on “%(machine)s” not on this computer',
383+ L) % { 'amount' : L, 'machine': self.current_hostname}
384+>>>>>>> MERGE-SOURCE
385 i += L
386 docs = enq.get_documents()
387 self.cat_docid_map["missingpkg"] = set([doc.get_docid() for doc in docs])
388@@ -455,9 +466,15 @@
389
390 L = len(enq.matches)
391 if L:
392+<<<<<<< TREE
393 cat_title = utf8(ngettext(u'%(amount)s item on this computer not on “%(machine)s”',
394 '%(amount)s items on this computer not on “%(machine)s”',
395 L)) % { 'amount' : L, 'machine': utf8(self.current_hostname)}
396+=======
397+ cat_title = ngettext(u'%(amount)s item on this computer not on “%(machine)s”',
398+ u'%(amount)s items on this computer not on “%(machine)s”',
399+ L) % { 'amount' : L, 'machine': self.current_hostname}
400+>>>>>>> MERGE-SOURCE
401 i += L
402 docs = enq.get_documents()
403 self.cat_docid_map["additionalpkg"] = set([doc.get_docid() for doc in docs])
404
405=== modified file 'softwarecenter/ui/gtk3/panes/softwarepane.py'
406--- softwarecenter/ui/gtk3/panes/softwarepane.py 2012-02-28 13:51:20 +0000
407+++ softwarecenter/ui/gtk3/panes/softwarepane.py 2012-03-05 18:23:26 +0000
408@@ -34,7 +34,8 @@
409
410 from softwarecenter.utils import (ExecutionTime,
411 wait_for_apt_cache_ready,
412- utf8
413+ utf8,
414+ get_exec_line_from_desktop
415 )
416
417 from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager
418@@ -43,8 +44,15 @@
419 from softwarecenter.ui.gtk3.widgets.searchaid import SearchAid
420
421 from softwarecenter.ui.gtk3.views.appview import AppView
422+<<<<<<< TREE
423 from softwarecenter.ui.gtk3.views.appdetailsview import AppDetailsView
424
425+=======
426+from softwarecenter.ui.gtk3.views.appdetailsview_gtk import (
427+ AppDetailsViewGtk as
428+ AppDetailsView)
429+
430+>>>>>>> MERGE-SOURCE
431 from basepane import BasePane
432
433 LOG = logging.getLogger(__name__)
434@@ -283,6 +291,87 @@
435 vm = get_viewmanager()
436 vm.nav_forward()
437
438+<<<<<<< TREE
439+=======
440+ def on_transaction_started(self, backend, pkgname, appname, trans_id,
441+ trans_type):
442+ self._register_unity_launcher_transaction_started(
443+ backend, pkgname, appname, trans_id, trans_type)
444+
445+
446+ def _get_onscreen_icon_details_for_launcher_service(self, app):
447+ if self.is_app_details_view_showing():
448+ return self.app_details_view.get_app_icon_details()
449+ else:
450+ # TODO: implement the app list view case once it has been specified
451+ return (0, 0, 0)
452+
453+ def _register_unity_launcher_transaction_started(self, backend, pkgname,
454+ appname, trans_id,
455+ trans_type):
456+ # mvo: use use softwarecenter.utils explictely so that we can monkey
457+ # patch it in the test
458+ if not softwarecenter.utils.is_unity_running():
459+ return
460+ # add to launcher only applies in the details view currently
461+ if not self.is_app_details_view_showing():
462+ return
463+ # we only care about getting the launcher information on an install
464+ if not trans_type == TransactionTypes.INSTALL:
465+ if pkgname in self.unity_launcher_items:
466+ self.unity_launcher_items.pop(pkgname)
467+ self.action_bar.clear()
468+ return
469+ # gather details for this transaction and create the launcher_info object
470+ app = Application(pkgname=pkgname, appname=appname)
471+ appdetails = app.get_details(self.db)
472+ (icon_size, icon_x, icon_y) = self._get_onscreen_icon_details_for_launcher_service(app)
473+ launcher_info = UnityLauncherInfo(app.name,
474+ appdetails.icon,
475+ "", # we set the icon_file_path value *after* install
476+ icon_x,
477+ icon_y,
478+ icon_size,
479+ appdetails.desktop_file,
480+ "", # we set the installed_desktop_file_path *after* install
481+ trans_id)
482+ self.unity_launcher_items[app.pkgname] = launcher_info
483+ self.show_add_to_launcher_panel(backend, pkgname, appname, app, appdetails, trans_id, trans_type)
484+
485+ def show_add_to_launcher_panel(self, backend, pkgname, appname, app, appdetails, trans_id, trans_type):
486+ """
487+ if Unity is currently running, display a panel to allow the user
488+ the choose whether to add a newly-installed application to the
489+ launcher
490+ """
491+ # TODO: handle local deb install case
492+ # TODO: implement the list view case (once it is specified)
493+ # only show the panel if unity is running and this is a package install
494+ #
495+ # we only show the prompt for apps with a desktop file
496+ if not appdetails.desktop_file:
497+ return
498+ # do not add apps that have no Exec entry in their desktop file
499+ # (e.g. wine, see LP: #848437 or ubuntu-restricted-extras,
500+ # see LP: #913756)
501+ if (os.path.exists(appdetails.desktop_file) and
502+ not get_exec_line_from_desktop(appdetails.desktop_file)):
503+ return
504+
505+ self.action_bar.add_button(ActionButtons.CANCEL_ADD_TO_LAUNCHER,
506+ _("Not Now"),
507+ self.on_cancel_add_to_launcher,
508+ pkgname)
509+ self.action_bar.add_button(ActionButtons.ADD_TO_LAUNCHER,
510+ _("Add to Launcher"),
511+ self.on_add_to_launcher,
512+ pkgname,
513+ app,
514+ appdetails,
515+ trans_id)
516+ self.action_bar.set_label(utf8(_("Add %s to the launcher?")) % utf8(app.name))
517+
518+>>>>>>> MERGE-SOURCE
519 def on_query_complete(self, enquirer):
520 self.emit("app-list-changed", len(enquirer.matches))
521 self.app_view.display_matches(enquirer.matches,
522
523=== modified file 'softwarecenter/ui/gtk3/views/appdetailsview.py'
524=== modified file 'softwarecenter/ui/gtk3/views/purchaseview.py'
525--- softwarecenter/ui/gtk3/views/purchaseview.py 2012-02-09 10:34:40 +0000
526+++ softwarecenter/ui/gtk3/views/purchaseview.py 2012-03-05 18:23:26 +0000
527@@ -20,14 +20,21 @@
528 from gi.repository import GObject
529 from gi.repository import Gtk
530 from gi.repository import Gdk
531+<<<<<<< TREE
532 from gi.repository import Pango
533+=======
534+from gi.repository import WebKit as webkit
535+>>>>>>> MERGE-SOURCE
536 import logging
537 import os
538 import json
539 import sys
540 import urllib
541+<<<<<<< TREE
542 import urlparse
543 from gi.repository import WebKit as webkit
544+=======
545+>>>>>>> MERGE-SOURCE
546
547 from gettext import gettext as _
548
549@@ -36,6 +43,16 @@
550
551 LOG = logging.getLogger(__name__)
552
553+# enable certificates validation in webkit views unless specified otherwise
554+if not "SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK" in os.environ:
555+ session = webkit.get_default_session()
556+ session.set_property("ssl-ca-file", "/etc/ssl/certs/ca-certificates.crt")
557+else:
558+ # WARN the user!! Do not remove this
559+ LOG.warning("SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK " +
560+ "has been specified, all purchase transactions " +
561+ "are now INSECURE and UNENCRYPTED!!")
562+
563 class LocaleAwareWebView(webkit.WebView):
564
565 def __init__(self):
566
567=== modified file 'softwarecenter/ui/gtk3/widgets/buttons.py'
568--- softwarecenter/ui/gtk3/widgets/buttons.py 2012-02-20 15:59:17 +0000
569+++ softwarecenter/ui/gtk3/widgets/buttons.py 2012-03-05 18:23:26 +0000
570@@ -186,8 +186,13 @@
571 label = helper.get_appname(doc)
572 icon = helper.get_icon_at_size(doc, icon_size, icon_size)
573 stats = helper.get_review_stats(doc)
574+<<<<<<< TREE
575 helper.update_availability(doc)
576 helper.connect("needs-refresh", self._on_needs_refresh, doc, icon_size)
577+=======
578+ doc.installed = doc.available = None
579+ helper.connect("needs-refresh", self._on_needs_refresh, doc, icon_size)
580+>>>>>>> MERGE-SOURCE
581 self.is_installed = helper.is_installed(doc)
582 self._overlay = helper.icons.load_icon(Icons.INSTALLED_OVERLAY,
583 self.INSTALLED_OVERLAY_SIZE,
584
585=== modified file 'test/test_database.py'
586--- test/test_database.py 2012-02-28 22:35:19 +0000
587+++ test/test_database.py 2012-03-05 18:23:26 +0000
588@@ -150,12 +150,16 @@
589 ppa.count("/") == 1,
590 "ARCHIVE_PPA value incorrect, got '%s'" % ppa)
591 self.assertTrue(
592+<<<<<<< TREE
593 "-icon-" in doc.get_value(XapianValues.ICON))
594 # check support url in the DB
595 url=doc.get_value(XapianValues.SUPPORT_SITE_URL)
596 if url:
597 self.assertTrue(url.startswith("http") or
598 url.startswith("mailto:"))
599+=======
600+ "-icon-" in doc.get_value(XapianValues.ICON))
601+>>>>>>> MERGE-SOURCE
602
603 def test_license_string_data_from_software_center_agent(self):
604 #os.environ["SOFTWARE_CENTER_DEBUG_HTTP"] = "1"
605
606=== modified file 'test/test_utils.py'
607--- test/test_utils.py 2012-03-01 11:32:37 +0000
608+++ test/test_utils.py 2012-03-05 18:23:26 +0000
609@@ -161,6 +161,45 @@
610 self.assertTrue(os.path.exists(os.path.join(dirname, "foo-random")))
611
612
613+class TestExpungeCache(unittest.TestCase):
614+
615+ def test_expunge_cache(self):
616+ import subprocess
617+ import tempfile
618+ dirname = tempfile.mkdtemp('s-c-testsuite')
619+ for name, content in [ ("foo-301", "status: 301"),
620+ ("foo-200", "status: 200"),
621+ ("foo-random", "random"),
622+ ]:
623+ fullpath = os.path.join(dirname, name)
624+ open(fullpath, "w").write(content)
625+ # set to 1970+1s time to ensure the cleaner finds it
626+ os.utime(fullpath, (1,1))
627+ res = subprocess.call(["../utils/expunge-cache.py", dirname])
628+ # no arguments
629+ self.assertEqual(res, 1)
630+ # by status
631+ res = subprocess.call(["../utils/expunge-cache.py",
632+ "--debug",
633+ "--by-unsuccessful-http-states",
634+ dirname])
635+ self.assertFalse(os.path.exists(os.path.join(dirname, "foo-301")))
636+ self.assertTrue(os.path.exists(os.path.join(dirname, "foo-200")))
637+ self.assertTrue(os.path.exists(os.path.join(dirname, "foo-random")))
638+
639+ # by time
640+ res = subprocess.call(["../utils/expunge-cache.py",
641+ "--debug",
642+ "--by-days", "1",
643+ dirname])
644+ # now we expect the old file to be gone but the unknown one not to
645+ # be touched
646+ self.assertFalse(os.path.exists(os.path.join(dirname, "foo-200")))
647+ self.assertTrue(os.path.exists(os.path.join(dirname, "foo-random")))
648+
649+
650+
651+
652 if __name__ == "__main__":
653 import logging
654 logging.basicConfig(level=logging.DEBUG)
655
656=== modified file 'utils/expunge-cache.py'
657--- utils/expunge-cache.py 2012-02-29 17:14:43 +0000
658+++ utils/expunge-cache.py 2012-03-05 18:23:26 +0000
659@@ -1,80 +1,166 @@
660-#!/usr/bin/python
661-
662-"""
663-Expunge httplib2 caches
664-"""
665-
666-import argparse
667-import logging
668-import os
669-import time
670-import sys
671-
672-class ExpungeCache(object):
673- def __init__(self, dirs, args):
674- self.dirs = dirs
675- # days to keep data in the cache (0 == disabled)
676- self.keep_time = 60*60*24* args.by_days
677- self.keep_only_http200 = args.by_unsuccessful_http_states
678- self.dry_run = args.dry_run
679-
680- def _rm(self, f):
681- if self.dry_run:
682- print "Would delete: %s" % f
683- else:
684- logging.debug("Deleting: %s" % f)
685- os.unlink(f)
686-
687- def clean(self):
688- # go over the directories
689- now = time.time()
690- for d in self.dirs:
691- for root, dirs, files in os.walk(d):
692- for f in files:
693- fullpath = os.path.join(root, f)
694- header = open(fullpath).readline().strip()
695- if not header.startswith("status:"):
696- logging.debug(
697- "Skipping files with unknown header: '%s'" % f)
698- continue
699- if self.keep_only_http200 and header != "status: 200":
700- self._rm(fullpath)
701- if self.keep_time:
702- mtime = os.path.getmtime(fullpath)
703- logging.debug("mtime of '%s': '%s" % (f, mtime))
704- if (mtime + self.keep_time) < now:
705- self._rm(fullpath)
706-
707-if __name__ == "__main__":
708- parser = argparse.ArgumentParser(
709- description='clean software-center httplib2 cache')
710- parser.add_argument(
711- '--debug', action="store_true",
712- help='show debug output')
713- parser.add_argument(
714- '--dry-run', action="store_true",
715- help='do not act, just show what would be done')
716- parser.add_argument(
717- 'directories', metavar='directory', nargs='+', type=str,
718- help='directories to be checked')
719- parser.add_argument(
720- '--by-days', type=int, default=0,
721- help='expire everything older than N days')
722- parser.add_argument(
723- '--by-unsuccessful-http-states', action="store_true",
724- help='expire any non 200 status responses')
725- args = parser.parse_args()
726-
727- if args.debug:
728- logging.basicConfig(level=logging.DEBUG)
729- else:
730- logging.basicConfig(level=logging.INFO)
731-
732- # sanity checking
733- if args.by_days == 0 and not args.by_unsuccessful_http_states:
734- print "Need either --by-days or --by-unsuccessful-http-states argument"
735- sys.exit(1)
736-
737- # do it
738- cleaner = ExpungeCache(args.directories, args)
739- cleaner.clean()
740+<<<<<<< TREE
741+#!/usr/bin/python
742+
743+"""
744+Expunge httplib2 caches
745+"""
746+
747+import argparse
748+import logging
749+import os
750+import time
751+import sys
752+
753+class ExpungeCache(object):
754+ def __init__(self, dirs, args):
755+ self.dirs = dirs
756+ # days to keep data in the cache (0 == disabled)
757+ self.keep_time = 60*60*24* args.by_days
758+ self.keep_only_http200 = args.by_unsuccessful_http_states
759+ self.dry_run = args.dry_run
760+
761+ def _rm(self, f):
762+ if self.dry_run:
763+ print "Would delete: %s" % f
764+ else:
765+ logging.debug("Deleting: %s" % f)
766+ os.unlink(f)
767+
768+ def clean(self):
769+ # go over the directories
770+ now = time.time()
771+ for d in self.dirs:
772+ for root, dirs, files in os.walk(d):
773+ for f in files:
774+ fullpath = os.path.join(root, f)
775+ header = open(fullpath).readline().strip()
776+ if not header.startswith("status:"):
777+ logging.debug(
778+ "Skipping files with unknown header: '%s'" % f)
779+ continue
780+ if self.keep_only_http200 and header != "status: 200":
781+ self._rm(fullpath)
782+ if self.keep_time:
783+ mtime = os.path.getmtime(fullpath)
784+ logging.debug("mtime of '%s': '%s" % (f, mtime))
785+ if (mtime + self.keep_time) < now:
786+ self._rm(fullpath)
787+
788+if __name__ == "__main__":
789+ parser = argparse.ArgumentParser(
790+ description='clean software-center httplib2 cache')
791+ parser.add_argument(
792+ '--debug', action="store_true",
793+ help='show debug output')
794+ parser.add_argument(
795+ '--dry-run', action="store_true",
796+ help='do not act, just show what would be done')
797+ parser.add_argument(
798+ 'directories', metavar='directory', nargs='+', type=str,
799+ help='directories to be checked')
800+ parser.add_argument(
801+ '--by-days', type=int, default=0,
802+ help='expire everything older than N days')
803+ parser.add_argument(
804+ '--by-unsuccessful-http-states', action="store_true",
805+ help='expire any non 200 status responses')
806+ args = parser.parse_args()
807+
808+ if args.debug:
809+ logging.basicConfig(level=logging.DEBUG)
810+ else:
811+ logging.basicConfig(level=logging.INFO)
812+
813+ # sanity checking
814+ if args.by_days == 0 and not args.by_unsuccessful_http_states:
815+ print "Need either --by-days or --by-unsuccessful-http-states argument"
816+ sys.exit(1)
817+
818+ # do it
819+ cleaner = ExpungeCache(args.directories, args)
820+ cleaner.clean()
821+=======
822+#!/usr/bin/python
823+
824+"""
825+Expunge httplib2 caches
826+"""
827+
828+import argparse
829+import logging
830+import os
831+import time
832+import sys
833+
834+class ExpungeCache(object):
835+ def __init__(self, dirs, args):
836+ self.dirs = dirs
837+ # days to keep data in the cache (0 == disabled)
838+ self.keep_time = 60*60*24* args.by_days
839+ self.keep_only_http200 = args.by_unsuccessful_http_states
840+ self.dry_run = args.dry_run
841+
842+ def _rm(self, f):
843+ if self.dry_run:
844+ print "Would delete: %s" % f
845+ else:
846+ logging.debug("Deleting: %s" % f)
847+ os.unlink(f)
848+
849+ def clean(self):
850+ # go over the directories
851+ now = time.time()
852+ for d in self.dirs:
853+ for root, dirs, files in os.walk(d):
854+ for f in files:
855+ fullpath = os.path.join(root, f)
856+ header = open(fullpath).readline().strip()
857+ if not header.startswith("status:"):
858+ logging.debug(
859+ "Skipping files with unknown header: '%s'" % f)
860+ continue
861+ if self.keep_only_http200 and header != "status: 200":
862+ self._rm(fullpath)
863+ if self.keep_time:
864+ mtime = os.path.getmtime(fullpath)
865+ logging.debug("mtime of '%s': '%s" % (f, mtime))
866+ if (mtime + self.keep_time) < now:
867+ self._rm(fullpath)
868+
869+if __name__ == "__main__":
870+ parser = argparse.ArgumentParser(
871+ description='clean software-center httplib2 cache')
872+ parser.add_argument(
873+ '--debug', action="store_true",
874+ help='show debug output')
875+ parser.add_argument(
876+ '--dry-run', action="store_true",
877+ help='do not act, just show what would be done')
878+ parser.add_argument(
879+ 'directories', metavar='directory', nargs='+', type=str,
880+ help='directories to be checked')
881+ parser.add_argument(
882+ '--by-days', type=int, default=0,
883+ help='expire everything older than N days')
884+ parser.add_argument(
885+ '--by-unsuccessful-http-states', action="store_true",
886+ help='expire any non 200 status responses')
887+ args = parser.parse_args()
888+
889+ if args.debug:
890+ logging.basicConfig(level=logging.DEBUG)
891+ else:
892+ logging.basicConfig(level=logging.INFO)
893+
894+ # sanity checking
895+ if args.by_days == 0 and not args.by_unsuccessful_http_states:
896+ print "Need either --by-days or --by-unsuccessful-http-states argument"
897+ sys.exit(1)
898+
899+ # be nice
900+ os.nice(19)
901+
902+ # do it
903+ cleaner = ExpungeCache(args.directories, args)
904+ cleaner.clean()
905+>>>>>>> MERGE-SOURCE
906
907=== added file 'utils/piston-helpers/piston_get_scagent_available_apps.py.OTHER'
908--- utils/piston-helpers/piston_get_scagent_available_apps.py.OTHER 1970-01-01 00:00:00 +0000
909+++ utils/piston-helpers/piston_get_scagent_available_apps.py.OTHER 2012-03-05 18:23:26 +0000
910@@ -0,0 +1,194 @@
911+#!/usr/bin/python
912+
913+from gi.repository import GObject
914+
915+import argparse
916+import logging
917+import os
918+import pickle
919+import sys
920+
921+import piston_mini_client.auth
922+
923+from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
924+ SOFTWARE_CENTER_SSO_DESCRIPTION,
925+ )
926+from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
927+from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI
928+from softwarecenter.backend.login_sso import get_sso_backend
929+from softwarecenter.backend.restfulclient import UbuntuSSOAPI
930+from softwarecenter.utils import clear_token_from_ubuntu_sso
931+
932+from gettext import gettext as _
933+
934+LOG = logging.getLogger(__name__)
935+
936+class SSOLoginHelper(object):
937+ def __init__(self, xid=0):
938+ self.oauth = None
939+ self.xid = xid
940+ self.loop = GObject.MainLoop(GObject.main_context_default())
941+
942+ def _login_successful(self, sso_backend, oauth_result):
943+ self.oauth = oauth_result
944+ # FIXME: actually verify the token against ubuntu SSO
945+ self.loop.quit()
946+
947+ def verify_token(self, token):
948+ def _whoami_done(sso, me):
949+ self._whoami = me
950+ self.loop.quit()
951+ self._whoami = None
952+ sso = UbuntuSSOAPI(token)
953+ sso.connect("whoami", _whoami_done)
954+ sso.connect("error", lambda sso, err: self.loop.quit())
955+ sso.whoami()
956+ self.loop.run()
957+ # check if the token is valid
958+ if self._whoami is None:
959+ return False
960+ else:
961+ return True
962+
963+ def clear_token(self):
964+ clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING)
965+
966+ def get_oauth_token_sync(self):
967+ self.oauth = None
968+ sso = get_sso_backend(
969+ self.xid,
970+ SOFTWARE_CENTER_NAME_KEYRING,
971+ _(SOFTWARE_CENTER_SSO_DESCRIPTION))
972+ sso.connect("login-successful", self._login_successful)
973+ sso.connect("login-failed", lambda s: self.loop.quit())
974+ sso.connect("login-canceled", lambda s: self.loop.quit())
975+ sso.login_or_register()
976+ self.loop.run()
977+ return self.oauth
978+
979+if __name__ == "__main__":
980+ logging.basicConfig()
981+
982+ # command line parser
983+ parser = argparse.ArgumentParser(description="Helper for software-center-agent")
984+ parser.add_argument("--debug", action="store_true", default=False,
985+ help="enable debug output")
986+ parser.add_argument("--ignore-cache", action="store_true", default=False,
987+ help="force ignore cache")
988+ parser.add_argument("--parent-xid", default=0,
989+ help="xid of the parent window")
990+
991+ subparser = parser.add_subparsers(title="Commands")
992+ # available_apps
993+ command = subparser.add_parser("available_apps")
994+ command.add_argument("lang")
995+ command.add_argument("series")
996+ command.add_argument("arch")
997+ command.set_defaults(command="available_apps")
998+
999+ # available_apps_qa
1000+ command = subparser.add_parser("available_apps_qa")
1001+ command.add_argument("lang")
1002+ command.add_argument("series")
1003+ command.add_argument("arch")
1004+ command.set_defaults(command="available_apps_qa")
1005+ # subscriptions
1006+ command = subparser.add_parser("subscriptions_for_me")
1007+ command.set_defaults(command="subscriptions_for_me")
1008+ # exhibits
1009+ command = subparser.add_parser("exhibits")
1010+ command.add_argument("lang")
1011+ command.add_argument("series")
1012+ command.set_defaults(command="exhibits")
1013+
1014+ args = parser.parse_args()
1015+
1016+ if args.debug:
1017+ LOG.setLevel(logging.DEBUG)
1018+
1019+ if args.ignore_cache:
1020+ cachedir = None
1021+ else:
1022+ cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "scaclient")
1023+
1024+
1025+ # check if auth is required
1026+ if args.command in ("available_apps_qa", "subscriptions_for_me"):
1027+ helper = SSOLoginHelper(args.parent_xid)
1028+ token = helper.get_oauth_token_sync()
1029+ # check if the token is valid and reset it if it is not
1030+ if token and not helper.verify_token(token):
1031+ helper.clear_token()
1032+ # re-trigger login
1033+ token = helper.get_oauth_token_sync()
1034+ # if we don't have a token, error here
1035+ if not token:
1036+ sys.stderr.write("ERROR: can not obtain a oauth token\n")
1037+ sys.exit(1)
1038+
1039+ auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
1040+ token["token_secret"],
1041+ token["consumer_key"],
1042+ token["consumer_secret"])
1043+ scaclient = SoftwareCenterAgentAPI(cachedir=cachedir, auth=auth)
1044+ else:
1045+ scaclient = SoftwareCenterAgentAPI(cachedir=cachedir)
1046+
1047+ piston_reply = None
1048+
1049+ # common kwargs
1050+ if args.command in ("available_apps", "available_apps_qa"):
1051+ kwargs = {"lang": args.lang,
1052+ "series": args.series,
1053+ "arch": args.arch
1054+ }
1055+
1056+ # handle the args
1057+ if args.command == "available_apps":
1058+ try:
1059+ piston_reply = scaclient.available_apps(**kwargs)
1060+ except:
1061+ LOG.exception("available_apps")
1062+ sys.exit(1)
1063+
1064+ elif args.command == "available_apps_qa":
1065+ try:
1066+ piston_reply = scaclient.available_apps_qa(**kwargs)
1067+ except:
1068+ LOG.exception("available_apps_qa")
1069+ sys.exit(1)
1070+ elif args.command == "subscriptions_for_me":
1071+ try:
1072+ piston_reply = scaclient.subscriptions_for_me(complete_only=True)
1073+ # the new piston API send the data in a nasty format, most
1074+ # interessting stuff is in the "application" dict, move it
1075+ # back int othe main object here so that the parser understands it
1076+ for item in piston_reply:
1077+ for k, v in item.application.iteritems():
1078+ setattr(item, k, v)
1079+ except:
1080+ LOG.exception("subscriptions_for_me")
1081+ sys.exit(1)
1082+ if args.command == "exhibits":
1083+ try:
1084+ piston_reply = scaclient.exhibits(lang=args.lang, series=args.series)
1085+ except:
1086+ LOG.exception("exhibits")
1087+ sys.exit(1)
1088+
1089+ if args.debug:
1090+ LOG.debug("reply: %s" % piston_reply)
1091+ for item in piston_reply:
1092+ for var in vars(item):
1093+ print "%s: %s" % (var, getattr(item, var))
1094+ print "\n\n"
1095+
1096+
1097+ # print to stdout where its consumed by the parent
1098+ if piston_reply is not None:
1099+ try:
1100+ print pickle.dumps(piston_reply)
1101+ except IOError:
1102+ # this can happen if the parent gets killed, no need to trigger
1103+ # apport for this
1104+ pass

Subscribers

People subscribed via source and target branches