Merge lp:~cjwatson/ubiquity/libsoup-timezone into lp:ubiquity

Proposed by Colin Watson
Status: Merged
Merged at revision: 5006
Proposed branch: lp:~cjwatson/ubiquity/libsoup-timezone
Merge into: lp:ubiquity
Diff against target: 219 lines (+112/-42) (has conflicts)
4 files modified
debian/changelog (+10/-0)
debian/control (+1/-1)
tests/test_timezone.py (+18/-2)
ubiquity/plugins/ubi-timezone.py (+83/-39)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~cjwatson/ubiquity/libsoup-timezone
Reviewer Review Type Date Requested Status
Evan (community) Needs Information
Review via email: mp+77324@code.launchpad.net

Description of the change

Fix bug 837217 by fetching geoname data asynchronously using libsoup.

To post a comment you must log in.
Revision history for this message
Evan (ev) wrote :

./tests/run-pyflakes
ubiquity/plugins/ubi-timezone.py:116: undefined name 'Gtk'
ubiquity/plugins/ubi-timezone.py:116: undefined name 'GObject'
ubiquity/plugins/ubi-timezone.py:116: undefined name 'GObject'
ubiquity/plugins/ubi-timezone.py:117: undefined name 'GObject'
ubiquity/plugins/ubi-timezone.py:117: undefined name 'GObject'
ubiquity/plugins/ubi-timezone.py:118: undefined name 'GObject'

Revision history for this message
Evan (ev) wrote :

(I'm happy to fix those up as I merge)

Revision history for this message
Evan (ev) wrote :

Shouldn't the block of code that does a lookup against self.zones still live in the changed() function? Otherwise, the user will only get results, including locally-computed ones, if the callback for hitting the geonames server succeeds.

review: Needs Information
Revision history for this message
Evan (ev) wrote :

(This caused the test_city_entry test to fail, which expects to get some local results upon setting the entry to Eastern.)

Revision history for this message
Colin Watson (cjwatson) wrote :

Fixed the missing imports, thanks.

The callback is supposed to fire no matter what, whether by cancellation or timeout or whatever; in my tests, I got locally-computed results even when the network was dropping all outbound port-80 packets. But perhaps it would be better to set the completion model straight away and fill it in with network results once they arrive; the main gotcha there is making sure that we only cache once we get network results.

Revision history for this message
Evan (ev) wrote :

Apologies, I was indeed wrong about the callback only firing on success.

Matthew suggests that a reasonable threshold is that if the timeout is going to be greater than 2-3 seconds, we should show the locally-computed results while we wait for network results.

4997. By Colin Watson

missing imports

4998. By Colin Watson

Show tzdb completion entries after a short timeout.

4999. By Colin Watson

need to encode text before passing to urllib.quote

5000. By Colin Watson

fix up tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2011-09-30 13:49:36 +0000
+++ debian/changelog 2011-09-30 21:34:24 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1ubiquity (2.8.0) UNRELEASED; urgency=low2ubiquity (2.8.0) UNRELEASED; urgency=low
23
3 * Fix SSID UTF-8 decoding, and replace any still-invalid characters with4 * Fix SSID UTF-8 decoding, and replace any still-invalid characters with
@@ -29,6 +30,15 @@
2930
30 -- Adam Conrad <adconrad@ubuntu.com> Wed, 28 Sep 2011 16:43:42 -060031 -- Adam Conrad <adconrad@ubuntu.com> Wed, 28 Sep 2011 16:43:42 -0600
3132
33=======
34ubiquity (2.7.38) UNRELEASED; urgency=low
35
36 * GTK frontend:
37 - Fetch geoname data asynchronously (LP: #837217).
38
39 -- Colin Watson <cjwatson@ubuntu.com> Wed, 28 Sep 2011 13:43:44 +0100
40
41>>>>>>> MERGE-SOURCE
32ubiquity (2.7.37) oneiric; urgency=low42ubiquity (2.7.37) oneiric; urgency=low
3343
34 [ Evan Dandrea ]44 [ Evan Dandrea ]
3545
=== modified file 'debian/control'
--- debian/control 2011-09-28 09:54:01 +0000
+++ debian/control 2011-09-30 21:34:24 +0000
@@ -43,7 +43,7 @@
4343
44Package: ubiquity-frontend-gtk44Package: ubiquity-frontend-gtk
45Architecture: any45Architecture: any
46Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, ubiquity (= ${binary:Version}), python-dbus, gir1.2-gtk-3.0, gir1.2-gdkpixbuf-2.0, gir1.2-gstreamer-0.10, gir1.2-vte-2.90, gir1.2-webkit-3.0, iso-codes, x-window-manager, gksu, python-xklavier, gir1.2-timezonemap-1.0, python-libxml2, python-cairo, python-appindicator, gstreamer0.10-plugins-good46Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, ubiquity (= ${binary:Version}), python-dbus, gir1.2-gtk-3.0, gir1.2-gdkpixbuf-2.0, gir1.2-gstreamer-0.10, gir1.2-soup-2.4, gir1.2-vte-2.90, gir1.2-webkit-3.0, iso-codes, x-window-manager, gksu, python-xklavier, gir1.2-timezonemap-1.0, python-libxml2, python-cairo, python-appindicator, gstreamer0.10-plugins-good
47Suggests: gnome-control-center47Suggests: gnome-control-center
48Conflicts: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3)48Conflicts: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3)
49Replaces: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3)49Replaces: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3)
5050
=== modified file 'tests/test_timezone.py'
--- tests/test_timezone.py 2011-08-30 17:20:27 +0000
+++ tests/test_timezone.py 2011-09-30 21:34:24 +0000
@@ -19,10 +19,26 @@
19 controller.dbfilter = self.ubi_timezone.Page(None, db=db)19 controller.dbfilter = self.ubi_timezone.Page(None, db=db)
20 self.gtk = self.ubi_timezone.PageGtk(controller)20 self.gtk = self.ubi_timezone.PageGtk(controller)
2121
22 @mock.patch('gi.repository.Soup.SessionAsync')
23 @mock.patch('gi.repository.Soup.Message')
22 @mock.patch('json.loads')24 @mock.patch('json.loads')
23 @mock.patch('urllib2.build_opener')25 def test_city_entry(self, json_mock, *args):
24 def test_city_entry(self, opener_mock, json_mock):26 from gi.repository import GObject
27
28 # Patch GObject.timeout_add_seconds to call the supplied function
29 # immediately rather than waiting for the interval to expire.
30 def side_effect_factory(real_method):
31 def side_effect(interval, function, data):
32 function(data)
33 return side_effect
34
25 json_mock.return_value = []35 json_mock.return_value = []
36 real_method = GObject.timeout_add_seconds
37 method = mock.patch('gi.repository.GObject.timeout_add_seconds')
38 timeout_mock = method.start()
39 timeout_mock.side_effect = side_effect_factory(real_method)
40 self.addCleanup(method.stop)
41
26 self.gtk.set_timezone('America/New_York')42 self.gtk.set_timezone('America/New_York')
27 self.gtk.city_entry.set_text('Eastern')43 self.gtk.city_entry.set_text('Eastern')
28 self.gtk.changed(self.gtk.city_entry)44 self.gtk.changed(self.gtk.city_entry)
2945
=== modified file 'ubiquity/plugins/ubi-timezone.py'
--- ubiquity/plugins/ubi-timezone.py 2011-08-30 17:20:27 +0000
+++ ubiquity/plugins/ubi-timezone.py 2011-09-30 21:34:24 +0000
@@ -52,6 +52,9 @@
52 self.timezone = None52 self.timezone = None
53 self.zones = []53 self.zones = []
54 self.plugin_widgets = self.page54 self.plugin_widgets = self.page
55 self.geoname_cache = {}
56 self.geoname_session = None
57 self.geoname_timeout_id = None
5558
56 def plugin_translate(self, lang):59 def plugin_translate(self, lang):
57 #c = self.controller60 #c = self.controller
@@ -84,54 +87,95 @@
8487
8588
86 def changed(self, entry):89 def changed(self, entry):
87 from gi.repository import Gtk, GObject90 import urllib
91 from gi.repository import Gtk, GObject, Soup
92
88 text = self.city_entry.get_text().decode('utf-8')93 text = self.city_entry.get_text().decode('utf-8')
89 if not text:94 if not text:
90 return95 return
91 # TODO if the completion widget has a selection, return? How do we96 # TODO if the completion widget has a selection, return? How do we
92 # determine this?97 # determine this?
93 if text in self.changed.cache:98 if text in self.geoname_cache:
94 model = self.changed.cache[text]99 model = self.geoname_cache[text]
100 self.city_entry.get_completion().set_model(model)
95 else:101 else:
96 # fetch
97 model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING,102 model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING,
98 GObject.TYPE_STRING, GObject.TYPE_STRING,103 GObject.TYPE_STRING, GObject.TYPE_STRING,
99 GObject.TYPE_STRING)104 GObject.TYPE_STRING)
100 self.changed.cache[text] = model105
101 # TODO benchmark this106 if self.geoname_session is None:
102 results = [(name, self.tzdb.get_loc(city))107 self.geoname_session = Soup.SessionAsync()
103 for (name, city) in108 url = _geoname_url % (urllib.quote(text.encode('UTF-8')),
104 [(x[0], x[1]) for x in self.zones109 misc.get_release().version)
105 if x[0].lower().split('(', 1)[-1] \110 message = Soup.Message.new('GET', url)
106 .startswith(text.lower())]]111 message.request_headers.append('User-agent', 'Ubiquity/1.0')
107 for result in results:112 self.geoname_session.abort()
108 # We use name rather than loc.human_zone for i18n.113 if self.geoname_timeout_id is not None:
109 # TODO this looks pretty awful for US results:114 GObject.source_remove(self.geoname_timeout_id)
110 # United States (New York) (United States)115 self.geoname_timeout_id = \
111 # Might want to match the debconf format.116 GObject.timeout_add_seconds(2, self.geoname_timeout,
112 name, loc = result117 (text, model))
113 model.append([name, '', loc.human_country,118 self.geoname_session.queue_message(message, self.geoname_cb,
114 str(loc.latitude), str(loc.longitude)])119 (text, model))
115120
116 try:121 def geoname_add_tzdb(self, text, model):
117 import urllib2, urllib, json122 if len(model):
118 opener = urllib2.build_opener()123 # already added
119 opener.addheaders = [('User-agent', 'Ubiquity/1.0')]124 return
120 url = opener.open(_geoname_url %125
121 (urllib.quote(text), misc.get_release().version))126 # TODO benchmark this
122 for result in json.loads(url.read()):127 results = [(name, self.tzdb.get_loc(city))
123 model.append([result['name'],128 for (name, city) in
124 result['admin1'],129 [(x[0], x[1]) for x in self.zones
125 result['country'],130 if x[0].lower().split('(', 1)[-1] \
126 result['latitude'],131 .startswith(text.lower())]]
127 result['longitude']])132 for result in results:
128 except Exception, e:133 # We use name rather than loc.human_zone for i18n.
129 print 'exception:', e134 # TODO this looks pretty awful for US results:
130 # TODO because we don't return here, we could cache a135 # United States (New York) (United States)
131 # result that doesn't include the geonames results because136 # Might want to match the debconf format.
132 # of a network error.137 name, loc = result
133 self.city_entry.get_completion().set_model(model)138 model.append([name, '', loc.human_country,
134 changed.cache = {}139 str(loc.latitude), str(loc.longitude)])
140
141 def geoname_timeout(self, user_data):
142 text, model = user_data
143 self.geoname_add_tzdb(text, model)
144 self.geoname_timeout_id = None
145 self.city_entry.get_completion().set_model(model)
146 return False
147
148 def geoname_cb(self, session, message, user_data):
149 import syslog
150 import json
151 from gi.repository import GObject, Soup
152
153 text, model = user_data
154
155 if self.geoname_timeout_id is not None:
156 GObject.source_remove(self.geoname_timeout_id)
157 self.geoname_timeout_id = None
158 self.geoname_add_tzdb(text, model)
159
160 if message.status_code == Soup.KnownStatusCode.CANCELLED:
161 # Silently ignore cancellation.
162 pass
163 elif message.status_code != Soup.KnownStatusCode.OK:
164 # Log but otherwise ignore failures.
165 syslog.syslog('Geoname lookup for "%s" failed: %d %s' %
166 (text, message.status_code, message.reason_phrase))
167 else:
168 for result in json.loads(message.response_body.data):
169 model.append([result['name'],
170 result['admin1'],
171 result['country'],
172 result['latitude'],
173 result['longitude']])
174
175 # Only cache positive results.
176 self.geoname_cache[text] = model
177
178 self.city_entry.get_completion().set_model(model)
135179
136 def setup_page(self):180 def setup_page(self):
137 # TODO Put a frame around the completion to add contrast (LP: #605908)181 # TODO Put a frame around the completion to add contrast (LP: #605908)

Subscribers

People subscribed via source and target branches

to status/vote changes: