Merge lp:~tomdroid-dev/tomdroid/two-way-sync into lp:~tomdroid-maintainers/tomdroid/main
- two-way-sync
- Merge into main
Status: | Rejected | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Rejected by: | Stefan Hammer | ||||||||||||
Proposed branch: | lp:~tomdroid-dev/tomdroid/two-way-sync | ||||||||||||
Merge into: | lp:~tomdroid-maintainers/tomdroid/main | ||||||||||||
Diff against target: |
3957 lines (+2175/-640) 32 files modified
.classpath (+1/-1) AndroidManifest.xml (+3/-4) res/layout/main.xml (+1/-0) res/layout/note_view.xml (+42/-32) res/menu/main.xml (+5/-0) res/menu/view_note.xml (+48/-0) res/values/strings.xml (+6/-6) res/values/styles.xml (+14/-0) res/xml/preferences.xml (+5/-6) src/org/tomdroid/Note.java (+170/-42) src/org/tomdroid/NoteManager.java (+93/-77) src/org/tomdroid/NoteProvider.java (+5/-2) src/org/tomdroid/sync/LocalStorage.java (+156/-0) src/org/tomdroid/sync/SyncManager.java (+24/-22) src/org/tomdroid/sync/SyncMethod.java (+62/-102) src/org/tomdroid/sync/sd/SdCardSyncService.java (+71/-63) src/org/tomdroid/sync/web/OAuthConnection.java (+2/-0) src/org/tomdroid/sync/web/SnowySyncMethod.java (+116/-91) src/org/tomdroid/sync/web/SyncServer.java (+205/-0) src/org/tomdroid/ui/PreferencesActivity.java (+48/-45) src/org/tomdroid/ui/Rotate3dAnimation.java (+92/-0) src/org/tomdroid/ui/SyncMessageHandler.java (+8/-8) src/org/tomdroid/ui/Tomdroid.java (+32/-23) src/org/tomdroid/ui/ViewNote.java (+234/-109) src/org/tomdroid/ui/ViewSwitcher.java (+152/-0) src/org/tomdroid/util/NoteListCursorAdapter.java (+7/-5) src/org/tomdroid/util/Preferences.java (+2/-2) tests/org/tomdroid/sync/TestLocalStorage.java (+78/-0) tests/org/tomdroid/sync/web/MockSyncServer.java (+167/-0) tests/org/tomdroid/sync/web/MockedSyncServerTestCase.java (+81/-0) tests/org/tomdroid/sync/web/TestFetchingFromServer.java (+119/-0) tests/org/tomdroid/sync/web/TestUpdatingTheServer.java (+126/-0) |
||||||||||||
To merge this branch: | bzr merge lp:~tomdroid-dev/tomdroid/two-way-sync | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Olivier Bilodeau | Pending | ||
Review via email: mp+38072@code.launchpad.net |
Commit message
Description of the change
This branch introduces basic two-way syncing with the Web Sync Method. It's now possible to edit a note and push changes to the server. Collisions are handled by leaving both versions in the Note. Other features like creating and deleting Notes or having a fancy Edit View should be seperated tasks.
Olivier Bilodeau (plaxx) wrote : | # |
Stefan Hammer (j-4-deactivatedaccount) wrote : | # |
I think, if we get this awesome editing feature into the next release, we should do some minor changes to significantly enhance usability.
In list view it would be easy to implement and really awesome to have
* Longpress -> Delete
* Longpress -> Edit
In "Note view" and "Edit view" I would remove the Menu -> Settings Button, because the settings do not affect viewing Notes or editing them, therefore the button is just confusing. Atm, the settings screen is just for synchronisation. So I would just display it in the List view.
Also displaying the Sync Button, while displaying/editing a note is not user-friendly, because the note won't update itself while in "Note view" or "Edit view". You have to go back to the list and "reload" the note. Alternatively one could refresh the note after synchronising.
In note view we could add a menu button "List View" to get back to the List immediately. Now if you press several internal links, the back button is not really convenient.
I would also recommend to cut away the <note-content version="0.1"> tag at the beginning and the end. this is easily done and makes the note much more readable while editing.
Additionally it would be awesome to have a separate text field for the title, as it is on Ubuntu One online. I think this is also easily done, by just cutting the string at the first line break.
I hope my suggested optimisations are not too much effort, but for me it sounds, as one who knows the code just has to add/delete a view lines.
Jango
Stefan Hammer (j-4-deactivatedaccount) wrote : | # |
While using this branch, I discovered a very confusing issue: How is a note saved? Pressing the back button in Edit view does not save it, while using Menu - view does. Wouldn't it be better to call it just "save"?
What if one wants to discard his/her changes? Is it obvious enough to press back? I think we should include a "Discard" button.
What's about making a new note? is this feature already included?
=== All my suggestions summarised: ===
Menu items that make sense in my eyes:
* List view: New Note, About, Reset DB, Settings
* Note view: View List, Edit, Delete
* Edit view: Save, Discard, Delete
Hide Sync Button in Note view and Edit view.
And Longpress events in List view:
* Longpress -> Delete
* Longpress -> Edit
* Longpress -> Send (already included)
One could use this icons: (@android:
* New Note: ic_menu_compose
* About: ic_menu_
* Reset DB: ic_menu_revert
* Settings: ic_menu_preferences
* View List: ic_menu_home or ic_menu_
* Edit: ic_menu_edit
* Delete: ic_menu_delete
* Save: ic_menu_save
* Discard: ic_menu_
Unmerged revisions
- 280. By Rodja
-
merged with master (Tomdroid version 0.4)
- 279. By Rodja
-
added missing copyright informations
- 278. By Rodja
-
removed warnings and added gpl header
- 277. By Rodja
-
Non-synced Notes are displayed as 'locally modified'
- 276. By Rodja
-
merged with current sync-ui to get all the great improvements
- 275. By Rodja
-
Using doubletap to switch between view and edit mode because longpress is needed for copy/paste.
- 274. By Rodja
-
Removed obsolete member flag.
- 273. By Rodja
-
Added menu action to reset all local changes and get a clean sync from server.
- 272. By Rodja
-
Rebuilding content each time viewNote is called to show the editings.
- 271. By Rodja
-
Changing options menu according to edit mode.
Preview Diff
1 | === modified file '.classpath' |
2 | --- .classpath 2010-08-31 21:44:24 +0000 |
3 | +++ .classpath 2010-10-10 07:54:44 +0000 |
4 | @@ -1,10 +1,10 @@ |
5 | <?xml version="1.0" encoding="UTF-8"?> |
6 | <classpath> |
7 | <classpathentry kind="src" path="src"/> |
8 | + <classpathentry kind="src" path="tests"/> |
9 | <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> |
10 | <classpathentry kind="src" path="gen"/> |
11 | <classpathentry kind="lib" path="lib/signpost-core-1.2.1.1.jar"/> |
12 | <classpathentry kind="lib" path="lib/signpost-commonshttp4-1.2.1.1.jar"/> |
13 | <classpathentry kind="output" path="bin"/> |
14 | - <classpathentry kind="src" path="tests"/> |
15 | </classpath> |
16 | |
17 | === modified file 'AndroidManifest.xml' |
18 | --- AndroidManifest.xml 2010-10-09 20:20:32 +0000 |
19 | +++ AndroidManifest.xml 2010-10-10 07:54:44 +0000 |
20 | @@ -48,10 +48,9 @@ |
21 | |
22 | </activity> |
23 | |
24 | - <uses-library android:name="android.test.runner" /> |
25 | + <uses-library android:name="android.test.runner" /> |
26 | </application> |
27 | |
28 | - <uses-permission android:name="android.permission.INTERNET" /> |
29 | -<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="org.tomdroid"></instrumentation> |
30 | - |
31 | + <uses-permission android:name="android.permission.INTERNET" /> |
32 | + <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="org.tomdroid"></instrumentation> |
33 | </manifest> |
34 | |
35 | === modified file 'res/layout/main.xml' |
36 | --- res/layout/main.xml 2010-09-26 19:57:31 +0000 |
37 | +++ res/layout/main.xml 2010-10-10 07:54:44 +0000 |
38 | @@ -5,6 +5,7 @@ |
39 | http://www.launchpad.net/tomdroid |
40 | |
41 | Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org> |
42 | + Copyright 2010 Rodja Trappe <mail@rodja.net> |
43 | |
44 | This file is part of Tomdroid. |
45 | |
46 | |
47 | === modified file 'res/layout/note_view.xml' |
48 | --- res/layout/note_view.xml 2010-09-26 19:57:31 +0000 |
49 | +++ res/layout/note_view.xml 2010-10-10 07:54:44 +0000 |
50 | @@ -5,6 +5,7 @@ |
51 | http://www.launchpad.net/tomdroid |
52 | |
53 | Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org> |
54 | + Copyright 2010 Rodja Trappe <mail@rodja.net> |
55 | |
56 | This file is part of Tomdroid. |
57 | |
58 | @@ -21,40 +22,49 @@ |
59 | You should have received a copy of the GNU General Public License |
60 | along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
61 | --> |
62 | + |
63 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
64 | android:layout_width="fill_parent" |
65 | android:layout_height="fill_parent" |
66 | android:orientation="vertical" |
67 | - > |
68 | -<include android:id="@+id/actionbar" layout="@layout/actionbar" /> |
69 | -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
70 | - android:id="@+id/textScroller" |
71 | - android:layout_width="fill_parent" |
72 | - android:layout_height="fill_parent" |
73 | - android:background="#ffffffff" |
74 | - > |
75 | - <LinearLayout |
76 | - android:id="@+id/LinearLayout01" |
77 | - android:orientation="vertical" |
78 | - android:layout_width="fill_parent" |
79 | - android:layout_height="fill_parent"> |
80 | - |
81 | - <!-- <TextView |
82 | - android:id="@+id/title" |
83 | - android:layout_width="fill_parent" |
84 | - android:layout_height="wrap_content" |
85 | - android:padding="10dip" |
86 | - android:textStyle="bold" />--> |
87 | - |
88 | - <TextView |
89 | - xmlns:android="http://schemas.android.com/apk/res/android" |
90 | - android:id="@+id/content" |
91 | - android:layout_width="wrap_content" |
92 | - android:layout_height="wrap_content" |
93 | - android:singleLine="false" |
94 | - android:text="@string/strWait" |
95 | - android:padding="10dip" |
96 | - android:textColor="#ffb8bcb8" /> |
97 | - </LinearLayout> |
98 | -</ScrollView> |
99 | + > |
100 | + |
101 | + <include android:id="@+id/actionbar" layout="@layout/actionbar" /> |
102 | + |
103 | + <FrameLayout |
104 | + android:id="@+id/container" |
105 | + android:layout_width="fill_parent" |
106 | + android:layout_height="fill_parent" |
107 | + > |
108 | + |
109 | + <TextView |
110 | + android:background="@android:color/transparent" |
111 | + android:id="@+id/viewContent" |
112 | + android:layout_width="fill_parent" |
113 | + android:layout_height="fill_parent" |
114 | + android:gravity="top" |
115 | + android:scrollbars="vertical" |
116 | + android:fadingEdge="vertical" |
117 | + android:linksClickable="true" |
118 | + android:inputType="none" |
119 | + android:padding="10dip" |
120 | + android:textSize="20dip" |
121 | + /> |
122 | + |
123 | + <EditText |
124 | + android:background="@android:color/transparent" |
125 | + android:id="@+id/editContent" |
126 | + android:layout_width="fill_parent" |
127 | + android:layout_height="fill_parent" |
128 | + android:gravity="top" |
129 | + android:scrollbars="vertical" |
130 | + android:fadingEdge="vertical" |
131 | + android:linksClickable="false" |
132 | + android:inputType="textLongMessage|textMultiLine|textAutoComplete" |
133 | + android:padding="10dip" |
134 | + android:textSize="20dip" |
135 | + android:visibility="gone" |
136 | + /> |
137 | + |
138 | + </FrameLayout> |
139 | </LinearLayout> |
140 | \ No newline at end of file |
141 | |
142 | === modified file 'res/menu/main.xml' |
143 | --- res/menu/main.xml 2010-06-12 05:53:50 +0000 |
144 | +++ res/menu/main.xml 2010-10-10 07:54:44 +0000 |
145 | @@ -29,6 +29,11 @@ |
146 | android:id="@+id/menuAbout" |
147 | /> |
148 | |
149 | + <item |
150 | + android:icon="@android:drawable/ic_menu_revert" |
151 | + android:title="@string/menuRevert" |
152 | + android:id="@+id/menuRevert" |
153 | + /> |
154 | |
155 | <item |
156 | android:icon="@android:drawable/ic_menu_preferences" |
157 | |
158 | === added file 'res/menu/view_note.xml' |
159 | --- res/menu/view_note.xml 1970-01-01 00:00:00 +0000 |
160 | +++ res/menu/view_note.xml 2010-10-10 07:54:44 +0000 |
161 | @@ -0,0 +1,48 @@ |
162 | +<?xml version="1.0" encoding="utf-8"?> |
163 | +<!-- |
164 | + Tomdroid |
165 | + Tomboy on Android |
166 | + http://www.launchpad.net/tomdroid |
167 | + |
168 | + Copyright 2010 Rodja Trappe <mail@rodja.net> |
169 | + |
170 | + This file is part of Tomdroid. |
171 | + |
172 | + Tomdroid is free software: you can redistribute it and/or modify |
173 | + it under the terms of the GNU General Public License as published by |
174 | + the Free Software Foundation, either version 3 of the License, or |
175 | + (at your option) any later version. |
176 | + |
177 | + Tomdroid is distributed in the hope that it will be useful, |
178 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
179 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
180 | + GNU General Public License for more details. |
181 | + |
182 | + You should have received a copy of the GNU General Public License |
183 | + along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
184 | +--> |
185 | +<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
186 | + |
187 | + <item |
188 | + android:icon="@android:drawable/ic_menu_edit" |
189 | + android:title="@string/menuEdit" |
190 | + android:id="@+id/menuEdit" |
191 | + /> |
192 | + |
193 | + <item |
194 | + android:icon="@android:drawable/ic_menu_view" |
195 | + android:title="@string/menuView" |
196 | + android:id="@+id/menuView" |
197 | + /> |
198 | + |
199 | + <item |
200 | + android:icon="@android:drawable/ic_menu_delete" |
201 | + android:title="@string/menuDelete" |
202 | + android:id="@+id/menuDelete" |
203 | + /> |
204 | + |
205 | + <item |
206 | + android:icon="@android:drawable/ic_menu_preferences" |
207 | + android:title="@string/menuPrefs" |
208 | + android:id="@+id/menuPrefs"/> |
209 | +</menu> |
210 | |
211 | === modified file 'res/values/strings.xml' |
212 | --- res/values/strings.xml 2010-10-09 19:58:11 +0000 |
213 | +++ res/values/strings.xml 2010-10-10 07:54:44 +0000 |
214 | @@ -27,7 +27,6 @@ |
215 | <string name="author">Olivier Bilodeau</string> |
216 | |
217 | <!-- main.xml --> |
218 | - <string name="strListEmptyWaiting">Please wait while the notes load..</string> |
219 | <string name="strListEmptyNoNotes"> |
220 | There are no notes in Tomdroid\'s database. |
221 | </string> |
222 | @@ -35,6 +34,10 @@ |
223 | <string name="menuSync">Sync</string> |
224 | <string name="menuPrefs">Settings</string> |
225 | <string name="menuAbout">About</string> |
226 | + <string name="menuDelete">Delete</string> |
227 | + <string name="menuEdit">Edit</string> |
228 | + <string name="menuView">View</string> |
229 | + <string name="menuRevert">Reset DB</string> |
230 | <string name="strWelcome"> |
231 | Welcome to Tomdroid. |
232 | \n\nPlease note that this is beta quality software and that it contains known problems, |
233 | @@ -58,16 +61,13 @@ |
234 | <string name="btnCancel">Cancel</string> |
235 | |
236 | <!-- note-view.xml --> |
237 | - <string name="strWait">Please wait while note loads...</string> |
238 | - |
239 | <string name="prefSync">Synchronization</string> |
240 | |
241 | - <string name="prefSyncService">Service</string> |
242 | - <string name="prefSyncServer">Server</string> |
243 | + <string name="prefSyncMethod">Sync Method</string> |
244 | + <string name="prefSyncServerUri">Sync Server Address</string> |
245 | <string name="prefAuthenticate">Authenticate</string> |
246 | |
247 | <string name="prefSyncConnectionFailed">The connection to the server has failed, please check that the address you entered is correct.</string> |
248 | <string name="prefServerEmpty">The server address changed but the new value is empty</string> |
249 | |
250 | - |
251 | </resources> |
252 | |
253 | === added file 'res/values/styles.xml' |
254 | --- res/values/styles.xml 1970-01-01 00:00:00 +0000 |
255 | +++ res/values/styles.xml 2010-10-10 07:54:44 +0000 |
256 | @@ -0,0 +1,14 @@ |
257 | +<?xml version="1.0" encoding="utf-8"?> |
258 | +<resources> |
259 | + |
260 | +<style name="NoteListTitle" parent="@android:style/TextAppearance"> |
261 | + <item name="android:textSize">22sp</item> |
262 | + <item name="android:textColor">#000000</item> |
263 | + <item name="android:padding">10dip</item> |
264 | +</style> |
265 | + |
266 | +<style name="NoteListTileUnsynced" parent="@style/NoteListTitle"> |
267 | + <item name="android:textStyle">bold</item> |
268 | +</style> |
269 | + |
270 | +</resources> |
271 | \ No newline at end of file |
272 | |
273 | === modified file 'res/xml/preferences.xml' |
274 | --- res/xml/preferences.xml 2010-09-26 11:34:42 +0000 |
275 | +++ res/xml/preferences.xml 2010-10-10 07:54:44 +0000 |
276 | @@ -3,13 +3,12 @@ |
277 | |
278 | <PreferenceCategory android:title="@string/prefSync"> |
279 | |
280 | - <ListPreference android:title="@string/prefSyncService" |
281 | - android:dialogTitle="Choose the sync service to use" |
282 | - android:key="sync_service" |
283 | - android:defaultValue="tomboy-web"/> |
284 | + <ListPreference android:title="@string/prefSyncMethod" |
285 | + android:dialogTitle="Choose the sync method to use" |
286 | + android:key="sync_method"/> |
287 | |
288 | - <EditTextPreference android:key="sync_server" |
289 | - android:title="@string/prefSyncServer" |
290 | + <EditTextPreference android:key="sync_server_uri" |
291 | + android:title="@string/prefSyncServerUri" |
292 | android:positiveButtonText="@string/prefAuthenticate" |
293 | android:shouldDisableView="true"/> |
294 | |
295 | |
296 | === modified file 'src/org/tomdroid/Note.java' |
297 | --- src/org/tomdroid/Note.java 2010-10-03 12:16:28 +0000 |
298 | +++ src/org/tomdroid/Note.java 2010-10-10 07:54:44 +0000 |
299 | @@ -1,25 +1,26 @@ |
300 | /* |
301 | - * Tomdroid |
302 | - * Tomboy on Android |
303 | - * http://www.launchpad.net/tomdroid |
304 | + * Tomdroid |
305 | + * Tomboy on Android |
306 | + * http://www.launchpad.net/tomdroid |
307 | * |
308 | - * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org> |
309 | + * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org> |
310 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
311 | - * |
312 | - * This file is part of Tomdroid. |
313 | - * |
314 | - * Tomdroid is free software: you can redistribute it and/or modify |
315 | - * it under the terms of the GNU General Public License as published by |
316 | - * the Free Software Foundation, either version 3 of the License, or |
317 | - * (at your option) any later version. |
318 | + * Copyright 2010 Rodja Trappe <mail@rodja.net> |
319 | + * |
320 | + * This file is part of Tomdroid. |
321 | + * |
322 | + * Tomdroid is free software: you can redistribute it and/or modify |
323 | + * it under the terms of the GNU General Public License as published by |
324 | + * the Free Software Foundation, either version 3 of the License, or |
325 | + * (at your option) any later version. |
326 | * |
327 | * Tomdroid is distributed in the hope that it will be useful, |
328 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
329 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
330 | - * GNU General Public License for more details. |
331 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
332 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
333 | + * GNU General Public License for more details. |
334 | * |
335 | - * You should have received a copy of the GNU General Public License |
336 | - * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
337 | + * You should have received a copy of the GNU General Public License |
338 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
339 | */ |
340 | package org.tomdroid; |
341 | |
342 | @@ -27,32 +28,35 @@ |
343 | import java.util.regex.Matcher; |
344 | import java.util.regex.Pattern; |
345 | |
346 | +import org.json.JSONException; |
347 | import org.json.JSONObject; |
348 | import org.json.JSONArray; |
349 | import org.tomdroid.util.NoteContentBuilder; |
350 | import org.tomdroid.util.XmlUtils; |
351 | |
352 | +import android.database.Cursor; |
353 | import android.os.Handler; |
354 | import android.text.SpannableStringBuilder; |
355 | import android.text.format.Time; |
356 | import android.util.Log; |
357 | import android.util.TimeFormatException; |
358 | |
359 | -public class Note { |
360 | +public class Note implements Cloneable { |
361 | |
362 | // Static references to fields (used in Bundles, ContentResolvers, etc.) |
363 | public static final String ID = "_id"; |
364 | public static final String GUID = "guid"; |
365 | public static final String TITLE = "title"; |
366 | public static final String MODIFIED_DATE = "modified_date"; |
367 | + public static final String IS_SYNCED = "is_synced"; |
368 | public static final String URL = "url"; |
369 | public static final String FILE = "file"; |
370 | public static final String TAGS = "tags"; |
371 | public static final String NOTE_CONTENT = "content"; |
372 | - |
373 | + |
374 | // Logging info |
375 | - private static final String TAG = "Note"; |
376 | - |
377 | + private static final String TAG = "Note"; |
378 | + |
379 | // Notes constants |
380 | // TODO this is a weird yellow that was usable for the android emulator, I must confirm this for real usage |
381 | public static final int NOTE_HIGHLIGHT_COLOR = 0xFFFFFF77; |
382 | @@ -71,6 +75,8 @@ |
383 | private Time lastChangeDate; |
384 | private int dbId; |
385 | private UUID guid; |
386 | + private long lastSyncRevision; |
387 | + private boolean isSynced = true; |
388 | |
389 | // Date converter pattern (remove extra sub milliseconds from datetime string) |
390 | // ex: will strip 3020 in 2010-01-23T12:07:38.7743020-05:00 |
391 | @@ -80,15 +86,20 @@ |
392 | "([-\\+]\\d{2}:\\d{2})"); // matches timezone (-xx:xx or +xx:xx) |
393 | |
394 | public Note() { |
395 | - tags = new String(); |
396 | + setTitle("no tilte"); |
397 | + setGuid(UUID.randomUUID()); |
398 | + lastSyncRevision = 0; |
399 | + changeXmlContent("no content"); |
400 | + setTags(""); |
401 | } |
402 | - |
403 | + |
404 | public Note(JSONObject json) { |
405 | - |
406 | + |
407 | // These methods return an empty string if the key is not found |
408 | setTitle(XmlUtils.unescape(json.optString("title"))); |
409 | setGuid(json.optString("guid")); |
410 | setLastChangeDate(json.optString("last-change-date")); |
411 | + lastSyncRevision = json.optInt("last-sync-revision", -1); |
412 | setXmlContent(json.optString("note-content")); |
413 | JSONArray jtags = json.optJSONArray("tags"); |
414 | String tag; |
415 | @@ -100,11 +111,36 @@ |
416 | } |
417 | } |
418 | } |
419 | + |
420 | + public Note(Cursor cursor) { |
421 | + String content = cursor.getString(cursor.getColumnIndexOrThrow(Note.NOTE_CONTENT)); |
422 | + setXmlContent(content); |
423 | + |
424 | + String title = cursor.getString(cursor.getColumnIndexOrThrow(Note.TITLE)); |
425 | + setTitle(title); |
426 | + |
427 | + String lastChangeDate = cursor.getString(cursor.getColumnIndexOrThrow(Note.MODIFIED_DATE)); |
428 | + setLastChangeDate(lastChangeDate); |
429 | + |
430 | + setGuid(cursor.getString(cursor.getColumnIndexOrThrow(Note.GUID))); |
431 | + |
432 | + setTags(cursor.getString(cursor.getColumnIndexOrThrow(Note.TAGS))); |
433 | + |
434 | + int synced = cursor.getInt(cursor.getColumnIndexOrThrow(Note.IS_SYNCED)); |
435 | + isSynced(synced == 1 ? true : false); |
436 | + } |
437 | + |
438 | + /** |
439 | + * Weather the note is in sync with the server or not. Default is 'true'. |
440 | + */ |
441 | + public boolean isSynced() { |
442 | + return isSynced; |
443 | + } |
444 | + |
445 | + public void isSynced(boolean flag) { |
446 | + isSynced = flag; |
447 | + } |
448 | |
449 | - public String getTags() { |
450 | - return tags; |
451 | - } |
452 | - |
453 | public String getUrl() { |
454 | return url; |
455 | } |
456 | @@ -129,29 +165,47 @@ |
457 | this.title = title; |
458 | } |
459 | |
460 | + public String getTags() { |
461 | + return tags; |
462 | + } |
463 | + |
464 | + public void setTags(String tags) { |
465 | + this.tags= tags; |
466 | + } |
467 | + |
468 | public Time getLastChangeDate() { |
469 | return lastChangeDate; |
470 | } |
471 | |
472 | + public long getLastSyncRevision() { |
473 | + return lastSyncRevision; |
474 | + } |
475 | + |
476 | + public void setLastSyncRevision(long revision) { |
477 | + lastSyncRevision = revision; |
478 | + } |
479 | + |
480 | public void setLastChangeDate(Time lastChangeDate) { |
481 | this.lastChangeDate = lastChangeDate; |
482 | + lastChangeDate.switchTimezone(Time.TIMEZONE_UTC); |
483 | } |
484 | - |
485 | + |
486 | public void setLastChangeDate(String lastChangeDateStr) throws TimeFormatException { |
487 | - |
488 | + |
489 | // regexp out the sub-milliseconds from tomboy's datetime format |
490 | // Normal RFC 3339 format: 2008-10-13T16:00:00.000-07:00 |
491 | // Tomboy's (C# library) format: 2010-01-23T12:07:38.7743020-05:00 |
492 | Matcher m = dateCleaner.matcher(lastChangeDateStr); |
493 | if (m.find()) { |
494 | Log.d(TAG, "I had to clean out extra sub-milliseconds from the date"); |
495 | - lastChangeDateStr = m.group(1)+m.group(2); |
496 | - Log.v(TAG, "new date: "+lastChangeDateStr); |
497 | + lastChangeDateStr = m.group(1) + m.group(2); |
498 | + Log.v(TAG, "new date: " + lastChangeDateStr); |
499 | } |
500 | - |
501 | + |
502 | lastChangeDate = new Time(); |
503 | lastChangeDate.parse3339(lastChangeDateStr); |
504 | - } |
505 | + lastChangeDate.switchTimezone(Time.TIMEZONE_UTC); |
506 | + } |
507 | |
508 | public int getDbId() { |
509 | return dbId; |
510 | @@ -160,35 +214,109 @@ |
511 | public void setDbId(int id) { |
512 | this.dbId = id; |
513 | } |
514 | - |
515 | + |
516 | public UUID getGuid() { |
517 | return guid; |
518 | } |
519 | - |
520 | + |
521 | public void setGuid(String guid) { |
522 | this.guid = UUID.fromString(guid); |
523 | } |
524 | |
525 | + public void setGuid(UUID guid) { |
526 | + this.guid = guid; |
527 | + } |
528 | + |
529 | // TODO: should this handler passed around evolve into an observer pattern? |
530 | public SpannableStringBuilder getNoteContent(Handler handler) { |
531 | - |
532 | + |
533 | // TODO not sure this is the right place to do this |
534 | - noteContent = new NoteContentBuilder().setCaller(handler).setInputSource(xmlContent).build(); |
535 | + noteContent = new NoteContentBuilder().setCaller(handler).setInputSource(getXmlContent()) |
536 | + .build(); |
537 | return noteContent; |
538 | } |
539 | - |
540 | + |
541 | public String getXmlContent() { |
542 | return xmlContent; |
543 | } |
544 | - |
545 | + |
546 | + /** |
547 | + * Change the content while leaving last-change-date untouched. |
548 | + */ |
549 | public void setXmlContent(String xmlContent) { |
550 | this.xmlContent = xmlContent; |
551 | } |
552 | |
553 | + /** |
554 | + * Updates the content and sets last-change-date to now and flags the note as "not in sync with server". |
555 | + */ |
556 | + public void changeXmlContent(String xmlContent) { |
557 | + this.xmlContent = xmlContent; |
558 | + Time time = new Time(); |
559 | + time.setToNow(); |
560 | + setLastChangeDate(time); |
561 | + isSynced(false); |
562 | + } |
563 | + |
564 | + public JSONObject toJsonWithoutContent() throws JSONException { |
565 | + JSONObject json = toJson(); |
566 | + json.remove("note-content"); |
567 | + return json; |
568 | + } |
569 | + |
570 | + @Override |
571 | + public boolean equals(Object obj) { |
572 | + if (!(obj instanceof Note)) |
573 | + return false; |
574 | + |
575 | + Note note = (Note) obj; |
576 | + if (note.getGuid().equals(getGuid()) |
577 | + && note.getLastChangeDate().equals(getLastChangeDate()) |
578 | + && note.getTitle().equals(getTitle())) |
579 | + return true; |
580 | + |
581 | + return false; |
582 | + } |
583 | + |
584 | + public JSONObject toJson() throws JSONException { |
585 | + return new JSONObject("{'guid':'" + getGuid() + "', 'title':'" + getTitle() |
586 | + + "', 'note-content':'" + getJsonPreparedXmlContent() + "', 'last-change-date':'" |
587 | + + getLastChangeDate().format3339(false) + "', 'note-content-version':0.1}"); |
588 | + } |
589 | + |
590 | + private String getJsonPreparedXmlContent() { |
591 | + return getXmlContent().replace("\n", "\\n") |
592 | + .replace("\"", "\\\"") |
593 | + .replace("'", "\\'") |
594 | + .replace("\b", "\\b") |
595 | + .replace("\f", "\\f") |
596 | + .replace("\n", "\\n") |
597 | + .replace("\r", "\\r") |
598 | + .replace("\t", "\\t"); |
599 | + } |
600 | + |
601 | @Override |
602 | public String toString() { |
603 | |
604 | - return new String("Note: "+ getTitle() + " (" + getLastChangeDate() + ")"); |
605 | - } |
606 | - |
607 | + return new String("Note: " + getTitle() + " (" + getLastChangeDate() + ")"); |
608 | + } |
609 | + |
610 | + public Note clone() { |
611 | + |
612 | + Note clone = new Note(); |
613 | + |
614 | + clone.noteContent = noteContent; |
615 | + clone.xmlContent = xmlContent; |
616 | + clone.url = url; |
617 | + clone.fileName = fileName; |
618 | + clone.title = title; |
619 | + clone.lastChangeDate = lastChangeDate; |
620 | + clone.lastSyncRevision = lastSyncRevision; |
621 | + clone.dbId = dbId; |
622 | + clone.guid = guid; |
623 | + clone.tags = tags; |
624 | + |
625 | + return clone; |
626 | + |
627 | + } |
628 | } |
629 | |
630 | === modified file 'src/org/tomdroid/NoteManager.java' |
631 | --- src/org/tomdroid/NoteManager.java 2010-10-09 19:48:21 +0000 |
632 | +++ src/org/tomdroid/NoteManager.java 2010-10-10 07:54:44 +0000 |
633 | @@ -5,6 +5,7 @@ |
634 | * |
635 | * Copyright 2009, 2010 Benoit Garret <benoit.garret_launchpad@gadz.org> |
636 | * Copyright 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org> |
637 | + * Copyright 2010 Rodja Trappe <mail@rodja.net> |
638 | * |
639 | * This file is part of Tomdroid. |
640 | * |
641 | @@ -14,120 +15,135 @@ |
642 | * (at your option) any later version. |
643 | * |
644 | * Tomdroid is distributed in the hope that it will be useful, |
645 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
646 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
647 | - * GNU General Public License for more details. |
648 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
649 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
650 | + * GNU General Public License for more details. |
651 | * |
652 | - * You should have received a copy of the GNU General Public License |
653 | - * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
654 | + * You should have received a copy of the GNU General Public License |
655 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
656 | */ |
657 | package org.tomdroid; |
658 | |
659 | +import java.util.UUID; |
660 | + |
661 | import org.tomdroid.ui.Tomdroid; |
662 | import org.tomdroid.util.NoteListCursorAdapter; |
663 | |
664 | import android.app.Activity; |
665 | import android.content.ContentResolver; |
666 | import android.content.ContentValues; |
667 | +import android.content.Context; |
668 | import android.database.Cursor; |
669 | import android.net.Uri; |
670 | import android.util.Log; |
671 | import android.widget.ListAdapter; |
672 | |
673 | public class NoteManager { |
674 | - |
675 | - public static final String[] FULL_PROJECTION = { Note.ID, Note.TITLE, Note.FILE, Note.NOTE_CONTENT, Note.MODIFIED_DATE }; |
676 | - public static final String[] LIST_PROJECTION = { Note.ID, Note.TITLE, Note.MODIFIED_DATE }; |
677 | - public static final String[] TITLE_PROJECTION = { Note.TITLE }; |
678 | - public static final String[] GUID_PROJECTION = { Note.ID, Note.GUID }; |
679 | - public static final String[] ID_PROJECTION = { Note.ID }; |
680 | - public static final String[] EMPTY_PROJECTION = {}; |
681 | - |
682 | + public static final String[] FULL_PROJECTION = { Note.ID, Note.TITLE, Note.FILE, |
683 | + Note.NOTE_CONTENT, Note.MODIFIED_DATE, Note.GUID, Note.IS_SYNCED, Note.TAGS }; |
684 | + public static final String[] LIST_PROJECTION = { Note.ID, Note.TITLE, Note.MODIFIED_DATE, Note.IS_SYNCED }; |
685 | + public static final String[] TITLE_PROJECTION = { Note.TITLE }; |
686 | + public static final String[] GUID_PROJECTION = { Note.ID, Note.GUID }; |
687 | + public static final String[] ID_PROJECTION = { Note.ID }; |
688 | + public static final String[] EMPTY_PROJECTION = {}; |
689 | + |
690 | // static properties |
691 | - private static final String TAG = "NoteManager"; |
692 | - |
693 | - // gets a note from the content provider |
694 | - public static Note getNote(Activity activity, Uri uri) { |
695 | - |
696 | + private static final String TAG = "NoteManager"; |
697 | + |
698 | + public static Note getNote(Context context, Uri uri) { |
699 | + Cursor cursor = context.getContentResolver().query(uri, FULL_PROJECTION, null, null, null); |
700 | + return getNote(cursor); |
701 | + } |
702 | + |
703 | + public static Note getNote(Context context, UUID guid) { |
704 | + String[] whereArgs = { guid.toString() }; |
705 | + Cursor cursor = context.getContentResolver().query(Tomdroid.CONTENT_URI, FULL_PROJECTION, |
706 | + Note.GUID + "=?", whereArgs, null); |
707 | + |
708 | + return getNote(cursor); |
709 | + } |
710 | + |
711 | + /** |
712 | + * Creates a Note which is described at the cursor position. |
713 | + */ |
714 | + public static Note getNote(Cursor cursor) { |
715 | Note note = null; |
716 | - |
717 | - // can we find a matching note? |
718 | - Cursor cursor = activity.managedQuery(uri, FULL_PROJECTION, null, null, null); |
719 | - // cursor must not be null and must return more than 0 entry |
720 | if (!(cursor == null || cursor.getCount() == 0)) { |
721 | - |
722 | + |
723 | // create the note from the cursor |
724 | cursor.moveToFirst(); |
725 | - String noteContent = cursor.getString(cursor.getColumnIndexOrThrow(Note.NOTE_CONTENT)); |
726 | - String noteTitle = cursor.getString(cursor.getColumnIndexOrThrow(Note.TITLE)); |
727 | - |
728 | - note = new Note(); |
729 | - note.setXmlContent(noteContent); |
730 | - note.setTitle(noteTitle); |
731 | + note = new Note(cursor); |
732 | } |
733 | - |
734 | + cursor.close(); |
735 | return note; |
736 | } |
737 | - |
738 | - // puts a note in the content provider |
739 | + |
740 | public static void putNote(Activity activity, Note note) { |
741 | - |
742 | + |
743 | // verify if the note is already in the content provider |
744 | - |
745 | + |
746 | // TODO make the query prettier (use querybuilder) |
747 | Uri notes = Tomdroid.CONTENT_URI; |
748 | String[] whereArgs = new String[1]; |
749 | whereArgs[0] = note.getGuid().toString(); |
750 | - |
751 | + |
752 | // The note identifier is the guid |
753 | ContentResolver cr = activity.getContentResolver(); |
754 | - Cursor managedCursor = cr.query(notes, |
755 | - EMPTY_PROJECTION, |
756 | - Note.GUID + "= ?", |
757 | - whereArgs, |
758 | - null); |
759 | + Cursor managedCursor = cr |
760 | + .query(notes, EMPTY_PROJECTION, Note.GUID + "= ?", whereArgs, null); |
761 | activity.startManagingCursor(managedCursor); |
762 | - |
763 | + |
764 | // Preparing the values to be either inserted or updated |
765 | // depending on the result of the previous query |
766 | ContentValues values = new ContentValues(); |
767 | values.put(Note.TITLE, note.getTitle()); |
768 | values.put(Note.FILE, note.getFileName()); |
769 | values.put(Note.GUID, note.getGuid().toString()); |
770 | - // Notice that we store the date in UTC because sqlite doesn't handle RFC3339 timezone information |
771 | + values.put(Note.IS_SYNCED, note.isSynced() ? 1 : 0); |
772 | + // Notice that we store the date in UTC because sqlite doesn't handle RFC3339 timezone |
773 | + // information |
774 | values.put(Note.MODIFIED_DATE, note.getLastChangeDate().format3339(false)); |
775 | values.put(Note.NOTE_CONTENT, note.getXmlContent()); |
776 | values.put(Note.TAGS, note.getTags()); |
777 | - |
778 | + |
779 | if (managedCursor.getCount() == 0) { |
780 | - |
781 | + |
782 | // This note is not in the database yet we need to insert it |
783 | - if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"A new note has been detected (not yet in db)"); |
784 | - |
785 | - Uri uri = cr.insert(Tomdroid.CONTENT_URI, values); |
786 | - |
787 | - if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"Note inserted in content provider. ID: "+uri+" TITLE:"+note.getTitle()+" GUID:"+note.getGuid()); |
788 | + if (Tomdroid.LOGGING_ENABLED) |
789 | + Log.v(TAG, "A new note has been detected (not yet in db)"); |
790 | + |
791 | + Uri uri = cr.insert(Tomdroid.CONTENT_URI, values); |
792 | + |
793 | + if (Tomdroid.LOGGING_ENABLED) |
794 | + Log.v(TAG, "Note inserted in content provider. ID: '" + uri + "', TITLE: '" |
795 | + + note.getTitle() + "', GUID: '" + note.getGuid() + "',URI: " + uri); |
796 | } else { |
797 | - |
798 | + |
799 | // Overwrite the previous note if it exists |
800 | - cr.update(Tomdroid.CONTENT_URI, values, Note.GUID+" = ?", whereArgs); |
801 | - |
802 | - if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"Note updated in content provider. TITLE:"+note.getTitle()+" GUID:"+note.getGuid()); |
803 | + cr.update(Tomdroid.CONTENT_URI, values, Note.GUID + " = ?", whereArgs); |
804 | + |
805 | + if (Tomdroid.LOGGING_ENABLED) |
806 | + Log.v(TAG, "Note updated in content provider. TITLE:" + note.getTitle() + " GUID:" |
807 | + + note.getGuid()); |
808 | } |
809 | } |
810 | - |
811 | - public static boolean deleteNote(Activity activity, int id) |
812 | - { |
813 | - Uri uri = Uri.parse(Tomdroid.CONTENT_URI+"/"+id); |
814 | - |
815 | + |
816 | + public static boolean deleteNote(Activity activity, int databaseId) { |
817 | + Uri uri = Uri.parse(Tomdroid.CONTENT_URI + "/" + databaseId); |
818 | + |
819 | ContentResolver cr = activity.getContentResolver(); |
820 | int result = cr.delete(uri, null, null); |
821 | - |
822 | - if(result > 0) |
823 | + |
824 | + if (result > 0) |
825 | return true; |
826 | else |
827 | return false; |
828 | } |
829 | + |
830 | + public static void deleteNote(Context context, UUID guid) { |
831 | + String[] whereArgs = { guid.toString() }; |
832 | + context.getContentResolver().delete(Tomdroid.CONTENT_URI, Note.GUID + "=?", whereArgs); |
833 | + } |
834 | |
835 | public static Cursor getAllNotes(Activity activity, Boolean includeNotebookTemplates) { |
836 | // get a cursor representing all notes from the NoteProvider |
837 | @@ -140,51 +156,51 @@ |
838 | orderBy = Note.MODIFIED_DATE + " DESC"; |
839 | return activity.managedQuery(notes, LIST_PROJECTION, where, null, orderBy); |
840 | } |
841 | - |
842 | |
843 | public static ListAdapter getListAdapter(Activity activity) { |
844 | |
845 | + // get a cursor representing all notes from the NoteProvider |
846 | Cursor notesCursor = getAllNotes(activity, false); |
847 | |
848 | // set up an adapter binding the TITLE field of the cursor to the list item |
849 | - String[] from = new String[] { Note.TITLE, Note.MODIFIED_DATE }; |
850 | + String[] from = new String[] { Note.TITLE, Note.IS_SYNCED, Note.MODIFIED_DATE }; |
851 | int[] to = new int[] { R.id.note_title, R.id.note_date }; |
852 | return new NoteListCursorAdapter(activity, R.layout.main_list_item, notesCursor, from, to); |
853 | } |
854 | |
855 | // gets the titles of the notes present in the db, used in ViewNote.buildLinkifyPattern() |
856 | public static Cursor getTitles(Activity activity) { |
857 | - |
858 | + |
859 | // get a cursor containing the notes titles |
860 | return activity.managedQuery(Tomdroid.CONTENT_URI, TITLE_PROJECTION, null, null, null); |
861 | } |
862 | - |
863 | - // gets the ids of the notes present in the db, used in SyncService.deleteNotes() |
864 | + |
865 | + // gets the ids of the notes present in the db |
866 | public static Cursor getGuids(Activity activity) { |
867 | - |
868 | // get a cursor containing the notes guids |
869 | return activity.managedQuery(Tomdroid.CONTENT_URI, GUID_PROJECTION, null, null, null); |
870 | } |
871 | - |
872 | + |
873 | public static int getNoteId(Activity activity, String title) { |
874 | - |
875 | + |
876 | int id = 0; |
877 | - |
878 | + |
879 | // get the notes ids |
880 | String[] whereArgs = { title }; |
881 | - Cursor cursor = activity.managedQuery(Tomdroid.CONTENT_URI, ID_PROJECTION, Note.TITLE+"=?", whereArgs, null); |
882 | - |
883 | - // cursor must not be null and must return more than 0 entry |
884 | + Cursor cursor = activity.managedQuery(Tomdroid.CONTENT_URI, ID_PROJECTION, Note.TITLE |
885 | + + "=?", whereArgs, null); |
886 | + |
887 | + // cursor must not be null and must return more than 0 entry |
888 | if (!(cursor == null || cursor.getCount() == 0)) { |
889 | - |
890 | + |
891 | cursor.moveToFirst(); |
892 | id = cursor.getInt(cursor.getColumnIndexOrThrow(Note.ID)); |
893 | - } |
894 | - else { |
895 | + } else { |
896 | // TODO send an error to the user |
897 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes"); |
898 | + if (Tomdroid.LOGGING_ENABLED) |
899 | + Log.d(TAG, "Cursor returned null or 0 notes"); |
900 | } |
901 | - |
902 | + |
903 | return id; |
904 | } |
905 | } |
906 | |
907 | === modified file 'src/org/tomdroid/NoteProvider.java' |
908 | --- src/org/tomdroid/NoteProvider.java 2010-09-17 20:37:42 +0000 |
909 | +++ src/org/tomdroid/NoteProvider.java 2010-10-10 07:54:44 +0000 |
910 | @@ -5,6 +5,8 @@ |
911 | * |
912 | * Copyright 2009 Olivier Bilodeau <olivier@bottomlesspit.org> |
913 | * Copyright 2009 Benoit Garret <benoit.garret_launchpad@gadz.org> |
914 | + * Copyright 2010 Rodja Trappe <mail@rodja.net> |
915 | + * |
916 | * |
917 | * This file is part of Tomdroid. |
918 | * |
919 | @@ -69,7 +71,7 @@ |
920 | // -- |
921 | private static final String DATABASE_NAME = "tomdroid-notes.db"; |
922 | private static final String DB_TABLE_NOTES = "notes"; |
923 | - private static final int DB_VERSION = 3; |
924 | + private static final int DB_VERSION = 6; |
925 | private static final String DEFAULT_SORT_ORDER = Note.MODIFIED_DATE + " DESC"; |
926 | |
927 | private static HashMap<String, String> notesProjectionMap; |
928 | @@ -101,6 +103,7 @@ |
929 | + Note.FILE + " TEXT," |
930 | + Note.NOTE_CONTENT + " TEXT," |
931 | + Note.MODIFIED_DATE + " STRING," |
932 | + + Note.IS_SYNCED + " INTEGER," |
933 | + Note.TAGS + " STRING" |
934 | + ");"); |
935 | } |
936 | @@ -160,7 +163,6 @@ |
937 | orderBy = sortOrder; |
938 | } |
939 | |
940 | - |
941 | // Get the database and run the query |
942 | SQLiteDatabase db = dbHelper.getReadableDatabase(); |
943 | Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy); |
944 | @@ -300,5 +302,6 @@ |
945 | notesProjectionMap.put(Note.NOTE_CONTENT, Note.NOTE_CONTENT); |
946 | notesProjectionMap.put(Note.TAGS, Note.TAGS); |
947 | notesProjectionMap.put(Note.MODIFIED_DATE, Note.MODIFIED_DATE); |
948 | + notesProjectionMap.put(Note.IS_SYNCED, Note.IS_SYNCED); |
949 | } |
950 | } |
951 | |
952 | === added file 'src/org/tomdroid/sync/LocalStorage.java' |
953 | --- src/org/tomdroid/sync/LocalStorage.java 1970-01-01 00:00:00 +0000 |
954 | +++ src/org/tomdroid/sync/LocalStorage.java 2010-10-10 07:54:44 +0000 |
955 | @@ -0,0 +1,156 @@ |
956 | +/* |
957 | + * Tomdroid |
958 | + * Tomboy on Android |
959 | + * http://www.launchpad.net/tomdroid |
960 | + * |
961 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
962 | + * |
963 | + * This file is part of Tomdroid. |
964 | + * |
965 | + * Tomdroid is free software: you can redistribute it and/or modify |
966 | + * it under the terms of the GNU General Public License as published by |
967 | + * the Free Software Foundation, either version 3 of the License, or |
968 | + * (at your option) any later version. |
969 | + * |
970 | + * Tomdroid is distributed in the hope that it will be useful, |
971 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
972 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
973 | + * GNU General Public License for more details. |
974 | + * |
975 | + * You should have received a copy of the GNU General Public License |
976 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
977 | + */ |
978 | +package org.tomdroid.sync; |
979 | + |
980 | +import java.util.ArrayList; |
981 | +import java.util.HashSet; |
982 | +import java.util.Set; |
983 | +import java.util.UUID; |
984 | + |
985 | +import org.tomdroid.Note; |
986 | +import org.tomdroid.NoteManager; |
987 | +import org.tomdroid.ui.Tomdroid; |
988 | +import org.tomdroid.util.Preferences; |
989 | + |
990 | +import android.app.Activity; |
991 | +import android.database.Cursor; |
992 | +import android.util.Log; |
993 | + |
994 | +/** |
995 | + * Wrapper around NoteManager to hide "Android content cursors" and enables note storage |
996 | + * manipulation via Tomboy id's, titles and the like. |
997 | + */ |
998 | +public class LocalStorage { |
999 | + |
1000 | + private static final String TAG = "LocalStorage"; |
1001 | + |
1002 | + // TODO This data base accessor should not need a reference to an Activity. Currently the |
1003 | + // NoteManager unfortunately uses managed queries (which is dispensable). |
1004 | + private Activity activity; |
1005 | + |
1006 | + public LocalStorage(Activity activity) { |
1007 | + this.activity = activity; |
1008 | + } |
1009 | + |
1010 | + /** |
1011 | + * Insert a note in the content provider. The identifier for the notes is the guid. |
1012 | + */ |
1013 | + public void insertNote(Note note) { |
1014 | + NoteManager.putNote(this.activity, note); |
1015 | + } |
1016 | + |
1017 | + /** |
1018 | + * merges content into the existing note. The identifier for the note is the guid. |
1019 | + */ |
1020 | + public void mergeNote(Note note) { |
1021 | + // TODO implement a better merge algorithm then "append" |
1022 | + Note storedNote = getNote(note.getGuid()); |
1023 | + if (storedNote != null && !storedNote.isSynced()){ |
1024 | + note.changeXmlContent(note.getXmlContent() + " --merged-- " + storedNote.getXmlContent()); |
1025 | + } |
1026 | + NoteManager.putNote(this.activity, note); |
1027 | + } |
1028 | + |
1029 | + public Set<String> getNoteGuids() { |
1030 | + Set<String> idList = new HashSet<String>(); |
1031 | + |
1032 | + Cursor idCursor = NoteManager.getGuids(this.activity); |
1033 | + |
1034 | + // cursor must not be null and must return more than 0 entry |
1035 | + if (!(idCursor == null || idCursor.getCount() == 0)) { |
1036 | + |
1037 | + String guid; |
1038 | + idCursor.moveToFirst(); |
1039 | + |
1040 | + do { |
1041 | + guid = idCursor.getString(idCursor.getColumnIndexOrThrow(Note.GUID)); |
1042 | + idList.add(guid); |
1043 | + |
1044 | + } while (idCursor.moveToNext()); |
1045 | + |
1046 | + } else { |
1047 | + |
1048 | + // TODO send an error to the user |
1049 | + if (Tomdroid.LOGGING_ENABLED) |
1050 | + Log.d(TAG, "Cursor returned null or 0 notes"); |
1051 | + } |
1052 | + |
1053 | + return idList; |
1054 | + } |
1055 | + |
1056 | + public void deleteNotes(Set<String> guids) { |
1057 | + |
1058 | + for (String guid : guids) { |
1059 | + NoteManager.deleteNote(activity, UUID.fromString(guid)); |
1060 | + } |
1061 | + } |
1062 | + |
1063 | + /** |
1064 | + * Empties the complete database. Used to get a fresh start. |
1065 | + */ |
1066 | + public void resetDatabase() { |
1067 | + activity.getContentResolver().delete(Tomdroid.CONTENT_URI, null, null); |
1068 | + Preferences.putLong(Preferences.Key.LATEST_SYNC_REVISION, 0); |
1069 | + } |
1070 | + |
1071 | + public long getLatestSyncVersion() { |
1072 | + return (Long) Preferences.getLong(Preferences.Key.LATEST_SYNC_REVISION); |
1073 | + } |
1074 | + |
1075 | + public Note getNote(UUID guid) { |
1076 | + return NoteManager.getNote(activity, guid); |
1077 | + } |
1078 | + |
1079 | + public ArrayList<Note> getNewAndUpdatedNotes() { |
1080 | + ArrayList<Note> notes = new ArrayList<Note>(); |
1081 | + |
1082 | + String[] whereArgs = { "0" }; |
1083 | + Cursor cursor = activity.getContentResolver().query(Tomdroid.CONTENT_URI, |
1084 | + NoteManager.FULL_PROJECTION, Note.IS_SYNCED + "=?", whereArgs, null); |
1085 | + |
1086 | + if (cursor == null) return notes; |
1087 | + if (cursor.getCount() == 0) { |
1088 | + cursor.close(); |
1089 | + return notes; |
1090 | + } |
1091 | + |
1092 | + cursor.moveToFirst(); |
1093 | + |
1094 | + do { |
1095 | + notes.add(new Note(cursor)); |
1096 | + } while (cursor.moveToNext()); |
1097 | + |
1098 | + cursor.close(); |
1099 | + |
1100 | + return notes; |
1101 | + } |
1102 | + |
1103 | + public void onSynced(Long syncRevisionOfServer) { |
1104 | + Preferences.putLong(Preferences.Key.LATEST_SYNC_REVISION, syncRevisionOfServer); |
1105 | + ArrayList<Note> syncedNotes = getNewAndUpdatedNotes(); |
1106 | + for (Note note : syncedNotes) { |
1107 | + note.isSynced(true); |
1108 | + NoteManager.putNote(activity, note); |
1109 | + } |
1110 | + } |
1111 | +} |
1112 | |
1113 | === modified file 'src/org/tomdroid/sync/SyncManager.java' |
1114 | --- src/org/tomdroid/sync/SyncManager.java 2010-09-26 19:57:31 +0000 |
1115 | +++ src/org/tomdroid/sync/SyncManager.java 2010-10-10 07:54:44 +0000 |
1116 | @@ -4,6 +4,7 @@ |
1117 | * http://www.launchpad.net/tomdroid |
1118 | * |
1119 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
1120 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
1121 | * |
1122 | * This file is part of Tomdroid. |
1123 | * |
1124 | @@ -26,33 +27,33 @@ |
1125 | import java.util.ArrayList; |
1126 | |
1127 | import org.tomdroid.sync.sd.SdCardSyncService; |
1128 | -import org.tomdroid.sync.web.SnowySyncService; |
1129 | +import org.tomdroid.sync.web.SnowySyncMethod; |
1130 | import org.tomdroid.util.Preferences; |
1131 | |
1132 | import android.app.Activity; |
1133 | import android.os.Handler; |
1134 | -import android.util.Log; |
1135 | |
1136 | public class SyncManager { |
1137 | |
1138 | + @SuppressWarnings("unused") |
1139 | private static final String TAG = "SyncManager"; |
1140 | |
1141 | - private ArrayList<SyncService> services = new ArrayList<SyncService>(); |
1142 | + private ArrayList<SyncMethod> syncMethods = new ArrayList<SyncMethod>(); |
1143 | |
1144 | public SyncManager() { |
1145 | - createServices(); |
1146 | + createSyncMethods(); |
1147 | } |
1148 | |
1149 | - public ArrayList<SyncService> getServices() { |
1150 | - return services; |
1151 | + public ArrayList<SyncMethod> getSyncMethods() { |
1152 | + return syncMethods; |
1153 | } |
1154 | |
1155 | - public SyncService getService(String name) { |
1156 | + public SyncMethod getSyncMethod(String name) { |
1157 | |
1158 | - for (int i = 0; i < services.size(); i++) { |
1159 | - SyncService service = services.get(i); |
1160 | - if (name.equals(service.getName())) |
1161 | - return service; |
1162 | + for (int i = 0; i < syncMethods.size(); i++) { |
1163 | + SyncMethod method = syncMethods.get(i); |
1164 | + if (name.equals(method.getName())) |
1165 | + return method; |
1166 | } |
1167 | |
1168 | return null; |
1169 | @@ -60,13 +61,14 @@ |
1170 | |
1171 | public void startSynchronization() { |
1172 | |
1173 | - SyncService service = getCurrentService(); |
1174 | - service.startSynchronization(); |
1175 | + SyncMethod method = getCurrentSyncMethod(); |
1176 | + method.startSynchronization(); |
1177 | } |
1178 | |
1179 | - public SyncService getCurrentService() { |
1180 | - String serviceName = Preferences.getString(Preferences.Key.SYNC_SERVICE); |
1181 | - return getService(serviceName); |
1182 | + public SyncMethod getCurrentSyncMethod() { |
1183 | + |
1184 | + String syncMethodName = Preferences.getString(Preferences.Key.SYNC_METHOD); |
1185 | + return getSyncMethod(syncMethodName); |
1186 | } |
1187 | |
1188 | private static SyncManager instance = null; |
1189 | @@ -83,21 +85,21 @@ |
1190 | |
1191 | public static void setActivity(Activity a) { |
1192 | activity = a; |
1193 | - getInstance().createServices(); |
1194 | + getInstance().createSyncMethods(); |
1195 | } |
1196 | |
1197 | public static void setHandler(Handler h) { |
1198 | handler = h; |
1199 | - getInstance().createServices(); |
1200 | + getInstance().createSyncMethods(); |
1201 | } |
1202 | |
1203 | - private void createServices() { |
1204 | - services.clear(); |
1205 | + private void createSyncMethods() { |
1206 | + syncMethods.clear(); |
1207 | |
1208 | - services.add(new SnowySyncService(activity, handler)); |
1209 | + syncMethods.add(new SnowySyncMethod(activity, handler)); |
1210 | |
1211 | try { |
1212 | - services.add(new SdCardSyncService(activity, handler)); |
1213 | + syncMethods.add(new SdCardSyncService(activity, handler)); |
1214 | } catch (FileNotFoundException e) { |
1215 | // TODO Auto-generated catch block |
1216 | e.printStackTrace(); |
1217 | |
1218 | === renamed file 'src/org/tomdroid/sync/SyncService.java' => 'src/org/tomdroid/sync/SyncMethod.java' |
1219 | --- src/org/tomdroid/sync/SyncService.java 2010-08-30 16:33:02 +0000 |
1220 | +++ src/org/tomdroid/sync/SyncMethod.java 2010-10-10 07:54:44 +0000 |
1221 | @@ -24,153 +24,105 @@ |
1222 | */ |
1223 | package org.tomdroid.sync; |
1224 | |
1225 | -import java.util.ArrayList; |
1226 | - |
1227 | import java.util.concurrent.ExecutorService; |
1228 | import java.util.concurrent.Executors; |
1229 | |
1230 | -import org.tomdroid.Note; |
1231 | -import org.tomdroid.NoteManager; |
1232 | -import org.tomdroid.ui.Tomdroid; |
1233 | - |
1234 | import android.app.Activity; |
1235 | -import android.database.Cursor; |
1236 | +import android.content.Context; |
1237 | import android.os.Handler; |
1238 | import android.os.Message; |
1239 | import android.util.Log; |
1240 | import android.widget.Toast; |
1241 | |
1242 | -public abstract class SyncService { |
1243 | - |
1244 | - private static final String TAG = "SyncService"; |
1245 | - |
1246 | - private Activity activity; |
1247 | - private final ExecutorService pool; |
1248 | - private final static int poolSize = 1; |
1249 | - |
1250 | - private Handler handler; |
1251 | - private int syncProgress = 100; |
1252 | - |
1253 | +public abstract class SyncMethod { |
1254 | + |
1255 | + private static final String TAG = "SyncMethod"; |
1256 | + |
1257 | + private final static int poolSize = 1; |
1258 | + |
1259 | // handler messages |
1260 | - public final static int PARSING_COMPLETE = 1; |
1261 | - public final static int PARSING_FAILED = 2; |
1262 | - public final static int PARSING_NO_NOTES = 3; |
1263 | - public final static int NO_INTERNET = 4; |
1264 | - public final static int SYNC_PROGRESS = 5; |
1265 | - |
1266 | - public SyncService(Activity activity, Handler handler) { |
1267 | - |
1268 | + public final static int PARSING_COMPLETE = 1; |
1269 | + public final static int PARSING_FAILED = 2; |
1270 | + public final static int PARSING_NO_NOTES = 3; |
1271 | + public final static int NO_INTERNET = 4; |
1272 | + public final static int SYNC_PROGRESS = 5; |
1273 | + |
1274 | + private Activity activity; |
1275 | + private Handler handler; |
1276 | + |
1277 | + private ExecutorService pool; |
1278 | + |
1279 | + private int syncProgress = 100; |
1280 | + |
1281 | + private LocalStorage localStorage; |
1282 | + |
1283 | + public SyncMethod(Activity activity, Handler handler) { |
1284 | + |
1285 | this.activity = activity; |
1286 | this.handler = handler; |
1287 | pool = Executors.newFixedThreadPool(poolSize); |
1288 | - } |
1289 | + |
1290 | + localStorage = new LocalStorage(activity); |
1291 | + } |
1292 | + |
1293 | + protected LocalStorage getLocalStorage(){ |
1294 | + return localStorage; |
1295 | + } |
1296 | + |
1297 | |
1298 | public void startSynchronization() { |
1299 | - |
1300 | - if (syncProgress != 100){ |
1301 | - Toast.makeText(activity, "Sync already in prgress", Toast.LENGTH_SHORT).show(); |
1302 | + if (syncProgress != 100) { |
1303 | + Toast.makeText((Context) activity, "Sync already in prgress", Toast.LENGTH_SHORT).show(); |
1304 | return; |
1305 | } |
1306 | - |
1307 | sync(); |
1308 | } |
1309 | - |
1310 | + |
1311 | protected abstract void sync(); |
1312 | + |
1313 | public abstract boolean needsServer(); |
1314 | + |
1315 | public abstract boolean needsAuth(); |
1316 | - |
1317 | + |
1318 | /** |
1319 | * @return An unique identifier, not visible to the user. |
1320 | */ |
1321 | - |
1322 | public abstract String getName(); |
1323 | - |
1324 | + |
1325 | /** |
1326 | - * @return An human readable name, used in the preferences to distinguish the different sync services. |
1327 | + * @return An human readable name, used in the preferences to distinguish the different sync |
1328 | + * methods. |
1329 | */ |
1330 | - |
1331 | public abstract String getDescription(); |
1332 | - |
1333 | + |
1334 | /** |
1335 | - * Execute code in a separate thread. |
1336 | - * Use this for blocking and/or cpu intensive operations and thus avoid blocking the UI. |
1337 | + * Execute code in a separate thread. Use this for blocking and/or cpu intensive operations and |
1338 | + * thus avoid blocking the UI. |
1339 | * |
1340 | - * @param r The Runner subclass to execute |
1341 | + * @param r |
1342 | + * The Runner subclass to execute |
1343 | */ |
1344 | - |
1345 | protected void execInThread(Runnable r) { |
1346 | - |
1347 | + |
1348 | pool.execute(r); |
1349 | } |
1350 | - |
1351 | - /** |
1352 | - * Insert a note in the content provider. The identifier for the notes is the guid. |
1353 | - * |
1354 | - * @param note The note to insert. |
1355 | - */ |
1356 | - |
1357 | - protected void insertNote(Note note, boolean syncFinished) { |
1358 | - |
1359 | - NoteManager.putNote(this.activity, note); |
1360 | - |
1361 | - // if last note warn in UI that we are done |
1362 | - if (syncFinished) { |
1363 | - handler.sendEmptyMessage(PARSING_COMPLETE); |
1364 | - } |
1365 | - } |
1366 | - |
1367 | - /** |
1368 | - * Delete notes in the content provider. The guids passed identify the notes existing |
1369 | - * on the remote end (ie. that shouldn't be deleted). |
1370 | - * |
1371 | - * @param remoteGuids The notes NOT to delete. |
1372 | - */ |
1373 | - |
1374 | - protected void deleteNotes(ArrayList<String> remoteGuids) { |
1375 | - |
1376 | - Cursor localGuids = NoteManager.getGuids(this.activity); |
1377 | - |
1378 | - // cursor must not be null and must return more than 0 entry |
1379 | - if (!(localGuids == null || localGuids.getCount() == 0)) { |
1380 | - |
1381 | - String localGuid; |
1382 | - |
1383 | - localGuids.moveToFirst(); |
1384 | - |
1385 | - do { |
1386 | - localGuid = localGuids.getString(localGuids.getColumnIndexOrThrow(Note.GUID)); |
1387 | - |
1388 | - if(!remoteGuids.contains(localGuid)) { |
1389 | - int id = localGuids.getInt(localGuids.getColumnIndexOrThrow(Note.ID)); |
1390 | - NoteManager.deleteNote(this.activity, id); |
1391 | - } |
1392 | - |
1393 | - } while (localGuids.moveToNext()); |
1394 | - |
1395 | - } else { |
1396 | - |
1397 | - // TODO send an error to the user |
1398 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes"); |
1399 | - } |
1400 | - } |
1401 | - |
1402 | + |
1403 | /** |
1404 | * Send a message to the main UI. |
1405 | * |
1406 | - * @param message The message id to send, the PARSING_* or NO_INTERNET attributes can be used. |
1407 | + * @param message |
1408 | + * The message id to send, the PARSING_* or NO_INTERNET attributes can be used. |
1409 | */ |
1410 | - |
1411 | protected void sendMessage(int message) { |
1412 | - |
1413 | + |
1414 | handler.sendEmptyMessage(message); |
1415 | } |
1416 | - |
1417 | + |
1418 | /** |
1419 | * Update the synchronization progress |
1420 | * |
1421 | - * @param progress |
1422 | + * @param progress |
1423 | */ |
1424 | - |
1425 | protected void setSyncProgress(int progress) { |
1426 | synchronized (TAG) { |
1427 | Log.v(TAG, "sync progress: " + progress); |
1428 | @@ -181,10 +133,18 @@ |
1429 | |
1430 | handler.sendMessage(progressMessage); |
1431 | syncProgress = progress; |
1432 | + |
1433 | + if (progress == 100){ |
1434 | + onSyncCompleted(); |
1435 | + } |
1436 | } |
1437 | } |
1438 | + |
1439 | + protected void onSyncCompleted() { |
1440 | + handler.sendEmptyMessage(PARSING_COMPLETE); |
1441 | + } |
1442 | |
1443 | - protected int getSyncProgress(){ |
1444 | + protected int getSyncProgress() { |
1445 | synchronized (TAG) { |
1446 | return syncProgress; |
1447 | } |
1448 | |
1449 | === modified file 'src/org/tomdroid/sync/sd/SdCardSyncService.java' |
1450 | --- src/org/tomdroid/sync/sd/SdCardSyncService.java 2010-08-30 16:33:02 +0000 |
1451 | +++ src/org/tomdroid/sync/sd/SdCardSyncService.java 2010-10-10 07:54:44 +0000 |
1452 | @@ -40,7 +40,7 @@ |
1453 | import javax.xml.parsers.SAXParserFactory; |
1454 | |
1455 | import org.tomdroid.Note; |
1456 | -import org.tomdroid.sync.SyncService; |
1457 | +import org.tomdroid.sync.SyncMethod; |
1458 | import org.tomdroid.ui.Tomdroid; |
1459 | import org.xml.sax.InputSource; |
1460 | import org.xml.sax.SAXException; |
1461 | @@ -51,26 +51,31 @@ |
1462 | import android.util.Log; |
1463 | import android.util.TimeFormatException; |
1464 | |
1465 | -public class SdCardSyncService extends SyncService { |
1466 | - |
1467 | - private File path; |
1468 | - private int numberOfFilesToSync = 0; |
1469 | - |
1470 | +public class SdCardSyncService extends SyncMethod { |
1471 | + |
1472 | + private File path; |
1473 | + private int numberOfFilesToSync = 0; |
1474 | + private int filesSynced = 0; |
1475 | + |
1476 | // regexp for <note-content..>...</note-content> |
1477 | - private static Pattern note_content = Pattern.compile("<note-content.*>(.*)<\\/note-content>", Pattern.CASE_INSENSITIVE+Pattern.DOTALL); |
1478 | - |
1479 | + private static Pattern note_content = Pattern |
1480 | + .compile( |
1481 | + "<note-content.*>(.*)<\\/note-content>", |
1482 | + Pattern.CASE_INSENSITIVE |
1483 | + + Pattern.DOTALL); |
1484 | + |
1485 | // logging related |
1486 | - private final static String TAG = "SdCardSyncService"; |
1487 | - |
1488 | + private final static String TAG = "SdCardSyncService"; |
1489 | + |
1490 | public SdCardSyncService(Activity activity, Handler handler) throws FileNotFoundException { |
1491 | super(activity, handler); |
1492 | - |
1493 | + |
1494 | path = new File(Tomdroid.NOTES_PATH); |
1495 | - |
1496 | + |
1497 | if (!path.exists()) |
1498 | path.mkdir(); |
1499 | } |
1500 | - |
1501 | + |
1502 | @Override |
1503 | public String getDescription() { |
1504 | return "SD Card"; |
1505 | @@ -85,7 +90,7 @@ |
1506 | public boolean needsServer() { |
1507 | return false; |
1508 | } |
1509 | - |
1510 | + |
1511 | @Override |
1512 | public boolean needsAuth() { |
1513 | return false; |
1514 | @@ -100,54 +105,49 @@ |
1515 | if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "Loading local notes"); |
1516 | |
1517 | File[] fileList = path.listFiles(new NotesFilter()); |
1518 | - numberOfFilesToSync = fileList.length; |
1519 | - |
1520 | + numberOfFilesToSync = fileList.length; |
1521 | + |
1522 | // If there are no notes, warn the UI through an empty message |
1523 | if (fileList == null || fileList.length == 0) { |
1524 | - if (Tomdroid.LOGGING_ENABLED) Log.i(TAG, "There are no notes in "+path); |
1525 | + if (Tomdroid.LOGGING_ENABLED) |
1526 | + Log.i(TAG, "There are no notes in " + path); |
1527 | sendMessage(PARSING_NO_NOTES); |
1528 | setSyncProgress(100); |
1529 | return; |
1530 | } |
1531 | - |
1532 | - // every but the last note |
1533 | - for(int i = 0; i < fileList.length-1; i++) { |
1534 | - // TODO better progress reporting from within the workers |
1535 | - |
1536 | + |
1537 | + filesSynced = 0; |
1538 | + for (int i = 0; i < fileList.length; i++) { |
1539 | // give a filename to a thread and ask to parse it |
1540 | - execInThread(new Worker(fileList[i], false)); |
1541 | - } |
1542 | - |
1543 | - // last task, warn it so it'll warn UI when done |
1544 | - execInThread(new Worker(fileList[fileList.length-1], true)); |
1545 | + execInThread(new Worker(fileList[i])); |
1546 | + } |
1547 | } |
1548 | - |
1549 | + |
1550 | /** |
1551 | - * Simple filename filter that grabs files ending with .note |
1552 | - * TODO move into its own static class in a util package |
1553 | + * Simple filename filter that grabs files ending with .note TODO move into its own static class |
1554 | + * in a util package |
1555 | */ |
1556 | private class NotesFilter implements FilenameFilter { |
1557 | public boolean accept(File dir, String name) { |
1558 | return (name.endsWith(".note")); |
1559 | } |
1560 | } |
1561 | - |
1562 | + |
1563 | /** |
1564 | * The worker spawns a new note, parse the file its being given by the executor. |
1565 | */ |
1566 | - // TODO change type to callable to be able to throw exceptions? (if you throw make sure to display an alert only once) |
1567 | + // TODO change type to callable to be able to throw exceptions? (if you throw make sure to |
1568 | + // display an alert only once) |
1569 | // http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/Callable.html |
1570 | private class Worker implements Runnable { |
1571 | - |
1572 | + |
1573 | // the note to be loaded and parsed |
1574 | - private Note note = new Note(); |
1575 | - private File file; |
1576 | - private boolean isLast; |
1577 | - final char[] buffer = new char[0x10000]; |
1578 | - |
1579 | - public Worker(File f, boolean isLast) { |
1580 | + private Note note = new Note(); |
1581 | + private File file; |
1582 | + final char[] buffer = new char[0x10000]; |
1583 | + |
1584 | + public Worker(File f) { |
1585 | file = f; |
1586 | - this.isLast = isLast; |
1587 | } |
1588 | |
1589 | public void run() { |
1590 | @@ -178,7 +178,7 @@ |
1591 | if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "parsing note"); |
1592 | xr.parse(is); |
1593 | |
1594 | - // TODO wrap and throw a new exception here |
1595 | + // TODO wrap and throw a new exception here |
1596 | } catch (ParserConfigurationException e) { |
1597 | e.printStackTrace(); |
1598 | } catch (SAXException e) { |
1599 | @@ -187,51 +187,59 @@ |
1600 | e.printStackTrace(); |
1601 | } catch (TimeFormatException e) { |
1602 | e.printStackTrace(); |
1603 | - if (Tomdroid.LOGGING_ENABLED) Log.e(TAG, "Problem parsing the note's date and time"); |
1604 | + if (Tomdroid.LOGGING_ENABLED) |
1605 | + Log.e(TAG, "Problem parsing the note's date and time"); |
1606 | sendMessage(PARSING_FAILED); |
1607 | onWorkDone(); |
1608 | return; |
1609 | } |
1610 | |
1611 | // extract the <note-content..>...</note-content> |
1612 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "retrieving what is inside of note-content"); |
1613 | - |
1614 | - // FIXME here we are re-reading the whole note just to grab note-content out, there is probably a best way to do this (I'm talking to you xmlpull.org!) |
1615 | + if (Tomdroid.LOGGING_ENABLED) |
1616 | + Log.d(TAG, "retrieving what is inside of note-content"); |
1617 | + |
1618 | + // FIXME here we are re-reading the whole note just to grab note-content out, there is |
1619 | + // probably a best way to do this (I'm talking to you xmlpull.org!) |
1620 | StringBuilder out = new StringBuilder(); |
1621 | try { |
1622 | int read; |
1623 | Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8"); |
1624 | - |
1625 | + |
1626 | do { |
1627 | - read = reader.read(buffer, 0, buffer.length); |
1628 | - if (read>0) { |
1629 | - out.append(buffer, 0, read); |
1630 | - } |
1631 | - } |
1632 | - while (read>=0); |
1633 | - |
1634 | + read = reader.read(buffer, 0, buffer.length); |
1635 | + if (read > 0) { |
1636 | + out.append(buffer, 0, read); |
1637 | + } |
1638 | + } while (read >= 0); |
1639 | + |
1640 | Matcher m = note_content.matcher(out.toString()); |
1641 | if (m.find()) { |
1642 | note.setXmlContent(m.group()); |
1643 | } else { |
1644 | - if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to grab the note-content out of a note"); |
1645 | + if (Tomdroid.LOGGING_ENABLED) |
1646 | + Log |
1647 | + .w(TAG, |
1648 | + "Something went wrong trying to grab the note-content out of a note"); |
1649 | } |
1650 | |
1651 | } catch (IOException e) { |
1652 | // TODO handle properly |
1653 | e.printStackTrace(); |
1654 | - if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to read the note"); |
1655 | + if (Tomdroid.LOGGING_ENABLED) |
1656 | + Log.w(TAG, "Something went wrong trying to read the note"); |
1657 | } |
1658 | - |
1659 | - insertNote(note, isLast); |
1660 | + |
1661 | + getLocalStorage().insertNote(note); |
1662 | onWorkDone(); |
1663 | } |
1664 | - |
1665 | - private void onWorkDone(){ |
1666 | - if (isLast) |
1667 | - setSyncProgress(100); |
1668 | - else |
1669 | - setSyncProgress((int) (getSyncProgress() + 100.0 / numberOfFilesToSync)); |
1670 | + |
1671 | + private void onWorkDone() { |
1672 | + |
1673 | + synchronized (SdCardSyncService.this) { |
1674 | + filesSynced++; |
1675 | + } |
1676 | + |
1677 | + setSyncProgress((int) (100 * (filesSynced / (double) numberOfFilesToSync))); |
1678 | } |
1679 | } |
1680 | } |
1681 | |
1682 | === modified file 'src/org/tomdroid/sync/web/OAuthConnection.java' |
1683 | --- src/org/tomdroid/sync/web/OAuthConnection.java 2010-08-31 21:00:35 +0000 |
1684 | +++ src/org/tomdroid/sync/web/OAuthConnection.java 2010-10-10 07:54:44 +0000 |
1685 | @@ -4,6 +4,7 @@ |
1686 | * http://www.launchpad.net/tomdroid |
1687 | * |
1688 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
1689 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
1690 | * |
1691 | * This file is part of Tomdroid. |
1692 | * |
1693 | @@ -233,6 +234,7 @@ |
1694 | @Override |
1695 | public String get(String uri) throws java.net.UnknownHostException { |
1696 | |
1697 | + if (Tomdroid.LOGGING_ENABLED) Log.i(TAG, "GET " + uri); |
1698 | // Prepare a request object |
1699 | HttpGet httpGet = new HttpGet(uri); |
1700 | sign(httpGet); |
1701 | |
1702 | === renamed file 'src/org/tomdroid/sync/web/SnowySyncService.java' => 'src/org/tomdroid/sync/web/SnowySyncMethod.java' |
1703 | --- src/org/tomdroid/sync/web/SnowySyncService.java 2010-08-30 16:33:02 +0000 |
1704 | +++ src/org/tomdroid/sync/web/SnowySyncMethod.java 2010-10-10 07:54:44 +0000 |
1705 | @@ -4,6 +4,7 @@ |
1706 | * http://www.launchpad.net/tomdroid |
1707 | * |
1708 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
1709 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
1710 | * |
1711 | * This file is part of Tomdroid. |
1712 | * |
1713 | @@ -24,15 +25,13 @@ |
1714 | |
1715 | import java.net.UnknownHostException; |
1716 | import java.util.ArrayList; |
1717 | +import java.util.Set; |
1718 | |
1719 | -import org.json.JSONArray; |
1720 | import org.json.JSONException; |
1721 | -import org.json.JSONObject; |
1722 | import org.tomdroid.Note; |
1723 | import org.tomdroid.sync.ServiceAuth; |
1724 | -import org.tomdroid.sync.SyncService; |
1725 | +import org.tomdroid.sync.SyncMethod; |
1726 | import org.tomdroid.ui.Tomdroid; |
1727 | -import org.tomdroid.util.Preferences; |
1728 | |
1729 | import android.app.Activity; |
1730 | import android.net.Uri; |
1731 | @@ -40,14 +39,14 @@ |
1732 | import android.os.Message; |
1733 | import android.util.Log; |
1734 | |
1735 | -public class SnowySyncService extends SyncService implements ServiceAuth { |
1736 | - |
1737 | - private static final String TAG = "SnowySyncService"; |
1738 | - |
1739 | - public SnowySyncService(Activity activity, Handler handler) { |
1740 | +public class SnowySyncMethod extends SyncMethod implements ServiceAuth { |
1741 | + |
1742 | + private static final String TAG = "SnowySyncMethod"; |
1743 | + |
1744 | + public SnowySyncMethod(Activity activity, Handler handler) { |
1745 | super(activity, handler); |
1746 | } |
1747 | - |
1748 | + |
1749 | @Override |
1750 | public String getDescription() { |
1751 | return "Tomboy Web"; |
1752 | @@ -57,9 +56,9 @@ |
1753 | public String getName() { |
1754 | return "tomboy-web"; |
1755 | } |
1756 | - |
1757 | + |
1758 | public boolean isConfigured() { |
1759 | - OAuthConnection auth = getAuthConnection(); |
1760 | + OAuthConnection auth = SyncServer.getAuthConnection(); |
1761 | return auth.isAuthenticated(); |
1762 | } |
1763 | |
1764 | @@ -67,12 +66,12 @@ |
1765 | public boolean needsServer() { |
1766 | return true; |
1767 | } |
1768 | - |
1769 | + |
1770 | @Override |
1771 | public boolean needsAuth() { |
1772 | return true; |
1773 | } |
1774 | - |
1775 | + |
1776 | public void getAuthUri(final String server, final Handler handler) { |
1777 | |
1778 | execInThread(new Runnable() { |
1779 | @@ -110,7 +109,7 @@ |
1780 | // TODO: might be intelligent to show something like a progress dialog |
1781 | // else the user might try to sync before the authorization process |
1782 | // is complete |
1783 | - OAuthConnection auth = getAuthConnection(); |
1784 | + OAuthConnection auth = SyncServer.getAuthConnection(); |
1785 | boolean result = auth.getAccess(uri.getQueryParameter("oauth_verifier")); |
1786 | |
1787 | if (Tomdroid.LOGGING_ENABLED) { |
1788 | @@ -130,80 +129,38 @@ |
1789 | } |
1790 | }); |
1791 | } |
1792 | - |
1793 | + |
1794 | @Override |
1795 | - public boolean isSyncable(){ |
1796 | - return super.isSyncable() && isConfigured(); |
1797 | + public boolean isSyncable() { |
1798 | + return super.isSyncable() && isConfigured(); |
1799 | } |
1800 | - |
1801 | |
1802 | @Override |
1803 | protected void sync() { |
1804 | - |
1805 | + |
1806 | // start loading snowy notes |
1807 | setSyncProgress(0); |
1808 | - if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "Loading Snowy notes"); |
1809 | - |
1810 | - final String userRef = Preferences.getString(Preferences.Key.SYNC_SERVER_USER_API); |
1811 | - |
1812 | + if (Tomdroid.LOGGING_ENABLED) |
1813 | + Log.v(TAG, "Loading Snowy notes"); |
1814 | + |
1815 | execInThread(new Runnable() { |
1816 | - |
1817 | + |
1818 | public void run() { |
1819 | - |
1820 | - OAuthConnection auth = getAuthConnection(); |
1821 | - |
1822 | try { |
1823 | - String rawResponse = auth.get(userRef); |
1824 | + SyncServer server = new SyncServer(); |
1825 | setSyncProgress(30); |
1826 | - JSONObject response = new JSONObject(rawResponse); |
1827 | - String notesUrl = response.getJSONObject("notes-ref").getString("api-ref"); |
1828 | - |
1829 | - response = new JSONObject(auth.get(notesUrl)); |
1830 | - |
1831 | - long latestSyncRevision = (Long)Preferences.getLong(Preferences.Key.LATEST_SYNC_REVISION); |
1832 | - setSyncProgress(35); |
1833 | - |
1834 | - if (response.getLong("latest-sync-revision") < latestSyncRevision) { |
1835 | - setSyncProgress(100); |
1836 | - return; |
1837 | - } |
1838 | - |
1839 | - response = new JSONObject(auth.get(notesUrl + "?include_notes=true")); |
1840 | - JSONArray notes = response.getJSONArray("notes"); |
1841 | - setSyncProgress(60); |
1842 | - |
1843 | - // Delete the notes that are not in the database |
1844 | - ArrayList<String> remoteGuids = new ArrayList<String>(); |
1845 | - |
1846 | - for (int i = 0; i < notes.length(); i++) { |
1847 | - remoteGuids.add(notes.getJSONObject(i).getString("guid")); |
1848 | - } |
1849 | - |
1850 | - deleteNotes(remoteGuids); |
1851 | - setSyncProgress(70); |
1852 | - |
1853 | - // Insert or update the rest of the notes |
1854 | - for (int i = 0; i < notes.length() - 1; i++) { |
1855 | - |
1856 | - JSONObject jsonNote = notes.getJSONObject(i); |
1857 | - insertNote(new Note(jsonNote), false); |
1858 | - } |
1859 | - setSyncProgress(90); |
1860 | - |
1861 | - JSONObject jsonNote = notes.getJSONObject(notes.length() - 1); |
1862 | - insertNote(new Note(jsonNote), true); |
1863 | - |
1864 | - Preferences.putLong(Preferences.Key.LATEST_SYNC_REVISION, response |
1865 | - .getLong("latest-sync-revision")); |
1866 | - setSyncProgress(100); |
1867 | - |
1868 | + |
1869 | + syncWith(server); |
1870 | + |
1871 | } catch (JSONException e1) { |
1872 | - if (Tomdroid.LOGGING_ENABLED) Log.e(TAG, "Problem parsing the server response", e1); |
1873 | + if (Tomdroid.LOGGING_ENABLED) |
1874 | + Log.e(TAG, "Problem parsing the server response", e1); |
1875 | sendMessage(PARSING_FAILED); |
1876 | setSyncProgress(100); |
1877 | return; |
1878 | } catch (java.net.UnknownHostException e) { |
1879 | - if (Tomdroid.LOGGING_ENABLED) Log.e(TAG, "Internet connection not available"); |
1880 | + if (Tomdroid.LOGGING_ENABLED) |
1881 | + Log.e(TAG, "Internet connection not available"); |
1882 | sendMessage(NO_INTERNET); |
1883 | setSyncProgress(100); |
1884 | return; |
1885 | @@ -211,22 +168,90 @@ |
1886 | } |
1887 | }); |
1888 | } |
1889 | - |
1890 | - private OAuthConnection getAuthConnection() { |
1891 | - |
1892 | - OAuthConnection auth = new OAuthConnection(); |
1893 | - |
1894 | - auth.accessToken = Preferences.getString(Preferences.Key.ACCESS_TOKEN); |
1895 | - auth.accessTokenSecret = Preferences.getString(Preferences.Key.ACCESS_TOKEN_SECRET); |
1896 | - auth.requestToken = Preferences.getString(Preferences.Key.REQUEST_TOKEN); |
1897 | - auth.requestTokenSecret = Preferences.getString(Preferences.Key.REQUEST_TOKEN_SECRET); |
1898 | - auth.oauth10a = Preferences.getBoolean(Preferences.Key.OAUTH_10A); |
1899 | - auth.authorizeUrl = Preferences.getString(Preferences.Key.AUTHORIZE_URL); |
1900 | - auth.accessTokenUrl = Preferences.getString(Preferences.Key.ACCESS_TOKEN_URL); |
1901 | - auth.requestTokenUrl = Preferences.getString(Preferences.Key.REQUEST_TOKEN_URL); |
1902 | - auth.rootApi = Preferences.getString(Preferences.Key.SYNC_SERVER_ROOT_API); |
1903 | - auth.userApi = Preferences.getString(Preferences.Key.SYNC_SERVER_USER_API); |
1904 | - |
1905 | - return auth; |
1906 | - } |
1907 | + |
1908 | + void syncWith(SyncServer server) throws UnknownHostException, JSONException { |
1909 | + |
1910 | + if (server.isInSync(getLocalStorage())) { |
1911 | + setSyncProgress(100); |
1912 | + return; |
1913 | + } |
1914 | + |
1915 | + ensureServerIdIsAsExpected(); |
1916 | + |
1917 | + ArrayList<Note> updatesFromServer = server.getNoteUpdates(); |
1918 | + setSyncProgress(50); |
1919 | + |
1920 | + fixTitleConflicts(updatesFromServer); |
1921 | + |
1922 | + insertAndUpdateLocalNotes(updatesFromServer); |
1923 | + setSyncProgress(70); |
1924 | + |
1925 | + deleteNotesNotFoundOnServer(server); |
1926 | + |
1927 | + if (!server.createNewRevisionWith(getLocalStorage().getNewAndUpdatedNotes())){ |
1928 | + setSyncProgress(100); |
1929 | + return; |
1930 | + } |
1931 | + setSyncProgress(90); |
1932 | + |
1933 | + deleteNotesNotFoundOnClient(server); |
1934 | + |
1935 | + getLocalStorage().onSynced(server.getSyncRevision()); |
1936 | + setSyncProgress(100); |
1937 | + } |
1938 | + |
1939 | + private void deleteNotesNotFoundOnServer(SyncServer server) throws UnknownHostException, |
1940 | + JSONException { |
1941 | + Set<String> remotelyRemovedNoteIds = getLocalStorage().getNoteGuids(); |
1942 | + remotelyRemovedNoteIds.removeAll(server.getNoteIds()); |
1943 | + getLocalStorage().deleteNotes(remotelyRemovedNoteIds); |
1944 | + } |
1945 | + |
1946 | + private void deleteNotesNotFoundOnClient(SyncServer server) throws UnknownHostException, |
1947 | + JSONException { |
1948 | + Set<String> locallyRemovedNoteIds = server.getNoteIds(); |
1949 | + locallyRemovedNoteIds.removeAll(getLocalStorage().getNoteGuids()); |
1950 | + server.delete(locallyRemovedNoteIds); |
1951 | + } |
1952 | + |
1953 | + /** |
1954 | + * Check if the server's guid is as expected to prevent deleting local notes, etc when the |
1955 | + * server has been wiped or reinitialized by another client. |
1956 | + */ |
1957 | + private void ensureServerIdIsAsExpected() { |
1958 | + |
1959 | + /* |
1960 | + * // If the server has been wiped or reinitialized by another client // for some reason, |
1961 | + * our local manifest is inaccurate and could misguide // sync into erroneously deleting |
1962 | + * local notes, etc. We reset the client // to prevent this situation. string serverId = |
1963 | + * server.Id; if (client.AssociatedServerId != serverId) { client.Reset (); |
1964 | + * client.AssociatedServerId = serverId; } |
1965 | + */ |
1966 | + } |
1967 | + |
1968 | + private void insertAndUpdateLocalNotes(ArrayList<Note> serverUpdates) { |
1969 | + for (Note noteUpdate : serverUpdates) { |
1970 | + getLocalStorage().mergeNote(noteUpdate); |
1971 | + } |
1972 | + } |
1973 | + |
1974 | + private void fixTitleConflicts(ArrayList<Note> noteUpdates) { |
1975 | + |
1976 | + // TODO implement in a similar way as Tomboy (see code below) |
1977 | + |
1978 | + /* |
1979 | + * // First, check for new local notes that might have title conflicts // with the updates |
1980 | + * coming from the server. Prompt the user if necessary. // TODO: Lots of searching here and |
1981 | + * in the next foreach... // Want this stuff to happen all at once first, but // maybe |
1982 | + * there's a way to store this info and pass it on? foreach (NoteUpdate noteUpdate in |
1983 | + * noteUpdates.Values) { if (FindNoteByUUID (noteUpdate.UUID) == null) { Note existingNote = |
1984 | + * NoteMgr.Find (noteUpdate.Title); if (existingNote != null && !noteUpdate.BasicallyEqualTo |
1985 | + * (existingNote)) { // Logger.Debug ("Sync: Early conflict detection for '{0}'", |
1986 | + * noteUpdate.Title); if (syncUI != null) { syncUI.NoteConflictDetected (NoteMgr, |
1987 | + * existingNote, noteUpdate, noteUpdateTitles); // Suspend this thread while the GUI is |
1988 | + * presented to // the user. syncThread.Suspend (); } } } } |
1989 | + */ |
1990 | + } |
1991 | + |
1992 | + |
1993 | } |
1994 | |
1995 | === added file 'src/org/tomdroid/sync/web/SyncServer.java' |
1996 | --- src/org/tomdroid/sync/web/SyncServer.java 1970-01-01 00:00:00 +0000 |
1997 | +++ src/org/tomdroid/sync/web/SyncServer.java 2010-10-10 07:54:44 +0000 |
1998 | @@ -0,0 +1,205 @@ |
1999 | +/* |
2000 | + * Tomdroid |
2001 | + * Tomboy on Android |
2002 | + * http://www.launchpad.net/tomdroid |
2003 | + * |
2004 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
2005 | + * |
2006 | + * This file is part of Tomdroid. |
2007 | + * |
2008 | + * Tomdroid is free software: you can redistribute it and/or modify |
2009 | + * it under the terms of the GNU General Public License as published by |
2010 | + * the Free Software Foundation, either version 3 of the License, or |
2011 | + * (at your option) any later version. |
2012 | + * |
2013 | + * Tomdroid is distributed in the hope that it will be useful, |
2014 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2015 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2016 | + * GNU General Public License for more details. |
2017 | + * |
2018 | + * You should have received a copy of the GNU General Public License |
2019 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
2020 | + */ |
2021 | +package org.tomdroid.sync.web; |
2022 | + |
2023 | +import java.net.UnknownHostException; |
2024 | +import java.util.ArrayList; |
2025 | +import java.util.HashSet; |
2026 | +import java.util.Set; |
2027 | + |
2028 | +import org.json.JSONArray; |
2029 | +import org.json.JSONException; |
2030 | +import org.json.JSONObject; |
2031 | +import org.tomdroid.Note; |
2032 | +import org.tomdroid.sync.LocalStorage; |
2033 | +import org.tomdroid.ui.Tomdroid; |
2034 | +import org.tomdroid.util.Preferences; |
2035 | + |
2036 | +import android.util.Log; |
2037 | + |
2038 | +public class SyncServer { |
2039 | + |
2040 | + final String TAG = "SyncServer"; |
2041 | + final String userReference = Preferences |
2042 | + .getString(Preferences.Key.SYNC_SERVER_USER_API); |
2043 | + private String notesApiReference; |
2044 | + String userName; |
2045 | + String firstName; |
2046 | + String lastName; |
2047 | + Long syncVersionOnServer; |
2048 | + Long currentSyncGuid; |
2049 | + private OAuthConnection authConnection; |
2050 | + |
2051 | + public SyncServer() throws UnknownHostException, JSONException { |
2052 | + authConnection = getAuthConnection(); |
2053 | + |
2054 | + readMetaData(getMetadata()); |
2055 | + } |
2056 | + |
2057 | + protected JSONObject getMetadata() throws UnknownHostException, JSONException { |
2058 | + String rawResponse = authConnection.get(userReference); |
2059 | + JSONObject response = new JSONObject(rawResponse); |
2060 | + return response; |
2061 | + } |
2062 | + |
2063 | + protected void readMetaData(JSONObject response) throws JSONException { |
2064 | + Log.v(TAG, "userRef response: " + response.toString()); |
2065 | + notesApiReference = response.getJSONObject("notes-ref").getString("api-ref"); |
2066 | + |
2067 | + userName = response.getString("user-name"); |
2068 | + firstName = response.getString("first-name"); |
2069 | + lastName = response.getString("last-name"); |
2070 | + |
2071 | + syncVersionOnServer = response.getLong("latest-sync-revision"); |
2072 | + currentSyncGuid = response.getLong("current-sync-guid"); |
2073 | + } |
2074 | + |
2075 | + private String getNotesUri() { |
2076 | + return notesApiReference; |
2077 | + } |
2078 | + |
2079 | + public boolean isInSync(LocalStorage localStorage) { |
2080 | + return localStorage.getLatestSyncVersion() == syncVersionOnServer |
2081 | + && localStorage.getNewAndUpdatedNotes().isEmpty(); |
2082 | + } |
2083 | + |
2084 | + public Long getSyncRevision() { |
2085 | + return syncVersionOnServer; |
2086 | + } |
2087 | + |
2088 | + public JSONArray getNotes() throws UnknownHostException, JSONException { |
2089 | + JSONObject response = new JSONObject(authConnection.get(getNotesUri() |
2090 | + + "?include_notes=true")); |
2091 | + return response.getJSONArray("notes"); |
2092 | + } |
2093 | + |
2094 | + public static OAuthConnection getAuthConnection() { |
2095 | + |
2096 | + OAuthConnection auth = new OAuthConnection(); |
2097 | + |
2098 | + auth.accessToken = Preferences.getString(Preferences.Key.ACCESS_TOKEN); |
2099 | + auth.accessTokenSecret = Preferences.getString(Preferences.Key.ACCESS_TOKEN_SECRET); |
2100 | + auth.requestToken = Preferences.getString(Preferences.Key.REQUEST_TOKEN); |
2101 | + auth.requestTokenSecret = Preferences.getString(Preferences.Key.REQUEST_TOKEN_SECRET); |
2102 | + auth.oauth10a = Preferences.getBoolean(Preferences.Key.OAUTH_10A); |
2103 | + auth.authorizeUrl = Preferences.getString(Preferences.Key.AUTHORIZE_URL); |
2104 | + auth.accessTokenUrl = Preferences.getString(Preferences.Key.ACCESS_TOKEN_URL); |
2105 | + auth.requestTokenUrl = Preferences.getString(Preferences.Key.REQUEST_TOKEN_URL); |
2106 | + auth.rootApi = Preferences.getString(Preferences.Key.SYNC_SERVER_ROOT_API); |
2107 | + auth.userApi = Preferences.getString(Preferences.Key.SYNC_SERVER_USER_API); |
2108 | + |
2109 | + return auth; |
2110 | + } |
2111 | + |
2112 | + public ArrayList<Note> getNoteUpdates() throws UnknownHostException, JSONException { |
2113 | + ArrayList<Note> updates = new ArrayList<Note>(); |
2114 | + |
2115 | + long since = Preferences.getLong(Preferences.Key.LATEST_SYNC_REVISION); |
2116 | + JSONObject response = getNoteUpdatesSince(since); |
2117 | + |
2118 | + JSONArray jsonNotes = response.getJSONArray("notes"); |
2119 | + for (int i = 0; i < jsonNotes.length(); i++){ |
2120 | + Note note = new Note(jsonNotes.getJSONObject(i)); |
2121 | + |
2122 | + updates.add(note); |
2123 | + } |
2124 | + |
2125 | + return updates; |
2126 | + } |
2127 | + |
2128 | + protected JSONObject getNoteUpdatesSince(long since) throws JSONException, UnknownHostException { |
2129 | + JSONObject response = new JSONObject(authConnection.get(getNotesUri() |
2130 | + + "?include_notes=true&since=" |
2131 | + + since)); |
2132 | + return response; |
2133 | + } |
2134 | + |
2135 | + public Set<String> getNoteIds() throws UnknownHostException, JSONException { |
2136 | + Set<String> guids = new HashSet<String>(); |
2137 | + |
2138 | + JSONObject response = getAllNotesWithoutContent(); |
2139 | + |
2140 | + JSONArray jsonNotes = response.getJSONArray("notes"); |
2141 | + for (int i = 0; i < jsonNotes.length(); i++){ |
2142 | + guids.add(jsonNotes.getJSONObject(i).getString("guid")); |
2143 | + } |
2144 | + |
2145 | + return guids; |
2146 | + } |
2147 | + |
2148 | + protected JSONObject getAllNotesWithoutContent() throws JSONException, UnknownHostException { |
2149 | + String rawResponse = authConnection.get(getNotesUri()); |
2150 | + JSONObject response = new JSONObject(rawResponse); |
2151 | + return response; |
2152 | + } |
2153 | + |
2154 | + |
2155 | + /** |
2156 | + * @return true if successful |
2157 | + */ |
2158 | + public boolean createNewRevisionWith(ArrayList<Note> newAndUpdatedNotes) throws JSONException { |
2159 | + if (newAndUpdatedNotes.isEmpty()){ |
2160 | + return true; |
2161 | + } |
2162 | + |
2163 | + JSONArray jsonNotes = new JSONArray(); |
2164 | + for (Note note : newAndUpdatedNotes) { |
2165 | + jsonNotes.put(note.toJson()); |
2166 | + } |
2167 | + |
2168 | + JSONObject updates = new JSONObject(); |
2169 | + updates.put("latest-sync-revision", getSyncRevision() + 1); |
2170 | + updates.put("note-changes", jsonNotes); |
2171 | + |
2172 | + long newRevision = upload(updates); |
2173 | + |
2174 | + if (newRevision == getSyncRevision() + 1) { |
2175 | + syncVersionOnServer = newRevision; |
2176 | + return true; |
2177 | + } |
2178 | + return false; |
2179 | + } |
2180 | + |
2181 | + /** |
2182 | + * @return new revision if successful, -1 if not |
2183 | + */ |
2184 | + protected int upload(JSONObject data){ |
2185 | + int revision = -1; |
2186 | + try { |
2187 | + JSONObject response = new JSONObject(authConnection.put(getNotesUri(), data.toString())); |
2188 | + revision = response.getInt("latest-sync-revision"); |
2189 | + } catch (UnknownHostException e) { |
2190 | + if (Tomdroid.LOGGING_ENABLED) Log.e(TAG, e.toString()); |
2191 | + } catch (JSONException e) { |
2192 | + if (Tomdroid.LOGGING_ENABLED) Log.e(TAG, e.toString()); |
2193 | + } |
2194 | + |
2195 | + return revision; |
2196 | + } |
2197 | + |
2198 | + |
2199 | + public void delete(Set<String> disposedNoteIds) { |
2200 | + // TODO Auto-generated method stub |
2201 | + } |
2202 | + |
2203 | +} |
2204 | |
2205 | === modified file 'src/org/tomdroid/ui/PreferencesActivity.java' |
2206 | --- src/org/tomdroid/ui/PreferencesActivity.java 2010-10-09 19:58:11 +0000 |
2207 | +++ src/org/tomdroid/ui/PreferencesActivity.java 2010-10-10 07:54:44 +0000 |
2208 | @@ -4,6 +4,7 @@ |
2209 | * http://www.launchpad.net/tomdroid |
2210 | * |
2211 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
2212 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
2213 | * Copyright 2010, Olivier Bilodeau <olivier@bottomlesspit.org> |
2214 | * |
2215 | * This file is part of Tomdroid. |
2216 | @@ -29,7 +30,7 @@ |
2217 | import org.tomdroid.R; |
2218 | import org.tomdroid.sync.ServiceAuth; |
2219 | import org.tomdroid.sync.SyncManager; |
2220 | -import org.tomdroid.sync.SyncService; |
2221 | +import org.tomdroid.sync.SyncMethod; |
2222 | import org.tomdroid.util.FirstNote; |
2223 | import org.tomdroid.util.Preferences; |
2224 | |
2225 | @@ -55,8 +56,8 @@ |
2226 | private static final String TAG = "PreferencesActivity"; |
2227 | |
2228 | // TODO: put the various preferences in fields and figure out what to do on activity suspend/resume |
2229 | - private EditTextPreference syncServer = null; |
2230 | - private ListPreference syncService = null; |
2231 | + private EditTextPreference syncServerUriPreference = null; |
2232 | + private ListPreference syncMethodPreference = null; |
2233 | |
2234 | @Override |
2235 | protected void onCreate(Bundle savedInstanceState) { |
2236 | @@ -65,25 +66,24 @@ |
2237 | addPreferencesFromResource(R.xml.preferences); |
2238 | |
2239 | // Fill the Preferences fields |
2240 | - syncServer = (EditTextPreference)findPreference(Preferences.Key.SYNC_SERVER.getName()); |
2241 | - syncService = (ListPreference)findPreference(Preferences.Key.SYNC_SERVICE.getName()); |
2242 | + syncServerUriPreference = (EditTextPreference)findPreference(Preferences.Key.SYNC_SERVER_URI.getName()); |
2243 | + syncMethodPreference = (ListPreference)findPreference(Preferences.Key.SYNC_METHOD.getName()); |
2244 | |
2245 | // Set the default values if nothing exists |
2246 | this.setDefaults(); |
2247 | |
2248 | - // Fill the services combo list |
2249 | - this.fillServices(); |
2250 | + fillSyncServicesList(); |
2251 | |
2252 | // Enable or disable the server field depending on the selected sync service |
2253 | - setServer(syncService.getValue()); |
2254 | + updatePreferencesTo(syncMethodPreference.getValue()); |
2255 | |
2256 | - syncService.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { |
2257 | + syncMethodPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { |
2258 | |
2259 | public boolean onPreferenceChange(Preference preference, Object newValue) { |
2260 | String selectedSyncServiceKey = (String)newValue; |
2261 | |
2262 | // did the selection change? |
2263 | - if (!syncService.getValue().contentEquals(selectedSyncServiceKey)) { |
2264 | + if (!syncMethodPreference.getValue().contentEquals(selectedSyncServiceKey)) { |
2265 | Log.d(TAG, "preference change triggered"); |
2266 | |
2267 | syncServiceChanged(selectedSyncServiceKey); |
2268 | @@ -93,7 +93,7 @@ |
2269 | }); |
2270 | |
2271 | // Re-authenticate if the sync server changes |
2272 | - syncServer.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { |
2273 | + syncServerUriPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { |
2274 | |
2275 | public boolean onPreferenceChange(Preference preference, |
2276 | Object serverUri) { |
2277 | @@ -105,6 +105,9 @@ |
2278 | return false; |
2279 | } |
2280 | |
2281 | + // update the value before doing anything |
2282 | + Preferences.putString(Preferences.Key.SYNC_SERVER_URI, (String) serverUri); |
2283 | + |
2284 | authenticate((String) serverUri); |
2285 | return true; |
2286 | } |
2287 | @@ -116,11 +119,11 @@ |
2288 | private void authenticate(String serverUri) { |
2289 | |
2290 | // update the value before doing anything |
2291 | - Preferences.putString(Preferences.Key.SYNC_SERVER, serverUri); |
2292 | - |
2293 | - SyncService currentService = SyncManager.getInstance().getCurrentService(); |
2294 | - |
2295 | - if (!currentService.needsAuth()) { |
2296 | + Preferences.putString(Preferences.Key.SYNC_SERVER_URI, serverUri); |
2297 | + |
2298 | + SyncMethod currentSyncMethod = SyncManager.getInstance().getCurrentSyncMethod(); |
2299 | + |
2300 | + if (!currentSyncMethod.needsAuth()) { |
2301 | return; |
2302 | } |
2303 | |
2304 | @@ -159,12 +162,12 @@ |
2305 | } |
2306 | }; |
2307 | |
2308 | - ((ServiceAuth) currentService).getAuthUri(serverUri, handler); |
2309 | + ((ServiceAuth) currentSyncMethod).getAuthUri(serverUri, handler); |
2310 | } |
2311 | |
2312 | - private void fillServices() |
2313 | + private void fillSyncServicesList() |
2314 | { |
2315 | - ArrayList<SyncService> availableServices = SyncManager.getInstance().getServices(); |
2316 | + ArrayList<SyncMethod> availableServices = SyncManager.getInstance().getSyncMethods(); |
2317 | CharSequence[] entries = new CharSequence[availableServices.size()]; |
2318 | CharSequence[] entryValues = new CharSequence[availableServices.size()]; |
2319 | |
2320 | @@ -173,33 +176,32 @@ |
2321 | entryValues[i] = availableServices.get(i).getName(); |
2322 | } |
2323 | |
2324 | - syncService.setEntries(entries); |
2325 | - syncService.setEntryValues(entryValues); |
2326 | + syncMethodPreference.setEntries(entries); |
2327 | + syncMethodPreference.setEntryValues(entryValues); |
2328 | } |
2329 | |
2330 | private void setDefaults() |
2331 | { |
2332 | - String defaultServer = (String)Preferences.Key.SYNC_SERVER.getDefault(); |
2333 | - syncServer.setDefaultValue(defaultServer); |
2334 | - if(syncServer.getText() == null) |
2335 | - syncServer.setText(defaultServer); |
2336 | - |
2337 | - String defaultService = (String)Preferences.Key.SYNC_SERVICE.getDefault(); |
2338 | - syncService.setDefaultValue(defaultService); |
2339 | - if(syncService.getValue() == null) |
2340 | - syncService.setValue(defaultService); |
2341 | + String defaultServer = (String)Preferences.Key.SYNC_SERVER_URI.getDefault(); |
2342 | + syncServerUriPreference.setDefaultValue(defaultServer); |
2343 | + if(syncServerUriPreference.getText() == null) |
2344 | + syncServerUriPreference.setText(defaultServer); |
2345 | + |
2346 | + String defaultService = (String)Preferences.Key.SYNC_METHOD.getDefault(); |
2347 | + syncMethodPreference.setDefaultValue(defaultService); |
2348 | + if(syncMethodPreference.getValue() == null) |
2349 | + syncMethodPreference.setValue(defaultService); |
2350 | + } |
2351 | |
2352 | - } |
2353 | - |
2354 | - private void setServer(String syncServiceKey) { |
2355 | - |
2356 | - SyncService service = SyncManager.getInstance().getService(syncServiceKey); |
2357 | - |
2358 | - if (service == null) |
2359 | + private void updatePreferencesTo(String syncMethodName) { |
2360 | + |
2361 | + SyncMethod syncMethod = SyncManager.getInstance().getSyncMethod(syncMethodName); |
2362 | + |
2363 | + if (syncMethod == null) |
2364 | return; |
2365 | |
2366 | - syncServer.setEnabled(service.needsServer()); |
2367 | - syncService.setSummary(service.getDescription()); |
2368 | + syncServerUriPreference.setEnabled(syncMethod.needsServer()); |
2369 | + syncMethodPreference.setSummary(syncMethod.getDescription()); |
2370 | } |
2371 | |
2372 | private void connectionFailed() { |
2373 | @@ -224,22 +226,23 @@ |
2374 | |
2375 | /** |
2376 | * Housekeeping when a syncServer changes |
2377 | - * @param syncServiceKey - key of the new sync service |
2378 | + * @param syncMethodKey - key of the new sync service |
2379 | */ |
2380 | - private void syncServiceChanged(String syncServiceKey) { |
2381 | + private void syncServiceChanged(String syncMethodKey) { |
2382 | |
2383 | - setServer(syncServiceKey); |
2384 | + updatePreferencesTo(syncMethodKey); |
2385 | |
2386 | // TODO this should be refactored further, notice that setServer performs the same operations |
2387 | - SyncService service = SyncManager.getInstance().getService(syncServiceKey); |
2388 | + SyncMethod syncMethod = SyncManager.getInstance().getSyncMethod( |
2389 | + syncMethodKey); |
2390 | |
2391 | - if (service == null) |
2392 | + if (syncMethod == null) |
2393 | return; |
2394 | |
2395 | // reset if no-auth required |
2396 | // I believe it's done this way because if needsAuth the database is reset when they successfully auth for the first time |
2397 | // TODO we should graphically warn the user that his database is about to be dropped |
2398 | - if (!service.needsAuth()){ |
2399 | + if (!syncMethod.needsAuth()){ |
2400 | resetLocalDatabase(); |
2401 | } |
2402 | } |
2403 | |
2404 | === added file 'src/org/tomdroid/ui/Rotate3dAnimation.java' |
2405 | --- src/org/tomdroid/ui/Rotate3dAnimation.java 1970-01-01 00:00:00 +0000 |
2406 | +++ src/org/tomdroid/ui/Rotate3dAnimation.java 2010-10-10 07:54:44 +0000 |
2407 | @@ -0,0 +1,92 @@ |
2408 | +/* |
2409 | + * Copyright (C) 2007 The Android Open Source Project |
2410 | + * |
2411 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
2412 | + * you may not use this file except in compliance with the License. |
2413 | + * You may obtain a copy of the License at |
2414 | + * |
2415 | + * http://www.apache.org/licenses/LICENSE-2.0 |
2416 | + * |
2417 | + * Unless required by applicable law or agreed to in writing, software |
2418 | + * distributed under the License is distributed on an "AS IS" BASIS, |
2419 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
2420 | + * See the License for the specific language governing permissions and |
2421 | + * limitations under the License. |
2422 | + */ |
2423 | + |
2424 | +package org.tomdroid.ui; |
2425 | + |
2426 | +import android.view.animation.Animation; |
2427 | +import android.view.animation.Transformation; |
2428 | +import android.graphics.Camera; |
2429 | +import android.graphics.Matrix; |
2430 | + |
2431 | +/** |
2432 | + * An animation that rotates the view on the Y axis between two specified angles. |
2433 | + * This animation also adds a translation on the Z axis (depth) to improve the effect. |
2434 | + */ |
2435 | +public class Rotate3dAnimation extends Animation { |
2436 | + private final float mFromDegrees; |
2437 | + private final float mToDegrees; |
2438 | + private final float mCenterX; |
2439 | + private final float mCenterY; |
2440 | + private final float mDepthZ; |
2441 | + private final boolean mReverse; |
2442 | + private Camera mCamera; |
2443 | + |
2444 | + /** |
2445 | + * Creates a new 3D rotation on the Y axis. The rotation is defined by its |
2446 | + * start angle and its end angle. Both angles are in degrees. The rotation |
2447 | + * is performed around a center point on the 2D space, definied by a pair |
2448 | + * of X and Y coordinates, called centerX and centerY. When the animation |
2449 | + * starts, a translation on the Z axis (depth) is performed. The length |
2450 | + * of the translation can be specified, as well as whether the translation |
2451 | + * should be reversed in time. |
2452 | + * |
2453 | + * @param fromDegrees the start angle of the 3D rotation |
2454 | + * @param toDegrees the end angle of the 3D rotation |
2455 | + * @param centerX the X center of the 3D rotation |
2456 | + * @param centerY the Y center of the 3D rotation |
2457 | + * @param reverse true if the translation should be reversed, false otherwise |
2458 | + */ |
2459 | + public Rotate3dAnimation(float fromDegrees, float toDegrees, |
2460 | + float centerX, float centerY, float depthZ, boolean reverse) { |
2461 | + mFromDegrees = fromDegrees; |
2462 | + mToDegrees = toDegrees; |
2463 | + mCenterX = centerX; |
2464 | + mCenterY = centerY; |
2465 | + mDepthZ = depthZ; |
2466 | + mReverse = reverse; |
2467 | + } |
2468 | + |
2469 | + @Override |
2470 | + public void initialize(int width, int height, int parentWidth, int parentHeight) { |
2471 | + super.initialize(width, height, parentWidth, parentHeight); |
2472 | + mCamera = new Camera(); |
2473 | + } |
2474 | + |
2475 | + @Override |
2476 | + protected void applyTransformation(float interpolatedTime, Transformation t) { |
2477 | + final float fromDegrees = mFromDegrees; |
2478 | + float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); |
2479 | + |
2480 | + final float centerX = mCenterX; |
2481 | + final float centerY = mCenterY; |
2482 | + final Camera camera = mCamera; |
2483 | + |
2484 | + final Matrix matrix = t.getMatrix(); |
2485 | + |
2486 | + camera.save(); |
2487 | + if (mReverse) { |
2488 | + camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); |
2489 | + } else { |
2490 | + camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); |
2491 | + } |
2492 | + camera.rotateY(degrees); |
2493 | + camera.getMatrix(matrix); |
2494 | + camera.restore(); |
2495 | + |
2496 | + matrix.preTranslate(-centerX, -centerY); |
2497 | + matrix.postTranslate(centerX, centerY); |
2498 | + } |
2499 | +} |
2500 | |
2501 | === modified file 'src/org/tomdroid/ui/SyncMessageHandler.java' |
2502 | --- src/org/tomdroid/ui/SyncMessageHandler.java 2010-08-25 19:43:16 +0000 |
2503 | +++ src/org/tomdroid/ui/SyncMessageHandler.java 2010-10-10 07:54:44 +0000 |
2504 | @@ -24,7 +24,7 @@ |
2505 | |
2506 | import org.tomdroid.R; |
2507 | import org.tomdroid.sync.SyncManager; |
2508 | -import org.tomdroid.sync.SyncService; |
2509 | +import org.tomdroid.sync.SyncMethod; |
2510 | |
2511 | import android.app.Activity; |
2512 | import android.app.AlertDialog; |
2513 | @@ -56,25 +56,25 @@ |
2514 | public void handleMessage(Message msg) { |
2515 | |
2516 | switch (msg.what) { |
2517 | - case SyncService.PARSING_COMPLETE: |
2518 | + case SyncMethod.PARSING_COMPLETE: |
2519 | // TODO put string in a translatable bundle |
2520 | Toast.makeText( |
2521 | activity, |
2522 | "Synchronization with " |
2523 | - + SyncManager.getInstance().getCurrentService().getDescription() |
2524 | + + SyncManager.getInstance().getCurrentSyncMethod().getDescription() |
2525 | + " is complete.", Toast.LENGTH_SHORT).show(); |
2526 | break; |
2527 | |
2528 | - case SyncService.PARSING_NO_NOTES: |
2529 | + case SyncMethod.PARSING_NO_NOTES: |
2530 | // TODO put string in a translatable bundle |
2531 | Toast.makeText( |
2532 | activity, |
2533 | "No notes found on " |
2534 | - + SyncManager.getInstance().getCurrentService().getDescription() |
2535 | + + SyncManager.getInstance().getCurrentSyncMethod().getDescription() |
2536 | + ".", Toast.LENGTH_SHORT).show(); |
2537 | break; |
2538 | |
2539 | - case SyncService.PARSING_FAILED: |
2540 | + case SyncMethod.PARSING_FAILED: |
2541 | if (Tomdroid.LOGGING_ENABLED) |
2542 | Log.w(TAG, "handler called with a parsing failed message"); |
2543 | |
2544 | @@ -95,13 +95,13 @@ |
2545 | } |
2546 | break; |
2547 | |
2548 | - case SyncService.NO_INTERNET: |
2549 | + case SyncMethod.NO_INTERNET: |
2550 | // TODO put string in a translatable bundle |
2551 | Toast.makeText(activity, "You are not connected to the internet.", |
2552 | Toast.LENGTH_SHORT).show(); |
2553 | break; |
2554 | |
2555 | - case SyncService.SYNC_PROGRESS: |
2556 | + case SyncMethod.SYNC_PROGRESS: |
2557 | handleSyncProgress(msg); |
2558 | break; |
2559 | |
2560 | |
2561 | === modified file 'src/org/tomdroid/ui/Tomdroid.java' |
2562 | --- src/org/tomdroid/ui/Tomdroid.java 2010-10-09 19:58:11 +0000 |
2563 | +++ src/org/tomdroid/ui/Tomdroid.java 2010-10-10 07:54:44 +0000 |
2564 | @@ -27,9 +27,10 @@ |
2565 | import org.tomdroid.Note; |
2566 | import org.tomdroid.NoteManager; |
2567 | import org.tomdroid.R; |
2568 | +import org.tomdroid.sync.LocalStorage; |
2569 | import org.tomdroid.sync.ServiceAuth; |
2570 | import org.tomdroid.sync.SyncManager; |
2571 | -import org.tomdroid.sync.SyncService; |
2572 | +import org.tomdroid.sync.SyncMethod; |
2573 | import org.tomdroid.util.FirstNote; |
2574 | import org.tomdroid.util.Preferences; |
2575 | |
2576 | @@ -71,7 +72,7 @@ |
2577 | public static final String NOTES_PATH = Environment.getExternalStorageDirectory() |
2578 | + "/tomdroid/"; |
2579 | // Logging should be disabled for release builds |
2580 | - public static final boolean LOGGING_ENABLED = false; |
2581 | + public static final boolean LOGGING_ENABLED = true; |
2582 | // Set this to false for release builds, the reason should be obvious |
2583 | public static final boolean CLEAR_PREFERENCES = false; |
2584 | |
2585 | @@ -83,8 +84,8 @@ |
2586 | private ListAdapter adapter; |
2587 | |
2588 | // UI feedback handler |
2589 | - private Handler syncMessageHandler = new SyncMessageHandler(this); |
2590 | - |
2591 | + private final Handler syncMessageHandler = new SyncMessageHandler(this); |
2592 | + |
2593 | /** Called when the activity is created. */ |
2594 | @Override |
2595 | public void onCreate(Bundle savedInstanceState) { |
2596 | @@ -137,6 +138,11 @@ |
2597 | showAboutDialog(); |
2598 | return true; |
2599 | |
2600 | + case R.id.menuRevert: |
2601 | + LocalStorage localStorage = new LocalStorage(this); |
2602 | + localStorage.resetDatabase(); |
2603 | + return true; |
2604 | + |
2605 | case R.id.menuPrefs: |
2606 | startActivity(new Intent(this, PreferencesActivity.class)); |
2607 | return true; |
2608 | @@ -145,34 +151,37 @@ |
2609 | return super.onOptionsItemSelected(item); |
2610 | } |
2611 | |
2612 | + @Override |
2613 | public void onResume() { |
2614 | super.onResume(); |
2615 | Intent intent = this.getIntent(); |
2616 | |
2617 | - SyncService currentService = SyncManager.getInstance().getCurrentService(); |
2618 | - |
2619 | - if (currentService.needsAuth() && intent != null) { |
2620 | + if (intent != null) { |
2621 | Uri uri = intent.getData(); |
2622 | |
2623 | if (uri != null && uri.getScheme().equals("tomdroid")) { |
2624 | Log.i(TAG, "Got url : " + uri.toString()); |
2625 | - |
2626 | - final ProgressDialog dialog = ProgressDialog.show(this, "", |
2627 | - "Completing authentication. Please wait...", true, false); |
2628 | - |
2629 | - Handler handler = new Handler() { |
2630 | - |
2631 | - @Override |
2632 | - public void handleMessage(Message msg) { |
2633 | - dialog.dismiss(); |
2634 | - } |
2635 | - |
2636 | - }; |
2637 | - |
2638 | - ((ServiceAuth) currentService).remoteAuthComplete(uri, handler); |
2639 | + SyncMethod currentSyncMethod = SyncManager.getInstance().getCurrentSyncMethod(); |
2640 | + |
2641 | + if (currentSyncMethod.needsAuth()) { |
2642 | + final ProgressDialog dialog = ProgressDialog.show(this, "", |
2643 | + "Completing authentication. Please wait...", true, false); |
2644 | + |
2645 | + Handler handler = new Handler() { |
2646 | + |
2647 | + @Override |
2648 | + public void handleMessage(Message msg) { |
2649 | + dialog.dismiss(); |
2650 | + } |
2651 | + |
2652 | + }; |
2653 | + |
2654 | + // the user has completed the remote auth, do the third part |
2655 | + ((ServiceAuth) currentSyncMethod).remoteAuthComplete(uri, handler); |
2656 | + } |
2657 | } |
2658 | } |
2659 | - |
2660 | + |
2661 | SyncManager.setActivity(this); |
2662 | SyncManager.setHandler(this.syncMessageHandler); |
2663 | } |
2664 | @@ -190,7 +199,7 @@ |
2665 | |
2666 | // format the string |
2667 | String aboutDialogFormat = getString(R.string.strAbout); |
2668 | - String aboutDialogStr = String.format(aboutDialogFormat, getString(R.string.app_desc), // App description |
2669 | + String aboutDialogStr = String.format(aboutDialogFormat, getString(R.string.app_desc), |
2670 | getString(R.string.author), // Author name |
2671 | ver // Version |
2672 | ); |
2673 | |
2674 | === modified file 'src/org/tomdroid/ui/ViewNote.java' |
2675 | --- src/org/tomdroid/ui/ViewNote.java 2010-10-09 19:54:14 +0000 |
2676 | +++ src/org/tomdroid/ui/ViewNote.java 2010-10-10 07:54:44 +0000 |
2677 | @@ -5,6 +5,7 @@ |
2678 | * |
2679 | * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org> |
2680 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
2681 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
2682 | * |
2683 | * This file is part of Tomdroid. |
2684 | * |
2685 | @@ -29,17 +30,18 @@ |
2686 | import org.tomdroid.Note; |
2687 | import org.tomdroid.NoteManager; |
2688 | import org.tomdroid.R; |
2689 | +import org.tomdroid.sync.LocalStorage; |
2690 | import org.tomdroid.sync.SyncManager; |
2691 | import org.tomdroid.util.LinkifyPhone; |
2692 | import org.tomdroid.util.NoteContentBuilder; |
2693 | |
2694 | import android.app.Activity; |
2695 | import android.app.AlertDialog; |
2696 | +import android.content.Context; |
2697 | import android.content.DialogInterface; |
2698 | import android.content.Intent; |
2699 | import android.content.DialogInterface.OnClickListener; |
2700 | import android.database.Cursor; |
2701 | -import android.graphics.Color; |
2702 | import android.net.Uri; |
2703 | import android.os.Bundle; |
2704 | import android.os.Handler; |
2705 | @@ -48,65 +50,90 @@ |
2706 | import android.text.util.Linkify; |
2707 | import android.text.util.Linkify.TransformFilter; |
2708 | import android.util.Log; |
2709 | -import android.view.KeyEvent; |
2710 | +import android.view.GestureDetector; |
2711 | +import android.view.Menu; |
2712 | +import android.view.MenuInflater; |
2713 | +import android.view.MenuItem; |
2714 | +import android.view.MotionEvent; |
2715 | +import android.view.ViewGroup; |
2716 | +import android.view.inputmethod.InputMethodManager; |
2717 | import android.widget.TextView; |
2718 | +import android.widget.Toast; |
2719 | |
2720 | // TODO this class is starting to smell |
2721 | public class ViewNote extends Activity { |
2722 | - |
2723 | + |
2724 | // UI elements |
2725 | - private TextView title; |
2726 | - private TextView content; |
2727 | - |
2728 | + private ViewGroup container; |
2729 | + |
2730 | // Model objects |
2731 | private Note note; |
2732 | private SpannableStringBuilder noteContent; |
2733 | - |
2734 | + |
2735 | // Logging info |
2736 | private static final String TAG = "ViewNote"; |
2737 | - |
2738 | + |
2739 | // UI feedback handler |
2740 | - private Handler syncMessageHandler = new SyncMessageHandler(this); |
2741 | + private final Handler syncMessageHandler = new SyncMessageHandler(this); |
2742 | + |
2743 | + private LocalStorage localStorage; |
2744 | + private ViewSwitcher viewSwitcher; |
2745 | + private GestureDetector gestureDetector; |
2746 | |
2747 | // TODO extract methods in here |
2748 | @Override |
2749 | protected void onCreate(Bundle savedInstanceState) { |
2750 | super.onCreate(savedInstanceState); |
2751 | - |
2752 | + |
2753 | setContentView(R.layout.note_view); |
2754 | - content = (TextView) findViewById(R.id.content); |
2755 | - content.setBackgroundColor(0xffffffff); |
2756 | - content.setTextColor(Color.DKGRAY); |
2757 | - content.setTextSize(18.0f); |
2758 | - title = (TextView) findViewById(R.id.title); |
2759 | - title.setBackgroundColor(0xffdddddd); |
2760 | - title.setTextColor(Color.DKGRAY); |
2761 | - title.setTextSize(18.0f); |
2762 | - |
2763 | + container = (ViewGroup) findViewById(R.id.container); |
2764 | + |
2765 | final Intent intent = getIntent(); |
2766 | - Uri uri = intent.getData(); |
2767 | - |
2768 | + handleUri(intent.getData()); |
2769 | + |
2770 | + localStorage = new LocalStorage(this); |
2771 | + viewSwitcher = new ViewSwitcher(container); |
2772 | + |
2773 | + gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { |
2774 | + |
2775 | + @Override |
2776 | + public boolean onDoubleTap(MotionEvent e) { |
2777 | + if (isInViewMode()) |
2778 | + switchToEditMode(); |
2779 | + else |
2780 | + switchToViewMode(); |
2781 | + return true; |
2782 | + } |
2783 | + }); |
2784 | + |
2785 | + viewNote(); |
2786 | + } |
2787 | + |
2788 | + @Override |
2789 | + public boolean dispatchTouchEvent(MotionEvent event) { |
2790 | + if (gestureDetector.onTouchEvent(event)) |
2791 | + return true; |
2792 | + return super.dispatchTouchEvent(event); |
2793 | + } |
2794 | + |
2795 | + private void handleUri(Uri uri) { |
2796 | if (uri != null) { |
2797 | - |
2798 | - // We were triggered by an Intent URI |
2799 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "ViewNote started: Intent-filter triggered."); |
2800 | + |
2801 | + // We were triggered by an Intent URI |
2802 | + if (Tomdroid.LOGGING_ENABLED) |
2803 | + Log.d(TAG, "ViewNote started: Intent-filter triggered."); |
2804 | |
2805 | // TODO validate the good action? |
2806 | // intent.getAction() |
2807 | - |
2808 | + |
2809 | // TODO verify that getNote is doing the proper validation |
2810 | note = NoteManager.getNote(this, uri); |
2811 | - |
2812 | - if(note != null) { |
2813 | - |
2814 | - noteContent = note.getNoteContent(noteContentHandler); |
2815 | - |
2816 | - //Log.i(TAG, "THE NOTE IS: " + note.getXmlContent().toString()); |
2817 | - |
2818 | - } else { |
2819 | - |
2820 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The note "+uri+" doesn't exist"); |
2821 | - |
2822 | + |
2823 | + if (note == null) { |
2824 | + |
2825 | + if (Tomdroid.LOGGING_ENABLED) |
2826 | + Log.d(TAG, "The note " + uri + " doesn't exist"); |
2827 | + |
2828 | // TODO put error string in a translatable resource |
2829 | new AlertDialog.Builder(this) |
2830 | .setMessage("The requested note could not be found. If you see this error " + |
2831 | @@ -119,10 +146,12 @@ |
2832 | }}) |
2833 | .show(); |
2834 | } |
2835 | + |
2836 | } else { |
2837 | - |
2838 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The Intent's data was null."); |
2839 | - |
2840 | + |
2841 | + if (Tomdroid.LOGGING_ENABLED) |
2842 | + Log.d(TAG, "The Intent's data was null."); |
2843 | + |
2844 | // TODO put error string in a translatable resource |
2845 | new AlertDialog.Builder(this) |
2846 | .setMessage("The requested note could not be found. If you see this error " + |
2847 | @@ -136,75 +165,164 @@ |
2848 | .show(); |
2849 | } |
2850 | } |
2851 | - |
2852 | + |
2853 | @Override |
2854 | - public void onResume(){ |
2855 | + public void onResume() { |
2856 | super.onResume(); |
2857 | SyncManager.setActivity(this); |
2858 | SyncManager.setHandler(this.syncMessageHandler); |
2859 | } |
2860 | |
2861 | - // TODO add a menu that switches the view to an EditText instead of TextView |
2862 | - // this will need some other quit mechanism as onKeyDown though.. (but the back key might do it) |
2863 | - |
2864 | - @Override |
2865 | - public boolean onKeyDown(int keyCode, KeyEvent event) { |
2866 | - // TODO Auto-generated method stub |
2867 | - super.onKeyDown(keyCode, event); |
2868 | - |
2869 | - finish(); |
2870 | - |
2871 | - return true; |
2872 | - } |
2873 | - |
2874 | - private void showNote() { |
2875 | - //setTitle(note.getTitle()); |
2876 | - |
2877 | + @Override |
2878 | + public void onPause() { |
2879 | + super.onPause(); |
2880 | + } |
2881 | + |
2882 | + @Override |
2883 | + public boolean onCreateOptionsMenu(Menu menu) { |
2884 | + |
2885 | + MenuInflater inflater = getMenuInflater(); |
2886 | + inflater.inflate(R.menu.view_note, menu); |
2887 | + menu.findItem(R.id.menuView).setVisible(false); |
2888 | + return true; |
2889 | + } |
2890 | + |
2891 | + @Override |
2892 | + public boolean onPrepareOptionsMenu(Menu menu) { |
2893 | + if (isInEditMode()) { |
2894 | + menu.findItem(R.id.menuEdit).setVisible(false); |
2895 | + menu.findItem(R.id.menuView).setVisible(true); |
2896 | + } else { |
2897 | + menu.findItem(R.id.menuEdit).setVisible(true); |
2898 | + menu.findItem(R.id.menuView).setVisible(false); |
2899 | + } |
2900 | + return true; |
2901 | + } |
2902 | + |
2903 | + @Override |
2904 | + public boolean onOptionsItemSelected(MenuItem item) { |
2905 | + switch (item.getItemId()) { |
2906 | + case R.id.menuDelete: |
2907 | + Toast.makeText(this, "deleting notes is not jet ipmlemented", Toast.LENGTH_SHORT) |
2908 | + .show(); |
2909 | + return true; |
2910 | + case R.id.menuView: |
2911 | + switchToViewMode(); |
2912 | + return true; |
2913 | + case R.id.menuEdit: |
2914 | + switchToEditMode(); |
2915 | + return true; |
2916 | + case R.id.menuPrefs: |
2917 | + startActivity(new Intent(this, PreferencesActivity.class)); |
2918 | + return true; |
2919 | + } |
2920 | + |
2921 | + return super.onOptionsItemSelected(item); |
2922 | + } |
2923 | + |
2924 | + private void saveEditedContent() { |
2925 | + TextView textView = (TextView) findViewById(R.id.editContent); |
2926 | + if (!textView.getText().toString().equals(note.getXmlContent())) { |
2927 | + note.changeXmlContent(textView.getText().toString()); |
2928 | + localStorage.insertNote(note); |
2929 | + if (Tomdroid.LOGGING_ENABLED) |
2930 | + Log.v(TAG, textView.getText().toString() + "\n----\n" + note.getXmlContent()); |
2931 | + |
2932 | + } |
2933 | + } |
2934 | + |
2935 | + private boolean isInEditMode() { |
2936 | + return viewSwitcher.isBacksideVisible(); |
2937 | + } |
2938 | + |
2939 | + private boolean isInViewMode() { |
2940 | + return viewSwitcher.isFrontsideVisible(); |
2941 | + } |
2942 | + |
2943 | + private void switchToEditMode() { |
2944 | + if (isInEditMode()) { |
2945 | + return; |
2946 | + } |
2947 | + viewSwitcher.swap(); |
2948 | + |
2949 | + editNote(); |
2950 | + } |
2951 | + |
2952 | + private void switchToViewMode() { |
2953 | + if (isInViewMode()) { |
2954 | + return; |
2955 | + } |
2956 | + saveEditedContent(); |
2957 | + viewSwitcher.swap(); |
2958 | + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |
2959 | + inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), |
2960 | + InputMethodManager.HIDE_NOT_ALWAYS); |
2961 | + viewNote(); |
2962 | + } |
2963 | + |
2964 | + private void viewNote() { |
2965 | + |
2966 | + setTitle(note.getTitle()); |
2967 | + noteContent = note.getNoteContent(NoteContentHandler); |
2968 | + } |
2969 | + |
2970 | + private void onContentBuilded() { |
2971 | // get rid of the title that is doubled in the note's content |
2972 | // using quote to escape potential regexp chars in pattern |
2973 | - Pattern removeTitle = Pattern.compile("^\\s*"+Pattern.quote(note.getTitle())+"\\n\\n"); |
2974 | + Pattern removeTitle = Pattern.compile("^\\s*" + Pattern.quote(note.getTitle()) + "\\n\\n"); |
2975 | Matcher m = removeTitle.matcher(noteContent); |
2976 | if (m.find()) { |
2977 | noteContent = noteContent.replace(0, m.end(), ""); |
2978 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "stripped the title from note-content"); |
2979 | + if (Tomdroid.LOGGING_ENABLED) |
2980 | + Log.d(TAG, "stripped the title from note-content"); |
2981 | } |
2982 | - |
2983 | + |
2984 | + TextView textView = (TextView) findViewById(R.id.viewContent); |
2985 | + |
2986 | // show the note (spannable makes the TextView able to output styled text) |
2987 | - content.setText(noteContent, TextView.BufferType.SPANNABLE); |
2988 | - title.setText((CharSequence) note.getTitle()); |
2989 | - |
2990 | - // add links to stuff that is understood by Android except phone numbers because it's too aggressive |
2991 | + textView.setText(noteContent, TextView.BufferType.SPANNABLE); |
2992 | + // add links to stuff that is understood by Android except phone numbers because it's too |
2993 | + // aggressive |
2994 | + |
2995 | // TODO this is SLOWWWW!!!! |
2996 | - Linkify.addLinks(content, Linkify.EMAIL_ADDRESSES|Linkify.WEB_URLS|Linkify.MAP_ADDRESSES); |
2997 | - |
2998 | + Linkify.addLinks(textView, Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS |
2999 | + | Linkify.MAP_ADDRESSES); |
3000 | + |
3001 | // Custom phone number linkifier (fixes lp:512204) |
3002 | - Linkify.addLinks(content, LinkifyPhone.PHONE_PATTERN, "tel:", LinkifyPhone.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter); |
3003 | - |
3004 | + Linkify.addLinks(textView, LinkifyPhone.PHONE_PATTERN, "tel:", |
3005 | + LinkifyPhone.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter); |
3006 | + |
3007 | // This will create a link every time a note title is found in the text. |
3008 | // The pattern contains a very dumb (title1)|(title2) escaped correctly |
3009 | - // Then we transform the url from the note name to the note id to avoid characters that mess up with the URI (ex: ?) |
3010 | - Linkify.addLinks(content, |
3011 | - buildNoteLinkifyPattern(), |
3012 | - Tomdroid.CONTENT_URI+"/", |
3013 | - null, |
3014 | - noteTitleTransformFilter); |
3015 | - } |
3016 | - |
3017 | - public void setTitle(CharSequence title){ |
3018 | + // Then we transform the url from the note name to the note id to avoid characters that mess |
3019 | + // up with the URI (ex: ?) |
3020 | + Linkify.addLinks(textView, buildNoteLinkifyPattern(), Tomdroid.CONTENT_URI + "/", null, |
3021 | + noteTitleTransformFilter); |
3022 | + } |
3023 | + |
3024 | + private void editNote() { |
3025 | + setTitle("Edit Mode"); |
3026 | + |
3027 | + TextView textView = (TextView) findViewById(R.id.editContent); |
3028 | + |
3029 | + textView.setText(note.getXmlContent()); |
3030 | + } |
3031 | + |
3032 | + @Override |
3033 | + public void setTitle(CharSequence title) { |
3034 | super.setTitle(title); |
3035 | // temporary setting title of actionbar until we have a better idea |
3036 | TextView titleView = (TextView) findViewById(R.id.title); |
3037 | titleView.setText(title); |
3038 | } |
3039 | - |
3040 | - private Handler noteContentHandler = new Handler() { |
3041 | + |
3042 | + private final Handler NoteContentHandler = new Handler() { |
3043 | |
3044 | @Override |
3045 | public void handleMessage(Message msg) { |
3046 | - |
3047 | //parsed ok - show |
3048 | if(msg.what == NoteContentBuilder.PARSE_OK) { |
3049 | - showNote(); |
3050 | + onContentBuilded(); |
3051 | |
3052 | //parsed not ok - error |
3053 | } else if(msg.what == NoteContentBuilder.PARSE_ERROR) { |
3054 | @@ -223,57 +341,64 @@ |
3055 | } |
3056 | } |
3057 | }; |
3058 | - |
3059 | + |
3060 | /** |
3061 | - * Builds a regular expression pattern that will match any of the note title currently in the collection. |
3062 | - * Useful for the Linkify to create the links to the notes. |
3063 | + * Builds a regular expression pattern that will match any of the note title currently in the |
3064 | + * collection. Useful for the Linkify to create the links to the notes. |
3065 | + * |
3066 | * @return regexp pattern |
3067 | */ |
3068 | - private Pattern buildNoteLinkifyPattern() { |
3069 | - |
3070 | + private Pattern buildNoteLinkifyPattern() { |
3071 | + |
3072 | StringBuilder sb = new StringBuilder(); |
3073 | Cursor cursor = NoteManager.getTitles(this); |
3074 | - |
3075 | - // cursor must not be null and must return more than 0 entry |
3076 | + |
3077 | + // cursor must not be null and must return more than 0 entry |
3078 | if (!(cursor == null || cursor.getCount() == 0)) { |
3079 | - |
3080 | + |
3081 | String title; |
3082 | - |
3083 | + |
3084 | cursor.moveToFirst(); |
3085 | - |
3086 | + |
3087 | do { |
3088 | title = cursor.getString(cursor.getColumnIndexOrThrow(Note.TITLE)); |
3089 | - |
3090 | - // Pattern.quote() here make sure that special characters in the note's title are properly escaped |
3091 | - sb.append("("+Pattern.quote(title)+")|"); |
3092 | - |
3093 | + |
3094 | + // Pattern.quote() here make sure that special characters in the note's title are |
3095 | + // properly escaped |
3096 | + sb.append("(" + Pattern.quote(title) + ")|"); |
3097 | + |
3098 | } while (cursor.moveToNext()); |
3099 | - |
3100 | + |
3101 | // get rid of the last | that is not needed (I know, its ugly.. better idea?) |
3102 | - String pt = sb.substring(0, sb.length()-1); |
3103 | + String pt = sb.substring(0, sb.length() - 1); |
3104 | |
3105 | // return a compiled match pattern |
3106 | return Pattern.compile(pt); |
3107 | - |
3108 | + |
3109 | } else { |
3110 | - |
3111 | + |
3112 | // TODO send an error to the user |
3113 | - if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes"); |
3114 | + if (Tomdroid.LOGGING_ENABLED) |
3115 | + Log.d(TAG, "Cursor returned null or 0 notes"); |
3116 | } |
3117 | - |
3118 | + |
3119 | return null; |
3120 | } |
3121 | - |
3122 | - // custom transform filter that takes the note's title part of the URI and translate it into the note id |
3123 | - // this was done to avoid problems with invalid characters in URI (ex: ? is the query separator but could be in a note title) |
3124 | - private TransformFilter noteTitleTransformFilter = new TransformFilter() { |
3125 | + |
3126 | + // custom transform filter that takes the note's title part of the URI and translate it into the |
3127 | + // note id |
3128 | + // this was done to avoid problems with invalid characters in URI (ex: ? is the query separator |
3129 | + // but could be in a note title) |
3130 | + private final TransformFilter noteTitleTransformFilter = new TransformFilter() { |
3131 | |
3132 | public String transformUrl(Matcher m, String str) { |
3133 | |
3134 | int id = NoteManager.getNoteId(ViewNote.this, str); |
3135 | - |
3136 | - // return something like content://org.tomdroid.notes/notes/3 |
3137 | - return Tomdroid.CONTENT_URI.toString()+"/"+id; |
3138 | - } |
3139 | + |
3140 | + // return something |
3141 | + // like |
3142 | + // content://org.tomdroid.notes/notes/3 |
3143 | + return Tomdroid.CONTENT_URI.toString() + "/" + id; |
3144 | + } |
3145 | }; |
3146 | } |
3147 | |
3148 | === added file 'src/org/tomdroid/ui/ViewSwitcher.java' |
3149 | --- src/org/tomdroid/ui/ViewSwitcher.java 1970-01-01 00:00:00 +0000 |
3150 | +++ src/org/tomdroid/ui/ViewSwitcher.java 2010-10-10 07:54:44 +0000 |
3151 | @@ -0,0 +1,152 @@ |
3152 | +/* |
3153 | + * Tomdroid |
3154 | + * Tomboy on Android |
3155 | + * http://www.launchpad.net/tomdroid |
3156 | + * |
3157 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3158 | + * |
3159 | + * This file is part of Tomdroid. |
3160 | + * |
3161 | + * Tomdroid is free software: you can redistribute it and/or modify |
3162 | + * it under the terms of the GNU General Public License as published by |
3163 | + * the Free Software Foundation, either version 3 of the License, or |
3164 | + * (at your option) any later version. |
3165 | + * |
3166 | + * Tomdroid is distributed in the hope that it will be useful, |
3167 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3168 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3169 | + * GNU General Public License for more details. |
3170 | + * |
3171 | + * You should have received a copy of the GNU General Public License |
3172 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3173 | + */ |
3174 | +package org.tomdroid.ui; |
3175 | + |
3176 | +import android.util.Log; |
3177 | +import android.view.View; |
3178 | +import android.view.ViewGroup; |
3179 | +import android.view.animation.AccelerateInterpolator; |
3180 | +import android.view.animation.Animation; |
3181 | +import android.view.animation.DecelerateInterpolator; |
3182 | + |
3183 | +/** |
3184 | + * This code was extracted from the Transition3D sample activity found in the Android ApiDemos. The |
3185 | + * animation is made of two smaller animations: the first half rotates the list by 90 degrees on the |
3186 | + * Y axis and the second half rotates the picture by 90 degrees on the Y axis. When the first half |
3187 | + * finishes, the list is made invisible and the picture is set visible. |
3188 | + */ |
3189 | +public class ViewSwitcher { |
3190 | + private static final String TAG = "ViewSwitcher"; |
3191 | + private ViewGroup mContainer; |
3192 | + private View mFrondside; |
3193 | + private View mBackside; |
3194 | + |
3195 | + private long mDuration = 300; |
3196 | + private float mDepthOfRotation = 300f; |
3197 | + |
3198 | + public ViewSwitcher(ViewGroup container) { |
3199 | + |
3200 | + mContainer = container; |
3201 | + mFrondside = container.getChildAt(0); |
3202 | + mBackside = container.getChildAt(1); |
3203 | + |
3204 | + // Since we are caching large views, we want to keep their cache |
3205 | + // between each animation |
3206 | + mContainer.setPersistentDrawingCache(ViewGroup.PERSISTENT_ANIMATION_CACHE); |
3207 | + |
3208 | + } |
3209 | + |
3210 | + public void setDuration(long duration) { |
3211 | + mDuration = duration; |
3212 | + } |
3213 | + |
3214 | + public void swap() { |
3215 | + float start, end; |
3216 | + |
3217 | + if (isFrontsideVisible()) { |
3218 | + Log.v(TAG, "turning to the backside!"); |
3219 | + start = 0; |
3220 | + end = 90; |
3221 | + } else { |
3222 | + Log.v(TAG, "turning to the frontside!"); |
3223 | + start = 180; |
3224 | + end = 90; |
3225 | + } |
3226 | + |
3227 | + Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, |
3228 | + mContainer.getWidth() / 2.0f, mContainer.getHeight() / 2.0f, mDepthOfRotation, true); |
3229 | + rotation.setDuration(mDuration / 2); |
3230 | + rotation.setFillAfter(true); |
3231 | + rotation.setInterpolator(new AccelerateInterpolator()); |
3232 | + rotation.setAnimationListener(new TurnAroundListener()); |
3233 | + |
3234 | + mContainer.startAnimation(rotation); |
3235 | + } |
3236 | + |
3237 | + public boolean isFrontsideVisible() { |
3238 | + return mFrondside.getVisibility() == View.VISIBLE; |
3239 | + } |
3240 | + |
3241 | + public boolean isBacksideVisible() { |
3242 | + return mBackside.getVisibility() == View.VISIBLE; |
3243 | + } |
3244 | + |
3245 | + /** |
3246 | + * Listen for the end of the first half of the animation. Then post a new action that |
3247 | + * effectively swaps the views when the container is rotated 90 degrees and thus invisible. |
3248 | + */ |
3249 | + private final class TurnAroundListener implements Animation.AnimationListener { |
3250 | + |
3251 | + public void onAnimationStart(Animation animation) { |
3252 | + } |
3253 | + |
3254 | + public void onAnimationEnd(Animation animation) { |
3255 | + mContainer.post(new SwapViews()); |
3256 | + } |
3257 | + |
3258 | + public void onAnimationRepeat(Animation animation) { |
3259 | + } |
3260 | + } |
3261 | + |
3262 | + /** |
3263 | + * Swapping the views and start the second half of the animation. |
3264 | + */ |
3265 | + private final class SwapViews implements Runnable { |
3266 | + |
3267 | + public void run() { |
3268 | + final float centerX = mContainer.getWidth() / 2.0f; |
3269 | + final float centerY = mContainer.getHeight() / 2.0f; |
3270 | + Rotate3dAnimation rotation; |
3271 | + |
3272 | + if (isFrontsideVisible()) { |
3273 | + mFrondside.setVisibility(View.GONE); |
3274 | + mBackside.setVisibility(View.VISIBLE); |
3275 | + unmirrorTheBackside(); |
3276 | + mBackside.requestFocus(); |
3277 | + |
3278 | + rotation = new Rotate3dAnimation(90, 180, centerX, centerY, mDepthOfRotation, false); |
3279 | + } else { |
3280 | + mBackside.setVisibility(View.GONE); |
3281 | + mBackside.clearAnimation(); // remove the mirroring |
3282 | + mFrondside.setVisibility(View.VISIBLE); |
3283 | + mFrondside.requestFocus(); |
3284 | + |
3285 | + rotation = new Rotate3dAnimation(90, 0, centerX, centerY, mDepthOfRotation, false); |
3286 | + } |
3287 | + |
3288 | + rotation.setDuration(mDuration / 2); |
3289 | + rotation.setFillAfter(true); |
3290 | + rotation.setInterpolator(new DecelerateInterpolator()); |
3291 | + |
3292 | + mContainer.startAnimation(rotation); |
3293 | + } |
3294 | + } |
3295 | + |
3296 | + private void unmirrorTheBackside() { |
3297 | + Rotate3dAnimation rotation = new Rotate3dAnimation(0, 180, mContainer.getWidth() / 2.0f, |
3298 | + mContainer.getHeight() / 2.0f, mDepthOfRotation, false); |
3299 | + rotation.setDuration(0); |
3300 | + rotation.setFillAfter(true); |
3301 | + mBackside.startAnimation(rotation); |
3302 | + } |
3303 | +} |
3304 | |
3305 | === modified file 'src/org/tomdroid/util/NoteListCursorAdapter.java' |
3306 | --- src/org/tomdroid/util/NoteListCursorAdapter.java 2010-10-09 19:45:06 +0000 |
3307 | +++ src/org/tomdroid/util/NoteListCursorAdapter.java 2010-10-10 07:54:44 +0000 |
3308 | @@ -4,6 +4,7 @@ |
3309 | * http://www.launchpad.net/tomdroid |
3310 | * |
3311 | * Copyright 2010, Matthew Stevenson <saturnreturn@gmail.com> |
3312 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3313 | * |
3314 | * This file is part of Tomdroid. |
3315 | * |
3316 | @@ -42,11 +43,11 @@ |
3317 | |
3318 | public class NoteListCursorAdapter extends SimpleCursorAdapter { |
3319 | |
3320 | - private int layout; |
3321 | - private int[] colors = new int[] { 0xFFFFFFFF, 0xFFEEEEEE }; |
3322 | + private final int layout; |
3323 | + private final int[] colors = new int[] { 0xFFFFFFFF, 0xFFEEEEEE }; |
3324 | |
3325 | - private DateFormat localeDateFormat; |
3326 | - private DateFormat localeTimeFormat; |
3327 | + private final DateFormat localeDateFormat; |
3328 | + private final DateFormat localeTimeFormat; |
3329 | |
3330 | public NoteListCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) { |
3331 | super(context, layout, c, from, to); |
3332 | @@ -98,7 +99,8 @@ |
3333 | Long lastModifiedMillis = lastModified.toMillis(false); |
3334 | Date lastModifiedDate = new Date(lastModifiedMillis); |
3335 | |
3336 | - String strModified = "Modified: "; |
3337 | + boolean isSynced = c.getInt(c.getColumnIndex(Note.IS_SYNCED)) != 0; |
3338 | + String strModified = (isSynced ? "" : "Locally ") + "Modified: "; |
3339 | //TODO this is very inefficient |
3340 | if (DateUtils.isToday(lastModifiedMillis)){ |
3341 | strModified += "Today, " + localeTimeFormat.format(lastModifiedDate); |
3342 | |
3343 | === modified file 'src/org/tomdroid/util/Preferences.java' |
3344 | --- src/org/tomdroid/util/Preferences.java 2010-09-26 19:57:31 +0000 |
3345 | +++ src/org/tomdroid/util/Preferences.java 2010-10-10 07:54:44 +0000 |
3346 | @@ -29,10 +29,10 @@ |
3347 | public class Preferences { |
3348 | |
3349 | public enum Key { |
3350 | - SYNC_SERVICE ("sync_service", "sdcard"), |
3351 | + SYNC_METHOD ("sync_method", "sdcard"), |
3352 | SYNC_SERVER_ROOT_API ("sync_server_root_api", ""), |
3353 | SYNC_SERVER_USER_API ("sync_server_user_api", ""), |
3354 | - SYNC_SERVER ("sync_server", "https://one.ubuntu.com/notes"), |
3355 | + SYNC_SERVER_URI ("sync_server_uri", "https://one.ubuntu.com/notes"), |
3356 | ACCESS_TOKEN ("access_token", ""), |
3357 | ACCESS_TOKEN_SECRET ("access_token_secret", ""), |
3358 | REQUEST_TOKEN ("request_token", ""), |
3359 | |
3360 | === added directory 'tests/org/tomdroid/sync' |
3361 | === added file 'tests/org/tomdroid/sync/TestLocalStorage.java' |
3362 | --- tests/org/tomdroid/sync/TestLocalStorage.java 1970-01-01 00:00:00 +0000 |
3363 | +++ tests/org/tomdroid/sync/TestLocalStorage.java 2010-10-10 07:54:44 +0000 |
3364 | @@ -0,0 +1,78 @@ |
3365 | +/* |
3366 | + * Tomdroid |
3367 | + * Tomboy on Android |
3368 | + * http://www.launchpad.net/tomdroid |
3369 | + * |
3370 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3371 | + * |
3372 | + * This file is part of Tomdroid. |
3373 | + * |
3374 | + * Tomdroid is free software: you can redistribute it and/or modify |
3375 | + * it under the terms of the GNU General Public License as published by |
3376 | + * the Free Software Foundation, either version 3 of the License, or |
3377 | + * (at your option) any later version. |
3378 | + * |
3379 | + * Tomdroid is distributed in the hope that it will be useful, |
3380 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3381 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3382 | + * GNU General Public License for more details. |
3383 | + * |
3384 | + * You should have received a copy of the GNU General Public License |
3385 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3386 | + */ |
3387 | +package org.tomdroid.sync; |
3388 | + |
3389 | +import org.tomdroid.Note; |
3390 | +import org.tomdroid.ui.Tomdroid; |
3391 | +import org.tomdroid.util.Preferences; |
3392 | + |
3393 | +import android.content.Intent; |
3394 | +import android.test.ActivityUnitTestCase; |
3395 | + |
3396 | +public class TestLocalStorage extends ActivityUnitTestCase<Tomdroid> { |
3397 | + |
3398 | + private LocalStorage localStorage; |
3399 | + |
3400 | + public TestLocalStorage() { |
3401 | + super(Tomdroid.class); |
3402 | + } |
3403 | + |
3404 | + @Override |
3405 | + public void setUp() throws Exception { |
3406 | + super.setUp(); |
3407 | + Preferences.init(getInstrumentation().getContext(), false); |
3408 | + |
3409 | + startActivity(new Intent(), null, null); |
3410 | + localStorage = new LocalStorage(getActivity()); |
3411 | + localStorage.resetDatabase(); |
3412 | + } |
3413 | + |
3414 | + @Override |
3415 | + public void tearDown() { |
3416 | + localStorage.resetDatabase(); |
3417 | + } |
3418 | + |
3419 | + protected LocalStorage getLocalStorage(){ |
3420 | + return localStorage; |
3421 | + } |
3422 | + |
3423 | + public void testOutOfSyncFlagStoringInContentProvider(){ |
3424 | + |
3425 | + Note note = new Note(); |
3426 | + note.setTitle("title"); |
3427 | + note.setXmlContent("content"); |
3428 | + note.isSynced(true); |
3429 | + assertTrue("default should be 'in sync'", note.isSynced()); |
3430 | + getLocalStorage().insertNote(note); |
3431 | + note = getLocalStorage().getNote(note.getGuid()); |
3432 | + assertTrue("should still be 'in sync'", note.isSynced()); |
3433 | + |
3434 | + note.changeXmlContent("modified content"); |
3435 | + assertFalse("should be 'out of sync'", note.isSynced()); |
3436 | + getLocalStorage().insertNote(note); |
3437 | + note = getLocalStorage().getNote(note.getGuid()); |
3438 | + assertFalse("locally stored note should still marked as 'out of sync with server'", note |
3439 | + .isSynced()); |
3440 | + |
3441 | + } |
3442 | +} |
3443 | \ No newline at end of file |
3444 | |
3445 | === added directory 'tests/org/tomdroid/sync/web' |
3446 | === added file 'tests/org/tomdroid/sync/web/MockSyncServer.java' |
3447 | --- tests/org/tomdroid/sync/web/MockSyncServer.java 1970-01-01 00:00:00 +0000 |
3448 | +++ tests/org/tomdroid/sync/web/MockSyncServer.java 2010-10-10 07:54:44 +0000 |
3449 | @@ -0,0 +1,167 @@ |
3450 | +/* |
3451 | + * Tomdroid |
3452 | + * Tomboy on Android |
3453 | + * http://www.launchpad.net/tomdroid |
3454 | + * |
3455 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3456 | + * |
3457 | + * This file is part of Tomdroid. |
3458 | + * |
3459 | + * Tomdroid is free software: you can redistribute it and/or modify |
3460 | + * it under the terms of the GNU General Public License as published by |
3461 | + * the Free Software Foundation, either version 3 of the License, or |
3462 | + * (at your option) any later version. |
3463 | + * |
3464 | + * Tomdroid is distributed in the hope that it will be useful, |
3465 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3466 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3467 | + * GNU General Public License for more details. |
3468 | + * |
3469 | + * You should have received a copy of the GNU General Public License |
3470 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3471 | + */ |
3472 | +package org.tomdroid.sync.web; |
3473 | + |
3474 | +import java.net.UnknownHostException; |
3475 | +import java.util.ArrayList; |
3476 | +import java.util.HashMap; |
3477 | +import java.util.NoSuchElementException; |
3478 | +import java.util.UUID; |
3479 | + |
3480 | +import org.json.JSONArray; |
3481 | +import org.json.JSONException; |
3482 | +import org.json.JSONObject; |
3483 | +import org.tomdroid.Note; |
3484 | + |
3485 | +public class MockSyncServer extends SyncServer { |
3486 | + |
3487 | + HashMap<UUID, Note> storedNotes = new HashMap<UUID, Note>(); |
3488 | + private ArrayList<Note> noteUpdates = new ArrayList<Note>(); |
3489 | + |
3490 | + TestDataManipulator testDataManipulator = new TestDataManipulator(); |
3491 | + private boolean isStoringLocked; |
3492 | + |
3493 | + public MockSyncServer() throws UnknownHostException, JSONException { |
3494 | + super(); |
3495 | + } |
3496 | + |
3497 | + @Override |
3498 | + protected JSONObject getMetadata() throws JSONException { |
3499 | + JSONObject mockedResponse = new JSONObject( |
3500 | + "{'user-name':'<in reality this is a http address>'," |
3501 | + + "'notes-ref':{'api-ref':'https://one.ubuntu.com/notes/api/1.0/op/'," |
3502 | + + "'href':'https://one.ubuntu.com/notes/'}," + "'current-sync-guid':'0'," |
3503 | + + "'last-name':'Mustermann','first-name':'Max','latest-sync-revision':0}"); |
3504 | + return mockedResponse; |
3505 | + } |
3506 | + |
3507 | + @Override |
3508 | + protected JSONObject getNoteUpdatesSince(long since) throws JSONException, UnknownHostException { |
3509 | + JSONArray notes = new JSONArray(); |
3510 | + for (int i = (int) since; i < noteUpdates.size(); i++) { |
3511 | + notes.put(noteUpdates.get(i).toJson()); |
3512 | + } |
3513 | + |
3514 | + JSONObject updates = new JSONObject(); |
3515 | + updates.put("notes", notes); |
3516 | + return updates; |
3517 | + } |
3518 | + |
3519 | + @Override |
3520 | + protected JSONObject getAllNotesWithoutContent() throws JSONException, UnknownHostException { |
3521 | + JSONArray notes = new JSONArray(); |
3522 | + for (Note note : storedNotes.values()) { |
3523 | + notes.put(note.toJsonWithoutContent()); |
3524 | + } |
3525 | + |
3526 | + JSONObject data = new JSONObject(); |
3527 | + data.put("notes", notes); |
3528 | + return data; |
3529 | + } |
3530 | + |
3531 | + @Override |
3532 | + public boolean createNewRevisionWith(ArrayList<Note> newAndUpdatedNotes) { |
3533 | + if (isStoringLocked) |
3534 | + return false; |
3535 | + |
3536 | + for (Note note : newAndUpdatedNotes) { |
3537 | + storedNotes.put(note.getGuid(), note); |
3538 | + noteUpdates.add(note.clone()); |
3539 | + } |
3540 | + return true; |
3541 | + } |
3542 | + |
3543 | + public void lockStoring() { |
3544 | + isStoringLocked = true; |
3545 | + } |
3546 | + |
3547 | + public void unlockStoring() { |
3548 | + isStoringLocked = false; |
3549 | + } |
3550 | + |
3551 | + class TestDataManipulator { |
3552 | + |
3553 | + private void onStoredDataChanged() { |
3554 | + syncVersionOnServer++; |
3555 | + } |
3556 | + |
3557 | + public Note createNewNote() { |
3558 | + onStoredDataChanged(); |
3559 | + Note note = new Note(); |
3560 | + note.setTitle("A Title"); |
3561 | + note.setGuid(UUID.randomUUID()); |
3562 | + note.changeXmlContent("Note content."); |
3563 | + note.setLastSyncRevision(syncVersionOnServer); |
3564 | + |
3565 | + storedNotes.put(note.getGuid(), note); |
3566 | + noteUpdates.add(note.clone()); |
3567 | + return note; |
3568 | + } |
3569 | + |
3570 | + public Note getNewestNote() { |
3571 | + Note newestNote = null; |
3572 | + for (Note note : storedNotes.values()) { |
3573 | + if (newestNote != null |
3574 | + && note.getLastChangeDate().toMillis(false) > newestNote |
3575 | + .getLastChangeDate().toMillis(false)) { |
3576 | + |
3577 | + } else { |
3578 | + newestNote = note; |
3579 | + } |
3580 | + } |
3581 | + return newestNote; |
3582 | + } |
3583 | + |
3584 | + public Note setTitleOfNewestNote(String title) { |
3585 | + onStoredDataChanged(); |
3586 | + |
3587 | + Note note = getNewestNote(); |
3588 | + note.setTitle(title); |
3589 | + note.setLastSyncRevision(syncVersionOnServer); |
3590 | + |
3591 | + noteUpdates.add(note.clone()); |
3592 | + return note; |
3593 | + } |
3594 | + |
3595 | + public Note setContentOfNewestNote(String content) { |
3596 | + onStoredDataChanged(); |
3597 | + Note note = getNewestNote(); |
3598 | + note.setLastSyncRevision(syncVersionOnServer); |
3599 | + note.changeXmlContent(content); |
3600 | + noteUpdates.add(note.clone()); |
3601 | + return note; |
3602 | + } |
3603 | + |
3604 | + public void deleteNote(UUID guid) { |
3605 | + storedNotes.remove(guid); |
3606 | + onStoredDataChanged(); |
3607 | + } |
3608 | + |
3609 | + public Note getNote(UUID guid) { |
3610 | + if (!storedNotes.containsKey(guid)) |
3611 | + throw new NoSuchElementException(); |
3612 | + |
3613 | + return storedNotes.get(guid); |
3614 | + } |
3615 | + } |
3616 | +} |
3617 | |
3618 | === added file 'tests/org/tomdroid/sync/web/MockedSyncServerTestCase.java' |
3619 | --- tests/org/tomdroid/sync/web/MockedSyncServerTestCase.java 1970-01-01 00:00:00 +0000 |
3620 | +++ tests/org/tomdroid/sync/web/MockedSyncServerTestCase.java 2010-10-10 07:54:44 +0000 |
3621 | @@ -0,0 +1,81 @@ |
3622 | +/* |
3623 | + * Tomdroid |
3624 | + * Tomboy on Android |
3625 | + * http://www.launchpad.net/tomdroid |
3626 | + * |
3627 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3628 | + * |
3629 | + * This file is part of Tomdroid. |
3630 | + * |
3631 | + * Tomdroid is free software: you can redistribute it and/or modify |
3632 | + * it under the terms of the GNU General Public License as published by |
3633 | + * the Free Software Foundation, either version 3 of the License, or |
3634 | + * (at your option) any later version. |
3635 | + * |
3636 | + * Tomdroid is distributed in the hope that it will be useful, |
3637 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3638 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3639 | + * GNU General Public License for more details. |
3640 | + * |
3641 | + * You should have received a copy of the GNU General Public License |
3642 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3643 | + */ |
3644 | +package org.tomdroid.sync.web; |
3645 | + |
3646 | +import org.json.JSONException; |
3647 | +import org.tomdroid.Note; |
3648 | +import org.tomdroid.sync.LocalStorage; |
3649 | +import org.tomdroid.ui.Tomdroid; |
3650 | +import org.tomdroid.util.Preferences; |
3651 | + |
3652 | +import android.content.Intent; |
3653 | +import android.os.Handler; |
3654 | +import android.test.ActivityUnitTestCase; |
3655 | + |
3656 | +public class MockedSyncServerTestCase extends ActivityUnitTestCase<Tomdroid> { |
3657 | + |
3658 | + private LocalStorage localStorage; |
3659 | + private MockSyncServer server; |
3660 | + private SnowySyncMethod syncMethod; |
3661 | + |
3662 | + public MockedSyncServerTestCase() { |
3663 | + super(Tomdroid.class); |
3664 | + } |
3665 | + |
3666 | + @Override |
3667 | + public void setUp() throws Exception { |
3668 | + super.setUp(); |
3669 | + Preferences.init(getInstrumentation().getContext(), false); |
3670 | + |
3671 | + startActivity(new Intent(), null, null); |
3672 | + syncMethod = new SnowySyncMethod(getActivity(), new Handler() { |
3673 | + }); |
3674 | + |
3675 | + localStorage = new LocalStorage(getActivity()); |
3676 | + localStorage.resetDatabase(); |
3677 | + |
3678 | + server = new MockSyncServer(); |
3679 | + } |
3680 | + |
3681 | + @Override |
3682 | + public void tearDown() { |
3683 | + localStorage.resetDatabase(); |
3684 | + } |
3685 | + |
3686 | + protected MockSyncServer getServer(){ |
3687 | + return server; |
3688 | + } |
3689 | + |
3690 | + protected SnowySyncMethod getSyncMethod(){ |
3691 | + return syncMethod; |
3692 | + } |
3693 | + |
3694 | + protected LocalStorage getLocalStorage(){ |
3695 | + return localStorage; |
3696 | + } |
3697 | + |
3698 | + protected void assertEquals(Note expected, Note actual) throws JSONException { |
3699 | + assertEquals("notes should be the same", expected.toJson().toString(), actual.toJson() |
3700 | + .toString()); |
3701 | + } |
3702 | +} |
3703 | |
3704 | === added file 'tests/org/tomdroid/sync/web/TestFetchingFromServer.java' |
3705 | --- tests/org/tomdroid/sync/web/TestFetchingFromServer.java 1970-01-01 00:00:00 +0000 |
3706 | +++ tests/org/tomdroid/sync/web/TestFetchingFromServer.java 2010-10-10 07:54:44 +0000 |
3707 | @@ -0,0 +1,119 @@ |
3708 | +/* |
3709 | + * Tomdroid |
3710 | + * Tomboy on Android |
3711 | + * http://www.launchpad.net/tomdroid |
3712 | + * |
3713 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3714 | + * |
3715 | + * This file is part of Tomdroid. |
3716 | + * |
3717 | + * Tomdroid is free software: you can redistribute it and/or modify |
3718 | + * it under the terms of the GNU General Public License as published by |
3719 | + * the Free Software Foundation, either version 3 of the License, or |
3720 | + * (at your option) any later version. |
3721 | + * |
3722 | + * Tomdroid is distributed in the hope that it will be useful, |
3723 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3724 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3725 | + * GNU General Public License for more details. |
3726 | + * |
3727 | + * You should have received a copy of the GNU General Public License |
3728 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3729 | + */ |
3730 | +package org.tomdroid.sync.web; |
3731 | + |
3732 | +import java.util.UUID; |
3733 | + |
3734 | +import org.tomdroid.Note; |
3735 | + |
3736 | +public class TestFetchingFromServer extends MockedSyncServerTestCase { |
3737 | + |
3738 | + @SuppressWarnings("unused") |
3739 | + private static final String TAG = "TestFetchingFromServer"; |
3740 | + |
3741 | + public void testInitialization() throws Exception { |
3742 | + assertEquals("Max", getServer().firstName); |
3743 | + assertEquals("Mustermann", getServer().lastName); |
3744 | + |
3745 | + assertTrue("should be in sync", getServer().isInSync(getLocalStorage())); |
3746 | + |
3747 | + getSyncMethod().syncWith(getServer()); |
3748 | + assertTrue("should be in sync", getServer().isInSync(getLocalStorage())); |
3749 | + } |
3750 | + |
3751 | + public void testLoadingNewNoteFromServer() throws Exception { |
3752 | + Note remoteNote = getServer().testDataManipulator.createNewNote(); |
3753 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3754 | + |
3755 | + getSyncMethod().syncWith(getServer()); |
3756 | + assertTrue("should be in sync again", getServer().isInSync(getLocalStorage())); |
3757 | + |
3758 | + assertEquals("note ids should be the same", getServer().getNoteIds(), getLocalStorage() |
3759 | + .getNoteGuids()); |
3760 | + Note localNote = getLocalStorage().getNote(remoteNote.getGuid()); |
3761 | + assertEquals(remoteNote, localNote); |
3762 | + } |
3763 | + |
3764 | + public void testChangingNoteTitleOnServer() throws Exception { |
3765 | + Note remoteNote = getServer().testDataManipulator.createNewNote(); |
3766 | + getSyncMethod().syncWith(getServer()); |
3767 | + |
3768 | + remoteNote = getServer().testDataManipulator.setTitleOfNewestNote("Another Title"); |
3769 | + assertEquals("server should still have one note", 1, getServer().storedNotes.size()); |
3770 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3771 | + |
3772 | + getSyncMethod().syncWith(getServer()); |
3773 | + assertTrue("should be in sync again", getServer().isInSync(getLocalStorage())); |
3774 | + |
3775 | + assertEquals(1, getLocalStorage().getNoteGuids().size()); |
3776 | + assertEquals("note ids should be the same", getServer().getNoteIds(), getLocalStorage() |
3777 | + .getNoteGuids()); |
3778 | + |
3779 | + Note localNote = getLocalStorage().getNote(remoteNote.getGuid()); |
3780 | + assertEquals(remoteNote, localNote); |
3781 | + assertEquals("local title should have changed", "Another Title", localNote.getTitle()); |
3782 | + } |
3783 | + |
3784 | + public void testChangingNoteContentOnServer() throws Exception { |
3785 | + Note remoteNote = getServer().testDataManipulator.createNewNote(); |
3786 | + getSyncMethod().syncWith(getServer()); |
3787 | + |
3788 | + remoteNote = getServer().testDataManipulator |
3789 | + .setContentOfNewestNote("some other note content"); |
3790 | + assertEquals("server should still have one note", 1, getServer().storedNotes.size()); |
3791 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3792 | + |
3793 | + getSyncMethod().syncWith(getServer()); |
3794 | + assertTrue("should be in sync again", getServer().isInSync(getLocalStorage())); |
3795 | + |
3796 | + assertEquals("note count", 1, getLocalStorage().getNoteGuids().size()); |
3797 | + assertEquals("note ids should be the same", getServer().getNoteIds(), getLocalStorage() |
3798 | + .getNoteGuids()); |
3799 | + |
3800 | + Note localNote = getLocalStorage().getNote(remoteNote.getGuid()); |
3801 | + assertEquals(remoteNote, localNote); |
3802 | + assertEquals("local content should have changed", "some other note content", localNote |
3803 | + .getXmlContent()); |
3804 | + } |
3805 | + |
3806 | + public void testDeletingNoteOnServer() throws Exception { |
3807 | + getServer().testDataManipulator.createNewNote(); |
3808 | + UUID deletedNoteGuid = getServer().testDataManipulator.createNewNote().getGuid(); |
3809 | + getServer().testDataManipulator.createNewNote(); |
3810 | + getSyncMethod().syncWith(getServer()); |
3811 | + assertEquals(3, getLocalStorage().getNoteGuids().size()); |
3812 | + |
3813 | + getServer().testDataManipulator.deleteNote(deletedNoteGuid); |
3814 | + assertEquals("server should have two notes", 2, getServer().storedNotes.size()); |
3815 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3816 | + |
3817 | + getSyncMethod().syncWith(getServer()); |
3818 | + assertTrue("should be in sync again", getServer().isInSync(getLocalStorage())); |
3819 | + |
3820 | + assertEquals(2, getLocalStorage().getNoteGuids().size()); |
3821 | + assertEquals("note ids should be the same", getServer().getNoteIds(), getLocalStorage() |
3822 | + .getNoteGuids()); |
3823 | + |
3824 | + assertNull("guid should be in use", getLocalStorage().getNote(deletedNoteGuid)); |
3825 | + } |
3826 | +} |
3827 | |
3828 | === added file 'tests/org/tomdroid/sync/web/TestUpdatingTheServer.java' |
3829 | --- tests/org/tomdroid/sync/web/TestUpdatingTheServer.java 1970-01-01 00:00:00 +0000 |
3830 | +++ tests/org/tomdroid/sync/web/TestUpdatingTheServer.java 2010-10-10 07:54:44 +0000 |
3831 | @@ -0,0 +1,126 @@ |
3832 | +/* |
3833 | + * Tomdroid |
3834 | + * Tomboy on Android |
3835 | + * http://www.launchpad.net/tomdroid |
3836 | + * |
3837 | + * Copyright 2010, Rodja Trappe <mail@rodja.net> |
3838 | + * |
3839 | + * This file is part of Tomdroid. |
3840 | + * |
3841 | + * Tomdroid is free software: you can redistribute it and/or modify |
3842 | + * it under the terms of the GNU General Public License as published by |
3843 | + * the Free Software Foundation, either version 3 of the License, or |
3844 | + * (at your option) any later version. |
3845 | + * |
3846 | + * Tomdroid is distributed in the hope that it will be useful, |
3847 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3848 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3849 | + * GNU General Public License for more details. |
3850 | + * |
3851 | + * You should have received a copy of the GNU General Public License |
3852 | + * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
3853 | + */ |
3854 | +package org.tomdroid.sync.web; |
3855 | + |
3856 | +import java.util.UUID; |
3857 | + |
3858 | +import org.tomdroid.Note; |
3859 | + |
3860 | +public class TestUpdatingTheServer extends MockedSyncServerTestCase { |
3861 | + |
3862 | + public void testChangingNoteOnClient() throws Exception { |
3863 | + UUID guid = getServer().testDataManipulator.createNewNote().getGuid(); |
3864 | + getSyncMethod().syncWith(getServer()); |
3865 | + |
3866 | + assertEquals("there should be no new/updated notes", 0, getLocalStorage() |
3867 | + .getNewAndUpdatedNotes().size()); |
3868 | + Note localNote = modifyLocalNote(guid); |
3869 | + assertEquals("should have a changed note", 1, getLocalStorage().getNewAndUpdatedNotes() |
3870 | + .size()); |
3871 | + assertEquals("should have the changed note", guid, getLocalStorage() |
3872 | + .getNewAndUpdatedNotes().get(0).getGuid()); |
3873 | + assertEquals("note should contain the update", localNote.getXmlContent(), getLocalStorage() |
3874 | + .getNewAndUpdatedNotes().get(0).getXmlContent()); |
3875 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3876 | + |
3877 | + getSyncMethod().syncWith(getServer()); |
3878 | + assertEquals("should have no changed notes anymore", 0, getLocalStorage() |
3879 | + .getNewAndUpdatedNotes().size()); |
3880 | + assertTrue("should be in sync", getServer().isInSync(getLocalStorage())); |
3881 | + |
3882 | + Note remoteNote = getServer().testDataManipulator.getNote(guid); |
3883 | + assertEquals("remote note should have correct timestamp", localNote.getLastChangeDate() |
3884 | + .format3339(false), remoteNote.getLastChangeDate().format3339(false)); |
3885 | + assertEquals("remote note should have been updated", localNote.getXmlContent(), remoteNote |
3886 | + .getXmlContent()); |
3887 | + assertEquals("locally stored note should bee the same as remote", getLocalStorage() |
3888 | + .getNote(guid).getXmlContent(), remoteNote.getXmlContent()); |
3889 | + |
3890 | + } |
3891 | + |
3892 | + public void testChangingDifferentNotesOnClientAndServer() throws Exception { |
3893 | + UUID guid = getServer().testDataManipulator.createNewNote().getGuid(); |
3894 | + getSyncMethod().syncWith(getServer()); |
3895 | + |
3896 | + modifyLocalNote(guid); |
3897 | + getServer().testDataManipulator.createNewNote(); |
3898 | + getSyncMethod().syncWith(getServer()); |
3899 | + |
3900 | + assertEquals(2, getLocalStorage().getNoteGuids().size()); |
3901 | + assertEquals(2, getServer().getNoteIds().size()); |
3902 | + assertTrue("should be in sync", getServer().isInSync(getLocalStorage())); |
3903 | + } |
3904 | + |
3905 | + public void testServerNotStoringLocalModificationWhileSyncing() throws Exception { |
3906 | + UUID guid = getServer().testDataManipulator.createNewNote().getGuid(); |
3907 | + getSyncMethod().syncWith(getServer()); |
3908 | + |
3909 | + modifyLocalNote(guid); |
3910 | + getServer().lockStoring(); |
3911 | + getSyncMethod().syncWith(getServer()); |
3912 | + getServer().unlockStoring(); |
3913 | + |
3914 | + assertEquals("should still have the changed note", 1, getLocalStorage() |
3915 | + .getNewAndUpdatedNotes().size()); |
3916 | + assertFalse("should be out of sync", getServer().isInSync(getLocalStorage())); |
3917 | + } |
3918 | + |
3919 | + public void testMergingLocalModificationWithModificationOnServer() throws Exception { |
3920 | + UUID guid = getServer().testDataManipulator.createNewNote().getGuid(); |
3921 | + getSyncMethod().syncWith(getServer()); |
3922 | + |
3923 | + modifyLocalNote(guid); |
3924 | + getServer().testDataManipulator.setContentOfNewestNote("server modification"); |
3925 | + getSyncMethod().syncWith(getServer()); |
3926 | + |
3927 | + assertEquals(1, getLocalStorage().getNoteGuids().size()); |
3928 | + assertEquals(1, getServer().getNoteIds().size()); |
3929 | + |
3930 | + assertTrue("should be in sync", getServer().isInSync(getLocalStorage())); |
3931 | + assertEquals("content should be merged", "server modification --merged-- Note content. Appended text for our test note!", getLocalStorage().getNote(guid) |
3932 | + .getXmlContent()); |
3933 | + assertEquals("content should be equal on client and server", getLocalStorage() |
3934 | + .getNote(guid).getXmlContent(), getServer().testDataManipulator.getNewestNote() |
3935 | + .getXmlContent()); |
3936 | + } |
3937 | + |
3938 | + |
3939 | + private Note modifyLocalNote(UUID guid) throws Exception { |
3940 | + Note note = getLocalStorage().getNote(guid); |
3941 | + long creationTime = note.getLastChangeDate().toMillis(false); |
3942 | + Thread.sleep(1100); |
3943 | + String newContent = note.getXmlContent() + " Appended text for our test note!"; |
3944 | + note.changeXmlContent(newContent); |
3945 | + |
3946 | + long modificationTime = note.getLastChangeDate().toMillis(false); |
3947 | + assertTrue("timestamp should have changed", creationTime < modificationTime); |
3948 | + |
3949 | + getLocalStorage().insertNote(note); |
3950 | + note = getLocalStorage().getNote(guid); |
3951 | + assertEquals("timestamp should have been updated", modificationTime, note |
3952 | + .getLastChangeDate().toMillis(false)); |
3953 | + assertEquals("local note should have been updated", newContent, note.getXmlContent()); |
3954 | + assertFalse("locally stored note should be marked as 'out of sync with server'", note.isSynced()); |
3955 | + return note; |
3956 | + } |
3957 | +} |
I would like to review this for inclusion in the next release which I would like to do soon.
I tried to merge with master but there are 14 conflicts... Most of them seems to be because of the "automated" tools reformatting xml and re-organizing imports and whitespace.. and I'm not saying it's your fault or whatever I'm just tired of tools messing with code because it's messing with source control...
Anyway, would you have the patience and the kindness to merge with lp:tomdroid so I can focus on the review bit? If your are busy or anything it's fine, I'll post-pone merging to the next cycle.