Merge lp:~mvo/software-center/support-multiple-exhibit-images into lp:software-center

Proposed by Michael Vogt
Status: Merged
Merged at revision: 3201
Proposed branch: lp:~mvo/software-center/support-multiple-exhibit-images
Merge into: lp:software-center
Diff against target: 516 lines (+244/-182)
4 files modified
softwarecenter/ui/gtk3/views/lobbyview.py (+3/-0)
softwarecenter/ui/gtk3/widgets/exhibits.py (+56/-37)
tests/gtk3/test_catview.py (+3/-145)
tests/gtk3/test_exhibits.py (+182/-0)
To merge this branch: bzr merge lp:~mvo/software-center/support-multiple-exhibit-images
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Review via email: mp+125959@code.launchpad.net

Description of the change

This branch adds support for multiple images in the exhibit banners.
This is to fix #920542. It also does a bit of drive-by cleanup in the
testscases by extracting the exhibits testcase into its own file.

To post a comment you must log in.
3203. By Michael Vogt

add some debug code

Revision history for this message
Gary Lasker (gary-lasker) wrote :

Thanks, Michael!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwarecenter/ui/gtk3/views/lobbyview.py'
2--- softwarecenter/ui/gtk3/views/lobbyview.py 2012-09-20 01:09:57 +0000
3+++ softwarecenter/ui/gtk3/views/lobbyview.py 2012-09-24 15:24:22 +0000
4@@ -150,6 +150,9 @@
5 exhibit.package_names.split(','))
6 if available:
7 result.append(exhibit)
8+ else:
9+ LOG.warn("skipping exhibit for: '%r' not available" % (
10+ exhibit.package_names))
11
12 # its ok if result is empty, since set_exhibits() will ignore
13 # empty lists
14
15=== modified file 'softwarecenter/ui/gtk3/widgets/exhibits.py'
16--- softwarecenter/ui/gtk3/widgets/exhibits.py 2012-09-12 14:08:18 +0000
17+++ softwarecenter/ui/gtk3/widgets/exhibits.py 2012-09-24 15:24:22 +0000
18@@ -86,10 +86,10 @@
19 "webservice-office-zoho")
20 self.title_translated = _("Our star apps")
21 self.published = True
22- self.banner_url = "file:%s" % (os.path.abspath(os.path.join(
23- softwarecenter.paths.datadir, "default_banner/fallback.png")))
24+ self.banner_urls = ["file:%s" % (os.path.abspath(os.path.join(
25+ softwarecenter.paths.datadir, "default_banner/fallback.png")))]
26 self.html = EXHIBIT_HTML % {
27- 'banner_url': self.banner_url,
28+ 'banner_url': self.banner_urls[0],
29 'title': _("Our star apps"),
30 'subtitle': _("Come and explore our favourites"),
31 }
32@@ -121,47 +121,66 @@
33 self.show_all()
34 self.loader = SimpleFileDownloader()
35 self.loader.connect("file-download-complete",
36- self.on_download_complete)
37+ self._on_one_download_complete)
38 self.loader.connect("error",
39- self.on_download_error)
40+ self._on_download_error)
41 self.exhibit = None
42- self.view.connect("notify::load-status", self._on_load_status)
43-
44- def _on_load_status(self, view, prop):
45+ self._downloaded_banner_images = []
46+ self.view.connect(
47+ "notify::load-status", self._on_internal_renderer_load_status)
48+
49+ def set_exhibit(self, exhibit):
50+ LOG.debug("set_exhibit: '%s'" % exhibit)
51+ self._downloaded_banner_images = []
52+ self.exhibit = exhibit
53+ self._download_next_banner_image()
54+
55+ def _on_download_error(self, loader, exception, error):
56+ LOG.warn("download failed: '%s', '%s'" % (exception, error))
57+
58+ def _on_one_download_complete(self, loader, path):
59+ LOG.debug("downloading of '%s' finished" % path)
60+ self._downloaded_banner_images.append(path)
61+ if len(self._downloaded_banner_images) < len(self.exhibit.banner_urls):
62+ self._download_next_banner_image()
63+ self._on_all_banner_images_downloaded()
64+
65+ def _on_all_banner_images_downloaded(self):
66+ LOG.debug("downloading of all banner images finished")
67+ html = self.exhibit.html
68+ cache_dir = os.path.join(
69+ softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR,
70+ "download-cache")
71+ for url, local_file in zip(self.exhibit.banner_urls,
72+ self._downloaded_banner_images):
73+ # no need to mangle local urls
74+ if url.startswith("file"):
75+ continue
76+ scheme, netloc, server_path, para, query, frag = urlparse(url)
77+ image_name = os.path.basename(local_file)
78+ # replace the server side path with the local image name, this
79+ # assumes that the image always comes from the same server as
80+ # the html
81+ html = html.replace(server_path, image_name)
82+ self.exhibit.html = html
83+ LOG.debug("mangled html: '%s'" % html)
84+ self.view.load_string(html, "text/html", "UTF-8",
85+ "file:%s/" % cache_dir)
86+
87+ def _download_next_banner_image(self):
88+ LOG.debug("_download_next_banner_image")
89+ self.loader.download_file(
90+ self.exhibit.banner_urls[len(self._downloaded_banner_images)],
91+ use_cache=True,
92+ simple_quoting_for_webkit=True)
93+
94+ def _on_internal_renderer_load_status(self, view, prop):
95+ """Called when the rendering of the html banner is done"""
96 if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
97 # this needs to run with a timeout because otherwise the
98 # status is emited before the offscreen image is finihsed
99 GObject.timeout_add(100, lambda: self.emit("render-finished"))
100
101- def on_download_error(self, loader, exception, error):
102- LOG.warn("download failed: '%s', '%s'" % (exception, error))
103-
104- def on_download_complete(self, loader, path):
105- image_name = os.path.basename(path)
106- cache_dir = os.path.dirname(path)
107- if hasattr(self.exhibit, "html") and self.exhibit.html:
108- html = self.exhibit.html
109- else:
110- html = EXHIBIT_HTML % {
111- 'banner_url': self.exhibit.banner_url,
112- 'title': self.exhibit.title_translated,
113- 'subtitle': "",
114- }
115- # replace the server side path with the local image name, this
116- # assumes that the image always comes from the same server as
117- # the html
118- scheme, netloc, server_path, para, query, frag = urlparse(
119- self.exhibit.banner_url)
120- html = html.replace(server_path, image_name)
121- self.view.load_string(html, "text/html", "UTF-8",
122- "file:%s/" % cache_dir)
123-
124- def set_exhibit(self, exhibit):
125- self.exhibit = exhibit
126- self.loader.download_file(exhibit.banner_url,
127- use_cache=True,
128- simple_quoting_for_webkit=True)
129-
130
131 class ExhibitButton(Gtk.Button):
132
133
134=== modified file 'tests/gtk3/test_catview.py'
135--- tests/gtk3/test_catview.py 2012-09-18 06:38:40 +0000
136+++ tests/gtk3/test_catview.py 2012-09-24 15:24:22 +0000
137@@ -6,11 +6,9 @@
138 from tests.utils import (
139 do_events,
140 do_events_with_sleep,
141- FakedCache,
142 get_test_db,
143 get_test_gtk3_icon_cache,
144 make_recommender_agent_recommend_me_dict,
145- ObjectWithSignals,
146 setup_test_env,
147 )
148 setup_test_env()
149@@ -19,7 +17,6 @@
150 import softwarecenter.paths
151
152 from softwarecenter.db.appfilter import AppFilter
153-from softwarecenter.db.database import StoreDatabase
154 from softwarecenter.enums import (SortMethods,
155 TransactionTypes,
156 RecommenderFeedbackActions)
157@@ -35,10 +32,10 @@
158 def setUpClass(cls):
159 cls.db = get_test_db()
160
161- def setUp(self):
162+ def setUp(self, selected_category=None):
163 self._cat = None
164 self._app = None
165- self.win = get_test_window_catview(self.db)
166+ self.win = get_test_window_catview(self.db, selected_category)
167 self.addCleanup(self.win.destroy)
168 self.notebook = self.win.get_child()
169 self.lobby = self.win.get_data("lobby")
170@@ -122,19 +119,12 @@
171 @patch('softwarecenter.ui.gtk3.widgets.recommendations.RecommenderAgent'
172 '.post_submit_profile')
173 def setUp(self, mock_query, mock_recommender_is_opted_in, mock_sso):
174- self._cat = None
175- self._app = None
176 # patch the recommender to specify that we are not opted-in at
177 # the start of each test
178 mock_recommender_is_opted_in.return_value = False
179 # we specify the "Internet" category because we do specific checks
180 # in the following tests that depend on this being the category choice
181- self.win = get_test_window_catview(self.db,
182- selected_category="Internet")
183- self.addCleanup(self.win.destroy)
184- self.notebook = self.win.get_child()
185- self.lobby = self.win.get_data("lobby")
186- self.subcat_view = self.win.get_data("subcat")
187+ super(RecommendationsTestCase, self).setUp(selected_category="Internet")
188 self.rec_panel = self.lobby.recommended_for_you_panel
189
190 def test_recommended_for_you_opt_in_display(self):
191@@ -371,138 +361,6 @@
192 mock_result)
193 do_events()
194
195-
196-class ExhibitsTestCase(unittest.TestCase):
197- """The test suite for the exhibits carousel."""
198-
199- def setUp(self):
200- self.cache = FakedCache()
201- self.db = StoreDatabase(cache=self.cache)
202- self.lobby = lobbyview.LobbyView(cache=self.cache, db=self.db,
203- icons=None, apps_filter=None)
204- self.addCleanup(self.lobby.destroy)
205-
206- def _get_banner_from_lobby(self):
207- return self.lobby.vbox.get_children()[-1].get_child()
208-
209- def test_featured_exhibit_by_default(self):
210- """Show the featured exhibit before querying the remote service."""
211- self.lobby._append_banner_ads()
212-
213- banner = self._get_banner_from_lobby()
214- self.assertEqual(1, len(banner.exhibits))
215- self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
216-
217- def test_no_exhibit_if_not_available(self):
218- """The exhibit should not be shown if the package is not available."""
219- exhibit = Mock()
220- exhibit.package_names = u'foobarbaz'
221-
222- sca = ObjectWithSignals()
223- sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
224-
225- with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
226- self.lobby._append_banner_ads()
227-
228- banner = self._get_banner_from_lobby()
229- self.assertEqual(1, len(banner.exhibits))
230- self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
231-
232- def test_exhibit_if_available(self):
233- """The exhibit should be shown if the package is available."""
234- exhibit = Mock()
235- exhibit.package_names = u'foobarbaz'
236- exhibit.banner_url = 'banner'
237- exhibit.title_translated = ''
238-
239- self.cache[u'foobarbaz'] = Mock()
240-
241- sca = ObjectWithSignals()
242- sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
243-
244- with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
245- self.lobby._append_banner_ads()
246-
247- banner = self._get_banner_from_lobby()
248- self.assertEqual(1, len(banner.exhibits))
249- self.assertIs(banner.exhibits[0], exhibit)
250-
251- def test_exhibit_if_mixed_availability(self):
252- """The exhibit should be shown even if some are not available."""
253- # available exhibit
254- exhibit = Mock()
255- exhibit.package_names = u'foobarbaz'
256- exhibit.banner_url = 'banner'
257- exhibit.title_translated = ''
258-
259- self.cache[u'foobarbaz'] = Mock()
260-
261- # not available exhibit
262- other = Mock()
263- other.package_names = u'not-there'
264-
265- sca = ObjectWithSignals()
266- sca.query_exhibits = lambda: sca.emit('exhibits', sca,
267- [exhibit, other])
268-
269- with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
270- self.lobby._append_banner_ads()
271-
272- banner = self._get_banner_from_lobby()
273- self.assertEqual(1, len(banner.exhibits))
274- self.assertIs(banner.exhibits[0], exhibit)
275-
276- def test_exhibit_with_url(self):
277- # available exhibit
278- exhibit = Mock()
279- exhibit.package_names = ''
280- exhibit.click_url = 'http://example.com'
281- exhibit.banner_url = 'banner'
282- exhibit.title_translated = ''
283-
284- sca = ObjectWithSignals()
285- sca.query_exhibits = lambda: sca.emit('exhibits', sca,
286- [exhibit])
287-
288- with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
289- # add the banners
290- self.lobby._append_banner_ads()
291- # fake click
292- alloc = self.lobby.exhibit_banner.get_allocation()
293- mock_event = Mock()
294- mock_event.x = alloc.x
295- mock_event.y = alloc.y
296- with patch.object(self.lobby.exhibit_banner, 'emit') as mock_emit:
297- self.lobby.exhibit_banner.on_button_press(None, mock_event)
298- self.lobby.exhibit_banner.on_button_release(None, mock_event)
299- mock_emit.assert_called()
300- signal_name = mock_emit.call_args[0][0]
301- call_exhibit = mock_emit.call_args[0][1]
302- self.assertEqual(signal_name, "show-exhibits-clicked")
303- self.assertEqual(call_exhibit.click_url, "http://example.com")
304-
305- def test_exhibit_with_featured_exhibit(self):
306- """ regression test for bug #1023777 """
307- sca = ObjectWithSignals()
308- sca.query_exhibits = lambda: sca.emit('exhibits', sca,
309- [lobbyview.FeaturedExhibit()])
310-
311- with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
312- # add the banners
313- self.lobby._append_banner_ads()
314- # fake click
315- alloc = self.lobby.exhibit_banner.get_allocation()
316- mock_event = Mock()
317- mock_event.x = alloc.x
318- mock_event.y = alloc.y
319- with patch.object(self.lobby, 'emit') as mock_emit:
320- self.lobby.exhibit_banner.on_button_press(None, mock_event)
321- self.lobby.exhibit_banner.on_button_release(None, mock_event)
322- mock_emit.assert_called()
323- signal_name = mock_emit.call_args[0][0]
324- call_category = mock_emit.call_args[0][1]
325- self.assertEqual(signal_name, "category-selected")
326- self.assertEqual(call_category.name, "Our star apps")
327
328 if __name__ == "__main__":
329 unittest.main()
330
331=== added file 'tests/gtk3/test_exhibits.py'
332--- tests/gtk3/test_exhibits.py 1970-01-01 00:00:00 +0000
333+++ tests/gtk3/test_exhibits.py 2012-09-24 15:24:22 +0000
334@@ -0,0 +1,182 @@
335+import os
336+import unittest
337+
338+from mock import patch, Mock
339+
340+from tests.utils import (
341+ FakedCache,
342+ ObjectWithSignals,
343+ setup_test_env,
344+)
345+setup_test_env()
346+
347+
348+from softwarecenter.db.database import StoreDatabase
349+from softwarecenter.ui.gtk3.views import lobbyview
350+from softwarecenter.ui.gtk3.widgets.exhibits import (
351+ _HtmlRenderer,
352+ )
353+
354+
355+class ExhibitsTestCase(unittest.TestCase):
356+ """The test suite for the exhibits carousel."""
357+
358+ def setUp(self):
359+ self.cache = FakedCache()
360+ self.db = StoreDatabase(cache=self.cache)
361+ self.lobby = lobbyview.LobbyView(cache=self.cache, db=self.db,
362+ icons=None, apps_filter=None)
363+ self.addCleanup(self.lobby.destroy)
364+
365+ def _get_banner_from_lobby(self):
366+ return self.lobby.vbox.get_children()[-1].get_child()
367+
368+ def test_featured_exhibit_by_default(self):
369+ """Show the featured exhibit before querying the remote service."""
370+ self.lobby._append_banner_ads()
371+
372+ banner = self._get_banner_from_lobby()
373+ self.assertEqual(1, len(banner.exhibits))
374+ self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
375+
376+ def test_no_exhibit_if_not_available(self):
377+ """The exhibit should not be shown if the package is not available."""
378+ exhibit = Mock()
379+ exhibit.package_names = u'foobarbaz'
380+
381+ sca = ObjectWithSignals()
382+ sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
383+
384+ with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
385+ self.lobby._append_banner_ads()
386+
387+ banner = self._get_banner_from_lobby()
388+ self.assertEqual(1, len(banner.exhibits))
389+ self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
390+
391+ def test_exhibit_if_available(self):
392+ """The exhibit should be shown if the package is available."""
393+ exhibit = Mock()
394+ exhibit.package_names = u'foobarbaz'
395+ exhibit.banner_urls = ['banner']
396+ exhibit.title_translated = ''
397+
398+ self.cache[u'foobarbaz'] = Mock()
399+
400+ sca = ObjectWithSignals()
401+ sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
402+
403+ with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
404+ self.lobby._append_banner_ads()
405+
406+ banner = self._get_banner_from_lobby()
407+ self.assertEqual(1, len(banner.exhibits))
408+ self.assertIs(banner.exhibits[0], exhibit)
409+
410+ def test_exhibit_if_mixed_availability(self):
411+ """The exhibit should be shown even if some are not available."""
412+ # available exhibit
413+ exhibit = Mock()
414+ exhibit.package_names = u'foobarbaz'
415+ exhibit.banner_urls = ['banner']
416+ exhibit.title_translated = ''
417+
418+ self.cache[u'foobarbaz'] = Mock()
419+
420+ # not available exhibit
421+ other = Mock()
422+ other.package_names = u'not-there'
423+
424+ sca = ObjectWithSignals()
425+ sca.query_exhibits = lambda: sca.emit('exhibits', sca,
426+ [exhibit, other])
427+
428+ with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
429+ self.lobby._append_banner_ads()
430+
431+ banner = self._get_banner_from_lobby()
432+ self.assertEqual(1, len(banner.exhibits))
433+ self.assertIs(banner.exhibits[0], exhibit)
434+
435+ def test_exhibit_with_url(self):
436+ # available exhibit
437+ exhibit = Mock()
438+ exhibit.package_names = ''
439+ exhibit.click_url = 'http://example.com'
440+ exhibit.banner_urls = ['banner']
441+ exhibit.title_translated = ''
442+
443+ sca = ObjectWithSignals()
444+ sca.query_exhibits = lambda: sca.emit('exhibits', sca,
445+ [exhibit])
446+
447+ with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
448+ # add the banners
449+ self.lobby._append_banner_ads()
450+ # fake click
451+ alloc = self.lobby.exhibit_banner.get_allocation()
452+ mock_event = Mock()
453+ mock_event.x = alloc.x
454+ mock_event.y = alloc.y
455+ with patch.object(self.lobby.exhibit_banner, 'emit') as mock_emit:
456+ self.lobby.exhibit_banner.on_button_press(None, mock_event)
457+ self.lobby.exhibit_banner.on_button_release(None, mock_event)
458+ mock_emit.assert_called()
459+ signal_name = mock_emit.call_args[0][0]
460+ call_exhibit = mock_emit.call_args[0][1]
461+ self.assertEqual(signal_name, "show-exhibits-clicked")
462+ self.assertEqual(call_exhibit.click_url, "http://example.com")
463+
464+ def test_exhibit_with_featured_exhibit(self):
465+ """ regression test for bug #1023777 """
466+ sca = ObjectWithSignals()
467+ sca.query_exhibits = lambda: sca.emit('exhibits', sca,
468+ [lobbyview.FeaturedExhibit()])
469+
470+ with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
471+ # add the banners
472+ self.lobby._append_banner_ads()
473+ # fake click
474+ alloc = self.lobby.exhibit_banner.get_allocation()
475+ mock_event = Mock()
476+ mock_event.x = alloc.x
477+ mock_event.y = alloc.y
478+ with patch.object(self.lobby, 'emit') as mock_emit:
479+ self.lobby.exhibit_banner.on_button_press(None, mock_event)
480+ self.lobby.exhibit_banner.on_button_release(None, mock_event)
481+ mock_emit.assert_called()
482+ signal_name = mock_emit.call_args[0][0]
483+ call_category = mock_emit.call_args[0][1]
484+ self.assertEqual(signal_name, "category-selected")
485+ self.assertEqual(call_category.name, "Our star apps")
486+
487+
488+class HtmlRendererTestCase(unittest.TestCase):
489+
490+ def test_multiple_images(self):
491+ downloader = ObjectWithSignals()
492+ downloader.download_file = lambda *args, **kwargs: downloader.emit(
493+ "file-download-complete", downloader, os.path.basename(args[0]))
494+
495+ with patch("softwarecenter.ui.gtk3.widgets.exhibits."
496+ "SimpleFileDownloader", lambda: downloader):
497+ renderer = _HtmlRenderer()
498+ mock_exhibit = Mock()
499+ mock_exhibit.banner_urls = [
500+ "http://example.com/path1/banner1.png",
501+ "http://example.com/path2/banner2.png",
502+ ]
503+ mock_exhibit.html = "url('/path1/banner1.png')#"\
504+ "url('/path2/banner2.png')"
505+
506+ renderer.set_exhibit(mock_exhibit)
507+ # assert the stuff we expected to get downloaded got downloaded
508+ self.assertEqual(
509+ renderer._downloaded_banner_images,
510+ ["banner1.png", "banner2.png"])
511+ # test that the path mangling worked
512+ self.assertEqual(
513+ mock_exhibit.html, "url('banner1.png')#url('banner2.png')")
514+
515+if __name__ == "__main__":
516+ unittest.main()

Subscribers

People subscribed via source and target branches