Merge lp:~martin-borho/ubuntu-weather-app/API-features into lp:ubuntu-weather-app/obsolete.trunk

Proposed by Martin Borho on 2013-04-27
Status: Merged
Approved by: Raúl Yeguas on 2013-04-29
Approved revision: 19
Merged at revision: 14
Proposed branch: lp:~martin-borho/ubuntu-weather-app/API-features
Merge into: lp:ubuntu-weather-app/obsolete.trunk
Diff against target: 452 lines (+195/-88)
4 files modified
components/AddLocationDialog.qml (+24/-16)
components/LocationTab.qml (+2/-6)
components/WeatherApi.js (+142/-44)
ubuntu-weather-app.qml (+27/-22)
To merge this branch: bzr merge lp:~martin-borho/ubuntu-weather-app/API-features
Reviewer Review Type Date Requested Status
Raúl Yeguas 2013-04-27 Approve on 2013-04-29
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve on 2013-04-27
Review via email: mp+161290@code.launchpad.net

Commit message

Updated to new openweathermap API version 2.5, API calls now in WorkerScript, added fixes for stability

Description of the change

- updated to openweathermap API version 2.5
- now 10 instead of 7 days in daily forecast
- API calls now done in WorkerScript
- handling occasional empty API responses from openweathermap by a prompt retry

To post a comment you must log in.
Raúl Yeguas (neokore) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components/AddLocationDialog.qml'
2--- components/AddLocationDialog.qml 2013-04-21 18:05:01 +0000
3+++ components/AddLocationDialog.qml 2013-04-27 14:57:26 +0000
4@@ -2,17 +2,31 @@
5 import Ubuntu.Components 0.1
6 import Ubuntu.Components.Popups 0.1
7 import Ubuntu.Components.ListItems 0.1 as ListItem;
8-import "WeatherApi.js" as Api
9
10 // Dialog for adding a new location
11 Component {
12 id: addLocationDialog
13- Dialog {
14+
15+ Dialog {
16 id: addLocationDialogue
17 height: units.gu(10)
18 title: i18n.tr("Add location")
19 text: i18n.tr("Write a city name o pick it by your location")
20
21+ WorkerScript {
22+ id: searchWorker
23+ source: "./WeatherApi.js"
24+ onMessage: {
25+ if(!messageObject.error) {
26+ messageObject.result.locations.forEach(function(loc) {
27+ citiesModel.append(loc);
28+ });
29+ } else {
30+ console.log(messageObject.error.msg+" / "+messageObject.error.request.url)
31+ }
32+ }
33+ }
34+
35 function locationSelected(index) {
36 var location = citiesModel.get(index);
37 // make clone
38@@ -21,18 +35,6 @@
39 PopupUtils.close(addLocationDialogue)
40 }
41
42- function onApiError(err) {
43- // TODO display
44- console.log("onApiError")
45- console.log(err.msg+" / "+err.request.url)
46- }
47-
48- function onSearchSuccess(locations) {
49- locations.results.forEach(function(v) {
50- citiesModel.append(v);
51- })
52- }
53-
54 Row {
55 TextField {
56 id: locationString
57@@ -47,7 +49,10 @@
58
59 onClicked: {
60 citiesModel.clear();
61- Api.WeatherApi.searchLocationByName({name:locationString.text, units:"metric"},onSearchSuccess,onApiError);
62+ searchWorker.sendMessage({
63+ action: "searchByName",
64+ params: {name:locationString.text, units:"metric"}
65+ })
66 }
67 }
68 }
69@@ -57,7 +62,10 @@
70 text: i18n.tr("Use my own location")
71 onClicked: {
72 citiesModel.clear();
73- Api.WeatherApi.searchLocationByPoint({coords: {"lon":10,"lat":53.549999}, units:"metric"}, onSearchSuccess,onApiError);
74+ searchWorker.sendMessage({
75+ action: "searchByPoint",
76+ params: {coords: {"lon":10,"lat":53.549999}, units:"metric"}
77+ })
78 }
79 }
80
81
82=== modified file 'components/LocationTab.qml'
83--- components/LocationTab.qml 2013-04-24 17:37:44 +0000
84+++ components/LocationTab.qml 2013-04-27 14:57:26 +0000
85@@ -43,18 +43,14 @@
86 dayForecastModel.append({
87 dateRel: "",//Tomorrow",
88 date: formatTimestamp(dailyForecasts[x].timestamp, 'dddd, dd MMMM yyyy'),
89- temp: dailyForecasts[x].temp,
90- tempMin: dailyForecasts[x].night,
91+ temp: dailyForecasts[x].max,
92+ tempMin: dailyForecasts[x].min,
93 cond: dailyForecasts[x].condition.id,
94 condIcon: dailyForecasts[x].condition.icon
95 });
96 }
97 }
98
99- function locationAdded(location) {
100- mainView.locationAdded({"service":"openweathermap","service_id":2643743,"name":"London","country":"GB"});
101- }
102-
103 // Menu for options
104 page: Page {
105
106
107=== modified file 'components/WeatherApi.js'
108--- components/WeatherApi.js 2013-04-21 18:05:01 +0000
109+++ components/WeatherApi.js 2013-04-27 14:57:26 +0000
110@@ -4,22 +4,25 @@
111 /**
112 provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
113 */
114- var _baseUrl = "http://api.openweathermap.org/data/2.1/";
115+ var _baseUrl = "http://api.openweathermap.org/data/2.5/";
116+
117 //
118 function _buildSearchResult(request, data) {
119 var searchResult = {
120- results: [],
121+ locations: [],
122 request: request
123 }
124- data.list.forEach(function(r) {
125- searchResult.results.push({
126- service: "openweathermap",
127- service_id: r.id,
128- name: r.name,
129- coord: r.coords,
130- country: (r.sys && r.sys.country) ? r.sys.country : ""
131- });
132- })
133+ if(data.cod === "200" && data.list) {
134+ data.list.forEach(function(r) {
135+ searchResult.locations.push({
136+ service: "openweathermap",
137+ service_id: r.id,
138+ name: r.name,
139+ coord: r.coords,
140+ country: (r.sys && r.sys.country) ? r.sys.country : ""
141+ });
142+ })
143+ }
144 return searchResult;
145 }
146 //
147@@ -73,10 +76,12 @@
148 // TODO date_txt
149 forecastResult.results.push({
150 timestamp: f.dt,
151- temp: f.temp,
152- night: f.night,
153- eve: f.eve,
154- morn: f.morn,
155+ day: f.temp.day,
156+ min: f.temp.min,
157+ max: f.temp.max,
158+ night: f.temp.night,
159+ eve: f.temp.eve,
160+ morn: f.temp.morn,
161 pressure: f.pressure,
162 humidity: f.humidity,
163 condition: f.weather[0],
164@@ -92,14 +97,14 @@
165 getSearchByNameRequest: function(params) {
166 var request = {
167 type: "search",
168- url: _baseUrl+"find/name?q="+encodeURIComponent(params.name)+"&units="+params.units,
169+ url: _baseUrl+"find?q="+encodeURIComponent(params.name)+"&units="+params.units,
170 formatter: _buildSearchResult
171 }
172 return request;
173 },
174 //
175 getSearchByPointRequest: function(params) {
176- var url = _baseUrl+"find/city?lat="+encodeURIComponent(params.coords.lat)
177+ var url = _baseUrl+"find?lat="+encodeURIComponent(params.coords.lat)
178 +"&lon="+encodeURIComponent(params.coords.lon)+"&units="+params.units,
179 request = {
180 type: "search",
181@@ -112,7 +117,7 @@
182 getCurrentConditionRequest: function(params) {
183 var request = {
184 type: "current",
185- url: _baseUrl + "weather/city/"+params.location.service_id+"?units="+params.units,
186+ url: _baseUrl + "weather?id="+params.location.service_id+"&units="+params.units,
187 formatter: _buildCurrentCondition
188 }
189 return request;
190@@ -121,7 +126,7 @@
191 getForecastRequest: function(params) {
192 var request = {
193 type: "forecast",
194- url: _baseUrl + "forecast/city/"+params.location.service_id+"?units="+params.units,
195+ url: _baseUrl + "forecast?id="+params.location.service_id+"&units="+params.units,
196 formatter: _buildForecast
197 }
198 return request;
199@@ -130,7 +135,8 @@
200 getDailyForecastRequest: function(params) {
201 var request = {
202 type: "daily",
203- url: _baseUrl + "forecast/city/"+params.location.service_id+"?mode=daily_compact&units="+params.units,
204+ url: _baseUrl + "forecast/daily?"+params.location.service_id
205+ +"&mode=daily_compact&cnt=10&units="+params.units,
206 formatter: _buildDailyForecast
207 }
208 return request;
209@@ -151,24 +157,32 @@
210 return _services["openweathermap"];
211 }
212 //
213- function _sendRequest(request, onSuccess, onError) {
214- var xmlHttp = new XMLHttpRequest();
215- if (xmlHttp) {
216- console.log(request.url);
217- xmlHttp.open('GET', request.url, true);
218- xmlHttp.onreadystatechange = function () {
219- if (xmlHttp.readyState == 4) {
220- var json = JSON.parse(xmlHttp.responseText);
221- if(xmlHttp.status === 200) {
222- var result = request.formatter(request,json);
223- onSuccess(result);
224- } else {
225- onError({error: true, msg: "something went wrong", request: request});
226+ function _sendRequest(request, onSuccess, onError) {
227+ var xmlHttp = new XMLHttpRequest();
228+ if (xmlHttp) {
229+ console.log(request.url);
230+ xmlHttp.open('GET', request.url, true);
231+ xmlHttp.onreadystatechange = function () {
232+ try {
233+ if (xmlHttp.readyState == 4) {
234+ //console.log(xmlHttp.responseText);
235+ var json = JSON.parse(xmlHttp.responseText);
236+ if(xmlHttp.status === 200) {
237+ var result = request.formatter(request,json);
238+ onSuccess(result);
239+ } else {
240+ onError({
241+ msg: "wrong response http code, got "+xmlHttp.status,
242+ request: request
243+ });
244+ }
245+ }
246+ } catch (e) {
247+ onError({msg: "wrong response data format", request: request});
248 }
249- }
250- };
251- xmlHttp.send(null);
252- }
253+ };
254+ xmlHttp.send(null);
255+ }
256 }
257 //
258 return {
259@@ -197,22 +211,106 @@
260 _sendRequest(request, onSuccess, onError);
261 },
262 //
263+ search: function(mode, params, onSuccess, onError) {
264+ var searchFunc = (mode === "point") ? this.searchLocationByPoint: this.searchLocationByName,
265+ retryHandler = (function(err) {
266+ console.log("search retry of "+err.request.url);
267+ searchFunc(params, onSuccess, onError);
268+ });
269+ searchFunc(params, onSuccess, retryHandler);
270+ },
271+ //
272 getLocationData: function(params, onSuccess, onError) {
273- var response = {
274- location: params.location
275+ var retryMap = {
276+ current:this.getCurrentCondition,
277+ daily:this.getDailyForecast,
278+ forecast:this.getForecast
279+ },
280+ response = {
281+ location: params.location,
282+ db: (params.db) ? params.db : null
283 },
284 addDataToResponse = (function(data) {
285 response[data.request.type] = data;
286- if(response["current"] !== undefined && response["forecast"] !== undefined && response["daily"] !== undefined) {
287+ if(response["current"] !== undefined
288+ && response["forecast"] !== undefined
289+ && response["daily"] !== undefined) {
290 onSuccess(response);
291 }
292- });
293+ }),
294+ onErrorHandler = (function(err) {
295+ onError(err);
296+ }),
297+ retryHandler = (function(err) {
298+ console.log("retry of "+err.request.url);
299+ var retryFunc = retryMap[err.request.type];
300+ retryFunc(params, addDataToResponse, onErrorHandler);
301+ })
302
303- this.getCurrentCondition(params, addDataToResponse, addDataToResponse)
304- this.getForecast(params, addDataToResponse, addDataToResponse);
305- this.getDailyForecast(params, addDataToResponse, addDataToResponse);
306+ this.getCurrentCondition(params, addDataToResponse, retryHandler);
307+ this.getForecast(params, addDataToResponse, retryHandler);
308+ this.getDailyForecast(params, addDataToResponse, retryHandler);
309 }
310 }
311 })({
312 "openweathermap": OpenWeatherMapApi
313 });
314+
315+
316+/*
317+ following WorkerScript handles the data requests against the weather API.
318+ "message" requires a "params" property with the required params to perform
319+ the API call and an "action" property, which will be added also to the response.
320+*/
321+if(typeof WorkerScript != "undefined") {
322+ WorkerScript.onMessage = function(message) {
323+ // handles the response data
324+ var finished = function(result) {
325+ WorkerScript.sendMessage({
326+ action: message.action,
327+ result: result
328+ })
329+ }
330+ // handles errors
331+ var onError = function(err) {
332+ console.log(JSON.stringify(err, null, true));
333+ WorkerScript.sendMessage({ 'error': err})
334+ }
335+ // keep order of locations, sort results
336+ var sortDataResults = function(locA, locB) {
337+ return locA.db.id - locB.db.id;
338+ }
339+ // perform the api calls
340+ if(message.action === "searchByName") {
341+ WeatherApi.search("name", message.params, finished, onError);
342+ } else if(message.action === "searchByPoint") {
343+ WeatherApi.search("point", message.params, finished, onError);
344+ } else if(message.action === "newLocationData") {
345+ WeatherApi.getLocationData(message.params, finished, onError);
346+ } else if(message.action === "updateData") {
347+ var locLength = message.params.locations.length,
348+ locUpdated = 0,
349+ result = [];
350+ if(locLength > 0) {
351+ message.params.locations.forEach(function(loc) {
352+ var updatedHnd = function (newData) {
353+ locUpdated += 1;
354+ result.push(newData);
355+ if(locUpdated === locLength) {
356+ result.sort(sortDataResults);
357+ finished(result);
358+ }
359+ },
360+ params = {
361+ location:loc.location,
362+ db: loc.db,
363+ units: message.params.units
364+ };
365+ WeatherApi.getLocationData(params, updatedHnd, onError);
366+ })
367+ } else {
368+ finished(result);
369+ }
370+ }
371+ }
372+}
373
374=== modified file 'ubuntu-weather-app.qml'
375--- ubuntu-weather-app.qml 2013-04-25 13:04:02 +0000
376+++ ubuntu-weather-app.qml 2013-04-27 14:57:26 +0000
377@@ -2,7 +2,6 @@
378 import Ubuntu.Components 0.1
379 import "components" as Components
380 import Ubuntu.Components.Popups 0.1
381-import "components/WeatherApi.js" as Api
382
383 MainView {
384 // objectName for functional testing purposes (autopilot-qt5)
385@@ -17,6 +16,25 @@
386 property var locationsList: []
387 property var tabsObject: null
388
389+ WorkerScript {
390+ id: locationDataWorker
391+ source: "components/WeatherApi.js"
392+ onMessage: {
393+ if(!messageObject.error) {
394+ if(messageObject.action === "updateData") {
395+ messageObject.result.forEach(function(loc) {
396+ storage.updateLocation(loc.db.id, loc);
397+ });
398+ buildTabs(messageObject.result);
399+ } else if(messageObject.action === "newLocationData") {
400+ onNewLocationDataSuccess(messageObject.result);
401+ }
402+ } else {
403+ console.log(messageObject.error.msg+" / "+messageObject.error.request.url)
404+ }
405+ }
406+ }
407+
408 // see https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1124071, temporary workaround
409 function buildTabs(locations) {
410 locationsList = locations;
411@@ -43,13 +61,10 @@
412 }
413
414 function locationAdded(locationObj) {
415- Api.WeatherApi.getLocationData({location:locationObj, units: "metric"}, onNewLocationDataSuccess, onApiError);
416- }
417-
418- function onApiError(err) {
419- // TODO show error popup
420- console.log("onApiError")
421- console.log(err.msg+" / "+err.request.url)
422+ locationDataWorker.sendMessage({
423+ action: "newLocationData",
424+ params: {location:locationObj, units:"metric"}
425+ })
426 }
427
428 function onNewLocationDataSuccess(resp) {
429@@ -59,20 +74,10 @@
430
431 function refreshData() {
432 storage.getLocations(function(locations) {
433- var locLength = locations.length,
434- locUpdated = 0;
435- if(locLength > 0) {
436- locations.forEach(function(loc) {
437- var updatedHnd = function (newData) {
438- locUpdated += 1;
439- storage.updateLocation(loc.db.id, newData);
440- if(locUpdated === locLength) {
441- buildTabs(locations);
442- }
443- }
444- Api.WeatherApi.getLocationData({location:loc.location, units: "metric"}, updatedHnd, onApiError);
445- })
446- } else buildTabs(locations);
447+ locationDataWorker.sendMessage({
448+ action: "updateData",
449+ params: {locations:locations, units:"metric"}
450+ })
451 });
452 }
453

Subscribers

People subscribed via source and target branches