Merge lp:~nik90/ubuntu-clock-app/transition-worldclock-u1db into lp:ubuntu-clock-app/saucy

Proposed by Nekhelesh Ramananthan
Status: Merged
Approved by: Nekhelesh Ramananthan
Approved revision: 331
Merged at revision: 329
Proposed branch: lp:~nik90/ubuntu-clock-app/transition-worldclock-u1db
Merge into: lp:ubuntu-clock-app/saucy
Diff against target: 540 lines (+106/-255)
6 files modified
clock/ClockPage.qml (+80/-31)
clock/WorldClock.qml (+5/-4)
clock/WorldClockModel.qml (+0/-199)
tests/autopilot/ubuntu_clock_app/emulators.py (+1/-1)
tests/autopilot/ubuntu_clock_app/tests/test_clock.py (+20/-12)
ubuntu-clock-app.qml (+0/-8)
To merge this branch: bzr merge lp:~nik90/ubuntu-clock-app/transition-worldclock-u1db
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Riccardo Padovani Approve
Andrew Hayzen u1db Pending
Ubuntu Clock Developers Pending
Review via email: mp+204967@code.launchpad.net

Commit message

Transitions the storage of world clocks and current location of user from LocalStorage to U1db.

Description of the change

Transitions the storage of world clocks and current location of user from LocalStorage to U1db. With this the clock app uses U1db entirely.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

Autopilot tests work as expected!

review: Approve
Revision history for this message
Nekhelesh Ramananthan (nik90) wrote :

Approving MP since a preliminary u1db code review was done by christian.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nekhelesh Ramananthan (nik90) wrote :

Random AP failure, approving again.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'clock/ClockPage.qml'
2--- clock/ClockPage.qml 2014-02-03 13:15:46 +0000
3+++ clock/ClockPage.qml 2014-02-05 16:44:42 +0000
4@@ -19,9 +19,10 @@
5 */
6
7 import QtQuick 2.0
8+import U1db 1.0 as U1db
9 import Ubuntu.Components 0.1
10+import QtQuick.XmlListModel 2.0
11 import Ubuntu.Components.ListItems 0.1 as ListItem
12-import QtQuick.XmlListModel 2.0
13 import "../common/ClockUtils.js" as Utils
14 import "../common"
15
16@@ -32,22 +33,47 @@
17 property string currentTimeFormatted
18 property real currentUTCTime
19 property real diff: new Date().getTimezoneOffset()
20- property alias currentCity: currentLocationCity.text
21- property alias currentLat: easterEggCircle.latitude
22- property alias currentLng: easterEggCircle.longitude
23+
24+ // Property to store the user location (city, longitude, latitude)
25+ property var userLocation: { "city": i18n.tr("Retrieving current location..."), "lng": 200, "lat": 200 }
26+
27+ // U1dB document to store the current location of the user
28+ U1db.Document {
29+ id: currentLocation
30+ database: db
31+ create: true
32+ docId: "currentUserLocation"
33+ defaults: { "city": "undefined", "lat": 200, "lng": 200 }
34+ }
35+
36+ // U1db Index to index all documents storing the world location details
37+ U1db.Index {
38+ database: db
39+ id: by_worldclock
40+ expression: ["worldlocation.city", "worldlocation.rawOffSet", "worldlocation.longitude", "worldlocation.latitude"]
41+ }
42+
43+ U1db.Query {
44+ id: worldClock
45+ index: by_worldclock
46+ query: ["*","*", "*", "*"]
47+ }
48
49 Component.onCompleted: {
50- // TODO: Query GPS for location data
51 Utils.log("ClockPage loaded");
52- if (worldModel.city !== "undefined") {
53- currentCity = worldModel.city;
54- currentLng = worldModel.longitude;
55- currentLat = worldModel.latitude;
56- }
57- Utils.log("Saved Current Location: " + worldModel.city);
58- Utils.log("Saved Current Location Coordinates: " + currentLng + "," + currentLat)
59 currentTimeFormatted = Utils.convertTime(new Date().getHours(), new Date().getMinutes(), new Date().getUTCSeconds(), appSetting.contents.timeFormat)
60 updateTime();
61+
62+ if (currentLocation.contents.city !== "undefined") {
63+ userLocation = {
64+ city: currentLocation.contents.city,
65+ lng: currentLocation.contents.lng,
66+ lat: currentLocation.contents.lat
67+ }
68+ }
69+
70+ Utils.log("Saved Current Location: " + currentLocation.contents.city);
71+ Utils.log("Saved Current Location Coordinates: " + currentLocation.contents.lng + "," + currentLocation.contents.lat)
72 }
73
74 actions: [
75@@ -62,6 +88,27 @@
76 }
77 ]
78
79+ // Function to save a world location to u1db database
80+ function addWorldLocation(cityName, diff, longitude, latitude) {
81+ Utils.log("Adding world location")
82+ db.putDoc({ "worldlocation": { "city": cityName, "rawOffSet": diff, "longitude": longitude, "latitude": latitude }}, encodeURIComponent(cityName+"_"+longitude+"_"+latitude))
83+ }
84+
85+ // Function to update current location of the userand save it to the database
86+ function updateCurrentLocation(cityname, longitude, latitude) {
87+ currentLocation.contents = {
88+ city: cityname,
89+ lng: longitude,
90+ lat: latitude
91+ }
92+
93+ userLocation = {
94+ city: cityname,
95+ lng: longitude,
96+ lat: latitude
97+ }
98+ }
99+
100 // Function which runs every second to update the clock label
101 function onTimerUpdate(now) {
102 currentTimeFormatted = Utils.convertTime(now.getHours(), now.getMinutes(), now.getUTCSeconds(), appSetting.contents.timeFormat)
103@@ -91,23 +138,20 @@
104 GeoIPModel {
105 id: geoIP
106 onStatusChanged: {
107- if (status == XmlListModel.Ready && geoIP.get(0).city != worldModel.city && geoIP.get(0).city != "None") {
108+ if (status == XmlListModel.Ready && geoIP.get(0).city !== currentLocation.contents.city && geoIP.get(0).city !== "None") {
109 Utils.log("Retrieved current location using GeoIP. Writing into database and setting it as current location")
110- currentCity = geoIP.get(0).city
111- currentLng = geoIP.get(0).lng
112- currentLat = geoIP.get(0).lat
113- worldModel.appendCurrentLocation(currentCity, currentLng, currentLat);
114+ updateCurrentLocation(geoIP.get(0).city, geoIP.get(0).lng, geoIP.get(0).lat);
115 }
116- else if (status == XmlListModel.Ready && geoIP.get(0).city == "None") {
117- if (worldModel.city == "undefined") {
118- currentCity = i18n.tr("Set current location name")
119+ else if (status == XmlListModel.Ready && geoIP.get(0).city === "None") {
120+ if (currentLocation.contents.city === "undefined") {
121+ userLocation.city = i18n.tr("Set current location name")
122 currentLocation.progression = true
123 }
124 }
125 else if(status == XmlListModel.Error) {
126 Utils.error("Unable to retrieve GeoIP Data -> " + geoIP.errorString())
127- if (worldModel.city == "undefined") {
128- currentCity = i18n.tr("Set current location name")
129+ if (currentLocation.contents.city === "undefined") {
130+ userLocation.city = i18n.tr("Set current location name")
131 currentLocation.progression = true
132 }
133 }
134@@ -147,7 +191,7 @@
135 }
136
137 onClicked: {
138- if (easterEggCircle.isReady == XmlListModel.Ready && worldModel.city !== "undefined")
139+ if (easterEggCircle.isReady == XmlListModel.Ready && currentLocation.contents.city !== "undefined")
140 clockFace.state == "" ? clockFace.state = "easteregg" : clockFace.state = "";
141 else
142 Utils.error("Sunrise/Sunset times cannot be loaded without setting the current location or without a internet connection.")
143@@ -159,6 +203,8 @@
144 anchors.centerIn: parent
145 width: clockFace.centerItem.width; height: width;
146 radius: width / 2;
147+ latitude: userLocation.lat
148+ longitude: userLocation.lng
149 }
150
151 states: [
152@@ -198,11 +244,9 @@
153 }
154
155 ListItem.Standard {
156- id: currentLocation
157-
158 Label {
159 id: currentLocationCity
160- text: i18n.tr("Retrieving current location...")
161+ text: userLocation.city
162 elide: Text.ElideRight
163 anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: currentLocationTime.left; margins: units.gu(3) }
164 color: Theme.palette.normal.baseText
165@@ -210,7 +254,7 @@
166 }
167 Label {
168 id: currentLocationTime
169- visible: worldModel.city !== "undefined" ? true : false
170+ visible: currentLocation.contents.city !== "undefined" ? true : false
171 text: currentTimeFormatted
172 anchors { verticalCenter: parent.verticalCenter; right: parent.right; rightMargin: currentLocation.progression == true ? units.gu(6) : units.gu(2) }
173 color: Theme.palette.normal.baseText
174@@ -236,14 +280,14 @@
175 clip: true
176 anchors { left: parent.left; right: parent.right }
177 height: 3 * units.gu(6) + units.gu(1) // height is defined to show 3 items by default with a small margin
178- model: worldModel
179+ model: worldClock
180 currentIndex: -1
181
182 delegate: ListItem.Standard {
183 objectName: "savedworldcity" + index
184 Label {
185 id: worldCityName
186- text: cityName
187+ text: model.contents.city
188 objectName: "city_name"
189 elide: Text.ElideRight
190 anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: worldCityTime.left; margins: units.gu(3) }
191@@ -253,7 +297,7 @@
192
193 Label {
194 id: worldCityTime
195- text: appSetting.contents.timeFormat === "12-hour" ? Qt.formatTime(new Date(currentUTCTime + (rawOffSet * 60000)), "h:mm AP") : Qt.formatTime(new Date(currentUTCTime + (rawOffSet * 60000)), "hh:mm")
196+ text: appSetting.contents.timeFormat === "12-hour" ? Qt.formatTime(new Date(currentUTCTime + (model.contents.rawOffSet * 60000)), "h:mm AP") : Qt.formatTime(new Date(currentUTCTime + (model.contents.rawOffSet * 60000)), "hh:mm")
197 anchors { verticalCenter: parent.verticalCenter; right: parent.right; rightMargin: units.gu(2) }
198 color: Theme.palette.normal.baseText
199 fontSize: "large"
200@@ -263,7 +307,12 @@
201 removable: true
202 confirmRemoval: true
203
204- onItemRemoved: worldModel.removePreset(index);
205+ onItemRemoved: {
206+ // NOTE: This causes the document to be deleted twice resulting in an error.
207+ // The bug has been reported at https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1276118
208+ Utils.log("Deleting world location: " + model.contents.cityname)
209+ db.putDoc("", model.docId)
210+ }
211 }
212 }
213 }
214
215=== modified file 'clock/WorldClock.qml'
216--- clock/WorldClock.qml 2014-02-01 15:30:26 +0000
217+++ clock/WorldClock.qml 2014-02-05 16:44:42 +0000
218@@ -18,8 +18,9 @@
219
220 import QtQuick 2.0
221 import Ubuntu.Components 0.1
222+import QtQuick.XmlListModel 2.0
223 import Ubuntu.Components.ListItems 0.1 as ListItem
224-import QtQuick.XmlListModel 2.0
225+import "../common/ClockUtils.js" as Utils
226
227 // Page to to search a world city and add it to the world clocks
228 Page {
229@@ -32,7 +33,7 @@
230 property alias setFocus: searchBox.setFocus
231
232 visible: false;
233- title: isWorldCity ? i18n.tr("Add City") : i18n.tr("Edit Current Location")
234+ title: isWorldCity ? i18n.tr("Add City") : i18n.tr("Edit Current Location")
235
236 Column {
237 id: worldClocks
238@@ -65,9 +66,9 @@
239 onStatusChanged: {
240 if(status == XmlListModel.Ready && worldClocks.city != "null") {
241 if (isWorldCity)
242- worldModel.appendPreset(worldClocks.city, getTimeDifference(cityDetailsModel.get(0).time), worldClocks.lng, worldClocks.lat);
243+ clockPage.addWorldLocation(worldClocks.city, getTimeDifference(cityDetailsModel.get(0).time), worldClocks.lng, worldClocks.lat)
244 else
245- worldModel.appendCurrentLocation(worldClocks.city, worldClocks.lng, worldClocks.lat);
246+ clockPage.updateCurrentLocation(worldClocks.city, worldClocks.lng, worldClocks.lat)
247 worldClocks.clearUserSearch();
248 pageStack.pop()
249 }
250
251=== removed file 'clock/WorldClockModel.qml'
252--- clock/WorldClockModel.qml 2014-02-01 15:43:50 +0000
253+++ clock/WorldClockModel.qml 1970-01-01 00:00:00 +0000
254@@ -1,199 +0,0 @@
255-/*
256- * Copyright (C) 2013 Canonical Ltd
257- *
258- * This program is free software: you can redistribute it and/or modify
259- * it under the terms of the GNU General Public License version 3 as
260- * published by the Free Software Foundation.
261- *
262- * This program is distributed in the hope that it will be useful,
263- * but WITHOUT ANY WARRANTY; without even the implied warranty of
264- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
265- * GNU General Public License for more details.
266- *
267- * You should have received a copy of the GNU General Public License
268- * along with this program. If not, see <http://www.gnu.org/licenses/>.
269- *
270- * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
271- */
272-
273-import QtQuick 2.0
274-import QtQuick.LocalStorage 2.0
275-import "../common/ClockUtils.js" as Utils
276-
277-ListModel {
278- id: model
279-
280- readonly property string dbName: "ubuntu-clock-app"
281- readonly property string dbDescription: "World Clock"
282-
283- property string city: "undefined";
284- property real longitude: 200;
285- property real latitude: 200;
286-
287- property var _db: null
288-
289- function appendCurrentLocation(cityname, longitude, latitude)
290- {
291- try {
292- _db.transaction(function(tx){
293- var currentValue;
294- var res = tx.executeSql('SELECT * FROM CurrentClock');
295-
296- if (res.rows.length > 0)
297- currentValue = res.rows.item(0).cityName;
298-
299- if (currentValue !== undefined) // Update existing value or insert if none
300- res = tx.executeSql('UPDATE CurrentClock SET cityName = ?, longitude = ?, latitude = ?', [cityname, longitude, latitude]);
301- else
302- res = tx.executeSql('INSERT INTO CurrentClock VALUES(?, ?, ?)', [cityname, longitude, latitude]);
303-
304- clockPage.currentCity = model.city = cityname
305- clockPage.currentLng = model.longitude = longitude
306- clockPage.currentLat = model.latitude = latitude
307- Utils.log("Updating current location (manually)")
308- });
309- } catch (err) {
310- Utils.error("Error setting current location '" + "': " + err);
311- return false;
312- }
313- }
314-
315- function indexOf(cityName)
316- {
317- for(var i = 0; i < model.count; i++) {
318- var element = model.get(i);
319-
320- if(element.cityName == cityName) {
321- return i;
322- }
323- }
324-
325- return -1;
326- }
327-
328- function appendPreset(cityName, rawOffSet, longitude, latitude)
329- {
330- if (_appendDB(cityName, rawOffSet, longitude, latitude)) {
331- var index = indexOf(cityName);
332- var location = {"cityName" : cityName, "rawOffSet" : rawOffSet, "longitude" : longitude, "latitude" : latitude}
333-
334- if(index != -1) {
335- model.set(index, location);
336- } else {
337- model.append(location);
338- }
339- }
340- }
341-
342- function removePreset(index)
343- {
344- var cityName = get(index).cityName
345- if (_removeDB(cityName)) {
346- model.remove(index);
347- }
348- }
349-
350- function _createDB() {
351- if (_db == null) return false;
352- try {
353- _db.transaction(function(tx){
354- tx.executeSql('CREATE TABLE IF NOT EXISTS WorldClock(cityName TEXT, rawOffSet REAL, longitude REAL, latitude REAL)');
355- });
356- } catch (err) {
357- Utils.error("Error creating WorldClock table in db:" + err);
358- return false;
359- }
360- try {
361- _db.transaction(function(tx){
362- tx.executeSql('CREATE TABLE IF NOT EXISTS CurrentClock(cityName TEXT, longitude REAL, latitude REAL)');
363- });
364- } catch (err) {
365- Utils.error("Error creating CurrentClock table in db:" + err);
366- return false;
367- }
368- return true;
369- }
370-
371- function _loadDB() {
372- _db = LocalStorage.openDatabaseSync(model.dbName, "", model.dbDescription, 1000);
373- if (_db == null) return false;
374- if (!_createDB()) {
375- _db = null;
376- return false;
377- }
378- try {
379- _db.readTransaction(function(tx){
380- var res = tx.executeSql('SELECT * FROM WorldClock');
381- if (res.rows.length > 0) {
382- for (var i = 0; i < res.rows.length; i++) {
383- model.append({"cityName" : res.rows.item(i).cityName,
384- "rawOffSet": res.rows.item(i).rawOffSet,
385- "longitude": res.rows.item(i).longitude,
386- "latitude" : res.rows.item(i).latitude
387- });
388- }
389- }
390- });
391- } catch (err) {
392- Utils.error("Error opening WorldClock database (" + err + ")");
393- _db = null
394- return false;
395- }
396- try {
397- _db.readTransaction(function(tx){
398- var currentstate = tx.executeSql('SELECT * FROM CurrentClock ORDER BY cityName');
399- if (currentstate.rows.length > 0) {
400- city = currentstate.rows.item(0).cityName;
401- longitude = currentstate.rows.item(0).longitude;
402- latitude = currentstate.rows.item(0).latitude;
403- }
404- });
405- } catch (err) {
406- Utils.error("Error opening CurrentLocation database (" + err + ")");
407- _db = null
408- return false;
409- }
410- return true;
411- }
412-
413- function _appendDB(cityName, rawOffSet, longitude, latitude) {
414- if (_db == null) return false;
415-
416- try {
417- _db.transaction(function(tx){
418- var currentValue;
419- var res = tx.executeSql('SELECT rawOffSet FROM WorldClock WHERE cityName=?', cityName);
420- if (res.rows.length > 0) {
421- currentValue = res.rows.item(0).rawOffSet;
422- }
423-
424- if (currentValue !== rawOffSet) {
425- // Update existing value or insert if none
426- if (currentValue !== undefined) {
427- res = tx.executeSql('UPDATE WorldClock SET rawOffSet = ?, longitude = ?, latitude = ? WHERE cityName = ?', [rawOffSet, longitude, latitude, cityName]);
428- } else {
429- res = tx.executeSql('INSERT INTO WorldClock VALUES(?, ?, ?, ?)', [cityName, rawOffSet, longitude, latitude]);
430- }
431- }
432- });
433- } catch (err) {
434- Utils.error("Error setting labeltxt '"+ label + "': " + err);
435- return false;
436- }
437- return true;
438- }
439-
440- function _removeDB(cityName) {
441- if (_db == null) return false;
442-
443- try {
444- _db.transaction(function(tx){
445- tx.executeSql('DELETE FROM WorldClock WHERE cityName = ?', [cityName]);
446- });
447- } catch (err) {
448- Utils.error("Error delete WorldClock'" + cityName + "': " + err);
449- return false;
450- }
451- return true;
452- }
453-}
454
455=== modified file 'tests/autopilot/ubuntu_clock_app/emulators.py'
456--- tests/autopilot/ubuntu_clock_app/emulators.py 2014-01-17 16:20:58 +0000
457+++ tests/autopilot/ubuntu_clock_app/emulators.py 2014-02-05 16:44:42 +0000
458@@ -83,7 +83,7 @@
459
460 def get_city_name(self, city):
461 """Return the name of the city passed as argument"""
462- return city.select_single('Label', objectName='city_name').text
463+ return city.wait_select_single('Label', objectName='city_name').text
464
465 def get_saved_cities_list(self):
466 """Return the component containing list of user saved world city locations."""
467
468=== modified file 'tests/autopilot/ubuntu_clock_app/tests/test_clock.py'
469--- tests/autopilot/ubuntu_clock_app/tests/test_clock.py 2014-01-06 20:53:01 +0000
470+++ tests/autopilot/ubuntu_clock_app/tests/test_clock.py 2014-02-05 16:44:42 +0000
471@@ -47,6 +47,21 @@
472 def tearDown(self):
473 super(TestClock, self).tearDown()
474
475+ def add_local_world_city(self):
476+ # Open clock toolbar
477+ self.main_view.open_toolbar()
478+
479+ # Click add city toolbar button
480+ self.main_view.get_toolbar().click_button('addCityAction')
481+
482+ # Click on first city in the list shown and ensure that it was saved
483+ city = self.main_view.grab_first_listed_city()
484+
485+ city_name = self.main_view.get_city_name(city)
486+ self.main_view.select_city(city)
487+
488+ return city_name
489+
490 def test_label_value(self):
491 """The clock label must contain the correct time."""
492
493@@ -59,24 +74,17 @@
494 def test_add_local_world_city(self):
495 """Test to check if adding a local listed world city works"""
496
497- # Open clock toolbar
498- self.main_view.open_toolbar()
499-
500- # Click add city toolbar button
501- self.main_view.get_toolbar().click_button('addCityAction')
502-
503- # Click on first city in the list shown and ensure that it was saved
504- city = self.main_view.grab_first_listed_city()
505- city_name = self.main_view.get_city_name(city)
506- self.main_view.select_city(city)
507+ # Add a local world city
508+ city_name = self.add_local_world_city()
509+
510 saved_city_name = self.main_view.get_city_name(self.main_view.grab_first_saved_city())
511 self.assertThat(saved_city_name, Eventually(Equals(city_name)))
512
513 def test_delete_local_world_city(self):
514 """Test to check if deleting a saved local world city works"""
515
516- # Add a local world city
517- self.test_add_local_world_city()
518+ # Add a local world city
519+ city_name = self.add_local_world_city()
520
521 # Close toolbar and drag page up to see the saved world city list
522 self.main_view.close_toolbar()
523
524=== modified file 'ubuntu-clock-app.qml'
525--- ubuntu-clock-app.qml 2014-02-04 11:38:16 +0000
526+++ ubuntu-clock-app.qml 2014-02-05 16:44:42 +0000
527@@ -150,14 +150,6 @@
528 // Tab content begins here
529 page: ClockPage {
530 id: clockPage
531-
532- WorldClockModel {
533- id: worldModel;
534- Component.onCompleted: {
535- _loadDB();
536- Utils.log("Clock Database loaded")
537- }
538- }
539 }
540 }
541

Subscribers

People subscribed via source and target branches