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
=== modified file 'softwarecenter/ui/gtk3/views/lobbyview.py'
--- softwarecenter/ui/gtk3/views/lobbyview.py 2012-09-20 01:09:57 +0000
+++ softwarecenter/ui/gtk3/views/lobbyview.py 2012-09-24 15:24:22 +0000
@@ -150,6 +150,9 @@
150 exhibit.package_names.split(','))150 exhibit.package_names.split(','))
151 if available:151 if available:
152 result.append(exhibit)152 result.append(exhibit)
153 else:
154 LOG.warn("skipping exhibit for: '%r' not available" % (
155 exhibit.package_names))
153156
154 # its ok if result is empty, since set_exhibits() will ignore157 # its ok if result is empty, since set_exhibits() will ignore
155 # empty lists158 # empty lists
156159
=== modified file 'softwarecenter/ui/gtk3/widgets/exhibits.py'
--- softwarecenter/ui/gtk3/widgets/exhibits.py 2012-09-12 14:08:18 +0000
+++ softwarecenter/ui/gtk3/widgets/exhibits.py 2012-09-24 15:24:22 +0000
@@ -86,10 +86,10 @@
86 "webservice-office-zoho")86 "webservice-office-zoho")
87 self.title_translated = _("Our star apps")87 self.title_translated = _("Our star apps")
88 self.published = True88 self.published = True
89 self.banner_url = "file:%s" % (os.path.abspath(os.path.join(89 self.banner_urls = ["file:%s" % (os.path.abspath(os.path.join(
90 softwarecenter.paths.datadir, "default_banner/fallback.png")))90 softwarecenter.paths.datadir, "default_banner/fallback.png")))]
91 self.html = EXHIBIT_HTML % {91 self.html = EXHIBIT_HTML % {
92 'banner_url': self.banner_url,92 'banner_url': self.banner_urls[0],
93 'title': _("Our star apps"),93 'title': _("Our star apps"),
94 'subtitle': _("Come and explore our favourites"),94 'subtitle': _("Come and explore our favourites"),
95 }95 }
@@ -121,47 +121,66 @@
121 self.show_all()121 self.show_all()
122 self.loader = SimpleFileDownloader()122 self.loader = SimpleFileDownloader()
123 self.loader.connect("file-download-complete",123 self.loader.connect("file-download-complete",
124 self.on_download_complete)124 self._on_one_download_complete)
125 self.loader.connect("error",125 self.loader.connect("error",
126 self.on_download_error)126 self._on_download_error)
127 self.exhibit = None127 self.exhibit = None
128 self.view.connect("notify::load-status", self._on_load_status)128 self._downloaded_banner_images = []
129129 self.view.connect(
130 def _on_load_status(self, view, prop):130 "notify::load-status", self._on_internal_renderer_load_status)
131
132 def set_exhibit(self, exhibit):
133 LOG.debug("set_exhibit: '%s'" % exhibit)
134 self._downloaded_banner_images = []
135 self.exhibit = exhibit
136 self._download_next_banner_image()
137
138 def _on_download_error(self, loader, exception, error):
139 LOG.warn("download failed: '%s', '%s'" % (exception, error))
140
141 def _on_one_download_complete(self, loader, path):
142 LOG.debug("downloading of '%s' finished" % path)
143 self._downloaded_banner_images.append(path)
144 if len(self._downloaded_banner_images) < len(self.exhibit.banner_urls):
145 self._download_next_banner_image()
146 self._on_all_banner_images_downloaded()
147
148 def _on_all_banner_images_downloaded(self):
149 LOG.debug("downloading of all banner images finished")
150 html = self.exhibit.html
151 cache_dir = os.path.join(
152 softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR,
153 "download-cache")
154 for url, local_file in zip(self.exhibit.banner_urls,
155 self._downloaded_banner_images):
156 # no need to mangle local urls
157 if url.startswith("file"):
158 continue
159 scheme, netloc, server_path, para, query, frag = urlparse(url)
160 image_name = os.path.basename(local_file)
161 # replace the server side path with the local image name, this
162 # assumes that the image always comes from the same server as
163 # the html
164 html = html.replace(server_path, image_name)
165 self.exhibit.html = html
166 LOG.debug("mangled html: '%s'" % html)
167 self.view.load_string(html, "text/html", "UTF-8",
168 "file:%s/" % cache_dir)
169
170 def _download_next_banner_image(self):
171 LOG.debug("_download_next_banner_image")
172 self.loader.download_file(
173 self.exhibit.banner_urls[len(self._downloaded_banner_images)],
174 use_cache=True,
175 simple_quoting_for_webkit=True)
176
177 def _on_internal_renderer_load_status(self, view, prop):
178 """Called when the rendering of the html banner is done"""
131 if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:179 if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
132 # this needs to run with a timeout because otherwise the180 # this needs to run with a timeout because otherwise the
133 # status is emited before the offscreen image is finihsed181 # status is emited before the offscreen image is finihsed
134 GObject.timeout_add(100, lambda: self.emit("render-finished"))182 GObject.timeout_add(100, lambda: self.emit("render-finished"))
135183
136 def on_download_error(self, loader, exception, error):
137 LOG.warn("download failed: '%s', '%s'" % (exception, error))
138
139 def on_download_complete(self, loader, path):
140 image_name = os.path.basename(path)
141 cache_dir = os.path.dirname(path)
142 if hasattr(self.exhibit, "html") and self.exhibit.html:
143 html = self.exhibit.html
144 else:
145 html = EXHIBIT_HTML % {
146 'banner_url': self.exhibit.banner_url,
147 'title': self.exhibit.title_translated,
148 'subtitle': "",
149 }
150 # replace the server side path with the local image name, this
151 # assumes that the image always comes from the same server as
152 # the html
153 scheme, netloc, server_path, para, query, frag = urlparse(
154 self.exhibit.banner_url)
155 html = html.replace(server_path, image_name)
156 self.view.load_string(html, "text/html", "UTF-8",
157 "file:%s/" % cache_dir)
158
159 def set_exhibit(self, exhibit):
160 self.exhibit = exhibit
161 self.loader.download_file(exhibit.banner_url,
162 use_cache=True,
163 simple_quoting_for_webkit=True)
164
165184
166class ExhibitButton(Gtk.Button):185class ExhibitButton(Gtk.Button):
167186
168187
=== modified file 'tests/gtk3/test_catview.py'
--- tests/gtk3/test_catview.py 2012-09-18 06:38:40 +0000
+++ tests/gtk3/test_catview.py 2012-09-24 15:24:22 +0000
@@ -6,11 +6,9 @@
6from tests.utils import (6from tests.utils import (
7 do_events,7 do_events,
8 do_events_with_sleep,8 do_events_with_sleep,
9 FakedCache,
10 get_test_db,9 get_test_db,
11 get_test_gtk3_icon_cache,10 get_test_gtk3_icon_cache,
12 make_recommender_agent_recommend_me_dict,11 make_recommender_agent_recommend_me_dict,
13 ObjectWithSignals,
14 setup_test_env,12 setup_test_env,
15)13)
16setup_test_env()14setup_test_env()
@@ -19,7 +17,6 @@
19import softwarecenter.paths17import softwarecenter.paths
2018
21from softwarecenter.db.appfilter import AppFilter19from softwarecenter.db.appfilter import AppFilter
22from softwarecenter.db.database import StoreDatabase
23from softwarecenter.enums import (SortMethods,20from softwarecenter.enums import (SortMethods,
24 TransactionTypes,21 TransactionTypes,
25 RecommenderFeedbackActions)22 RecommenderFeedbackActions)
@@ -35,10 +32,10 @@
35 def setUpClass(cls):32 def setUpClass(cls):
36 cls.db = get_test_db()33 cls.db = get_test_db()
3734
38 def setUp(self):35 def setUp(self, selected_category=None):
39 self._cat = None36 self._cat = None
40 self._app = None37 self._app = None
41 self.win = get_test_window_catview(self.db)38 self.win = get_test_window_catview(self.db, selected_category)
42 self.addCleanup(self.win.destroy)39 self.addCleanup(self.win.destroy)
43 self.notebook = self.win.get_child()40 self.notebook = self.win.get_child()
44 self.lobby = self.win.get_data("lobby")41 self.lobby = self.win.get_data("lobby")
@@ -122,19 +119,12 @@
122 @patch('softwarecenter.ui.gtk3.widgets.recommendations.RecommenderAgent'119 @patch('softwarecenter.ui.gtk3.widgets.recommendations.RecommenderAgent'
123 '.post_submit_profile')120 '.post_submit_profile')
124 def setUp(self, mock_query, mock_recommender_is_opted_in, mock_sso):121 def setUp(self, mock_query, mock_recommender_is_opted_in, mock_sso):
125 self._cat = None
126 self._app = None
127 # patch the recommender to specify that we are not opted-in at122 # patch the recommender to specify that we are not opted-in at
128 # the start of each test123 # the start of each test
129 mock_recommender_is_opted_in.return_value = False124 mock_recommender_is_opted_in.return_value = False
130 # we specify the "Internet" category because we do specific checks125 # we specify the "Internet" category because we do specific checks
131 # in the following tests that depend on this being the category choice126 # in the following tests that depend on this being the category choice
132 self.win = get_test_window_catview(self.db,127 super(RecommendationsTestCase, self).setUp(selected_category="Internet")
133 selected_category="Internet")
134 self.addCleanup(self.win.destroy)
135 self.notebook = self.win.get_child()
136 self.lobby = self.win.get_data("lobby")
137 self.subcat_view = self.win.get_data("subcat")
138 self.rec_panel = self.lobby.recommended_for_you_panel128 self.rec_panel = self.lobby.recommended_for_you_panel
139129
140 def test_recommended_for_you_opt_in_display(self):130 def test_recommended_for_you_opt_in_display(self):
@@ -371,138 +361,6 @@
371 mock_result)361 mock_result)
372 do_events()362 do_events()
373363
374
375class ExhibitsTestCase(unittest.TestCase):
376 """The test suite for the exhibits carousel."""
377
378 def setUp(self):
379 self.cache = FakedCache()
380 self.db = StoreDatabase(cache=self.cache)
381 self.lobby = lobbyview.LobbyView(cache=self.cache, db=self.db,
382 icons=None, apps_filter=None)
383 self.addCleanup(self.lobby.destroy)
384
385 def _get_banner_from_lobby(self):
386 return self.lobby.vbox.get_children()[-1].get_child()
387
388 def test_featured_exhibit_by_default(self):
389 """Show the featured exhibit before querying the remote service."""
390 self.lobby._append_banner_ads()
391
392 banner = self._get_banner_from_lobby()
393 self.assertEqual(1, len(banner.exhibits))
394 self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
395
396 def test_no_exhibit_if_not_available(self):
397 """The exhibit should not be shown if the package is not available."""
398 exhibit = Mock()
399 exhibit.package_names = u'foobarbaz'
400
401 sca = ObjectWithSignals()
402 sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
403
404 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
405 self.lobby._append_banner_ads()
406
407 banner = self._get_banner_from_lobby()
408 self.assertEqual(1, len(banner.exhibits))
409 self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
410
411 def test_exhibit_if_available(self):
412 """The exhibit should be shown if the package is available."""
413 exhibit = Mock()
414 exhibit.package_names = u'foobarbaz'
415 exhibit.banner_url = 'banner'
416 exhibit.title_translated = ''
417
418 self.cache[u'foobarbaz'] = Mock()
419
420 sca = ObjectWithSignals()
421 sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
422
423 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
424 self.lobby._append_banner_ads()
425
426 banner = self._get_banner_from_lobby()
427 self.assertEqual(1, len(banner.exhibits))
428 self.assertIs(banner.exhibits[0], exhibit)
429
430 def test_exhibit_if_mixed_availability(self):
431 """The exhibit should be shown even if some are not available."""
432 # available exhibit
433 exhibit = Mock()
434 exhibit.package_names = u'foobarbaz'
435 exhibit.banner_url = 'banner'
436 exhibit.title_translated = ''
437
438 self.cache[u'foobarbaz'] = Mock()
439
440 # not available exhibit
441 other = Mock()
442 other.package_names = u'not-there'
443
444 sca = ObjectWithSignals()
445 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
446 [exhibit, other])
447
448 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
449 self.lobby._append_banner_ads()
450
451 banner = self._get_banner_from_lobby()
452 self.assertEqual(1, len(banner.exhibits))
453 self.assertIs(banner.exhibits[0], exhibit)
454
455 def test_exhibit_with_url(self):
456 # available exhibit
457 exhibit = Mock()
458 exhibit.package_names = ''
459 exhibit.click_url = 'http://example.com'
460 exhibit.banner_url = 'banner'
461 exhibit.title_translated = ''
462
463 sca = ObjectWithSignals()
464 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
465 [exhibit])
466
467 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
468 # add the banners
469 self.lobby._append_banner_ads()
470 # fake click
471 alloc = self.lobby.exhibit_banner.get_allocation()
472 mock_event = Mock()
473 mock_event.x = alloc.x
474 mock_event.y = alloc.y
475 with patch.object(self.lobby.exhibit_banner, 'emit') as mock_emit:
476 self.lobby.exhibit_banner.on_button_press(None, mock_event)
477 self.lobby.exhibit_banner.on_button_release(None, mock_event)
478 mock_emit.assert_called()
479 signal_name = mock_emit.call_args[0][0]
480 call_exhibit = mock_emit.call_args[0][1]
481 self.assertEqual(signal_name, "show-exhibits-clicked")
482 self.assertEqual(call_exhibit.click_url, "http://example.com")
483
484 def test_exhibit_with_featured_exhibit(self):
485 """ regression test for bug #1023777 """
486 sca = ObjectWithSignals()
487 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
488 [lobbyview.FeaturedExhibit()])
489
490 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
491 # add the banners
492 self.lobby._append_banner_ads()
493 # fake click
494 alloc = self.lobby.exhibit_banner.get_allocation()
495 mock_event = Mock()
496 mock_event.x = alloc.x
497 mock_event.y = alloc.y
498 with patch.object(self.lobby, 'emit') as mock_emit:
499 self.lobby.exhibit_banner.on_button_press(None, mock_event)
500 self.lobby.exhibit_banner.on_button_release(None, mock_event)
501 mock_emit.assert_called()
502 signal_name = mock_emit.call_args[0][0]
503 call_category = mock_emit.call_args[0][1]
504 self.assertEqual(signal_name, "category-selected")
505 self.assertEqual(call_category.name, "Our star apps")
506 364
507if __name__ == "__main__":365if __name__ == "__main__":
508 unittest.main()366 unittest.main()
509367
=== added file 'tests/gtk3/test_exhibits.py'
--- tests/gtk3/test_exhibits.py 1970-01-01 00:00:00 +0000
+++ tests/gtk3/test_exhibits.py 2012-09-24 15:24:22 +0000
@@ -0,0 +1,182 @@
1import os
2import unittest
3
4from mock import patch, Mock
5
6from tests.utils import (
7 FakedCache,
8 ObjectWithSignals,
9 setup_test_env,
10)
11setup_test_env()
12
13
14from softwarecenter.db.database import StoreDatabase
15from softwarecenter.ui.gtk3.views import lobbyview
16from softwarecenter.ui.gtk3.widgets.exhibits import (
17 _HtmlRenderer,
18 )
19
20
21class ExhibitsTestCase(unittest.TestCase):
22 """The test suite for the exhibits carousel."""
23
24 def setUp(self):
25 self.cache = FakedCache()
26 self.db = StoreDatabase(cache=self.cache)
27 self.lobby = lobbyview.LobbyView(cache=self.cache, db=self.db,
28 icons=None, apps_filter=None)
29 self.addCleanup(self.lobby.destroy)
30
31 def _get_banner_from_lobby(self):
32 return self.lobby.vbox.get_children()[-1].get_child()
33
34 def test_featured_exhibit_by_default(self):
35 """Show the featured exhibit before querying the remote service."""
36 self.lobby._append_banner_ads()
37
38 banner = self._get_banner_from_lobby()
39 self.assertEqual(1, len(banner.exhibits))
40 self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
41
42 def test_no_exhibit_if_not_available(self):
43 """The exhibit should not be shown if the package is not available."""
44 exhibit = Mock()
45 exhibit.package_names = u'foobarbaz'
46
47 sca = ObjectWithSignals()
48 sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
49
50 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
51 self.lobby._append_banner_ads()
52
53 banner = self._get_banner_from_lobby()
54 self.assertEqual(1, len(banner.exhibits))
55 self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit)
56
57 def test_exhibit_if_available(self):
58 """The exhibit should be shown if the package is available."""
59 exhibit = Mock()
60 exhibit.package_names = u'foobarbaz'
61 exhibit.banner_urls = ['banner']
62 exhibit.title_translated = ''
63
64 self.cache[u'foobarbaz'] = Mock()
65
66 sca = ObjectWithSignals()
67 sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
68
69 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
70 self.lobby._append_banner_ads()
71
72 banner = self._get_banner_from_lobby()
73 self.assertEqual(1, len(banner.exhibits))
74 self.assertIs(banner.exhibits[0], exhibit)
75
76 def test_exhibit_if_mixed_availability(self):
77 """The exhibit should be shown even if some are not available."""
78 # available exhibit
79 exhibit = Mock()
80 exhibit.package_names = u'foobarbaz'
81 exhibit.banner_urls = ['banner']
82 exhibit.title_translated = ''
83
84 self.cache[u'foobarbaz'] = Mock()
85
86 # not available exhibit
87 other = Mock()
88 other.package_names = u'not-there'
89
90 sca = ObjectWithSignals()
91 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
92 [exhibit, other])
93
94 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
95 self.lobby._append_banner_ads()
96
97 banner = self._get_banner_from_lobby()
98 self.assertEqual(1, len(banner.exhibits))
99 self.assertIs(banner.exhibits[0], exhibit)
100
101 def test_exhibit_with_url(self):
102 # available exhibit
103 exhibit = Mock()
104 exhibit.package_names = ''
105 exhibit.click_url = 'http://example.com'
106 exhibit.banner_urls = ['banner']
107 exhibit.title_translated = ''
108
109 sca = ObjectWithSignals()
110 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
111 [exhibit])
112
113 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
114 # add the banners
115 self.lobby._append_banner_ads()
116 # fake click
117 alloc = self.lobby.exhibit_banner.get_allocation()
118 mock_event = Mock()
119 mock_event.x = alloc.x
120 mock_event.y = alloc.y
121 with patch.object(self.lobby.exhibit_banner, 'emit') as mock_emit:
122 self.lobby.exhibit_banner.on_button_press(None, mock_event)
123 self.lobby.exhibit_banner.on_button_release(None, mock_event)
124 mock_emit.assert_called()
125 signal_name = mock_emit.call_args[0][0]
126 call_exhibit = mock_emit.call_args[0][1]
127 self.assertEqual(signal_name, "show-exhibits-clicked")
128 self.assertEqual(call_exhibit.click_url, "http://example.com")
129
130 def test_exhibit_with_featured_exhibit(self):
131 """ regression test for bug #1023777 """
132 sca = ObjectWithSignals()
133 sca.query_exhibits = lambda: sca.emit('exhibits', sca,
134 [lobbyview.FeaturedExhibit()])
135
136 with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca):
137 # add the banners
138 self.lobby._append_banner_ads()
139 # fake click
140 alloc = self.lobby.exhibit_banner.get_allocation()
141 mock_event = Mock()
142 mock_event.x = alloc.x
143 mock_event.y = alloc.y
144 with patch.object(self.lobby, 'emit') as mock_emit:
145 self.lobby.exhibit_banner.on_button_press(None, mock_event)
146 self.lobby.exhibit_banner.on_button_release(None, mock_event)
147 mock_emit.assert_called()
148 signal_name = mock_emit.call_args[0][0]
149 call_category = mock_emit.call_args[0][1]
150 self.assertEqual(signal_name, "category-selected")
151 self.assertEqual(call_category.name, "Our star apps")
152
153
154class HtmlRendererTestCase(unittest.TestCase):
155
156 def test_multiple_images(self):
157 downloader = ObjectWithSignals()
158 downloader.download_file = lambda *args, **kwargs: downloader.emit(
159 "file-download-complete", downloader, os.path.basename(args[0]))
160
161 with patch("softwarecenter.ui.gtk3.widgets.exhibits."
162 "SimpleFileDownloader", lambda: downloader):
163 renderer = _HtmlRenderer()
164 mock_exhibit = Mock()
165 mock_exhibit.banner_urls = [
166 "http://example.com/path1/banner1.png",
167 "http://example.com/path2/banner2.png",
168 ]
169 mock_exhibit.html = "url('/path1/banner1.png')#"\
170 "url('/path2/banner2.png')"
171
172 renderer.set_exhibit(mock_exhibit)
173 # assert the stuff we expected to get downloaded got downloaded
174 self.assertEqual(
175 renderer._downloaded_banner_images,
176 ["banner1.png", "banner2.png"])
177 # test that the path mangling worked
178 self.assertEqual(
179 mock_exhibit.html, "url('banner1.png')#url('banner2.png')")
180
181if __name__ == "__main__":
182 unittest.main()

Subscribers

People subscribed via source and target branches