Merge lp:~tomdroid-dev/tomdroid/two-way-sync into lp:~tomdroid-maintainers/tomdroid/main

Proposed by Rodja
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
Reviewer Review Type Date Requested Status
Olivier Bilodeau Pending
Review via email: mp+38072@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Olivier Bilodeau (plaxx) wrote :

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.

Revision history for this message
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

Revision history for this message
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:drawable/)
 * New Note: ic_menu_compose
 * About: ic_menu_info_details
 * Reset DB: ic_menu_revert
 * Settings: ic_menu_preferences
 * View List: ic_menu_home or ic_menu_sort_by_size
 * Edit: ic_menu_edit
 * Delete: ic_menu_delete
 * Save: ic_menu_save
 * Discard: ic_menu_close_clear_cancel

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches