Merge lp:~plaxx/tomdroid/storage-redesign into lp:~benoit.garret/tomdroid/storage-redesign

Proposed by Olivier Bilodeau
Status: Merged
Merged at revision: 164
Proposed branch: lp:~plaxx/tomdroid/storage-redesign
Merge into: lp:~benoit.garret/tomdroid/storage-redesign
Diff against target: 873 lines (+217/-226)
9 files modified
src/org/tomdroid/Note.java (+6/-3)
src/org/tomdroid/NoteManager.java (+33/-20)
src/org/tomdroid/ui/Tomdroid.java (+18/-19)
src/org/tomdroid/ui/ViewNote.java (+55/-7)
src/org/tomdroid/util/AsyncNoteLoaderAndParser.java (+57/-15)
src/org/tomdroid/util/NoteContentBuilder.java (+12/-23)
src/org/tomdroid/util/XmlUtils.java (+0/-36)
src/org/tomdroid/xml/NoteContentHandler.java (+32/-40)
src/org/tomdroid/xml/NoteHandler.java (+4/-63)
To merge this branch: bzr merge lp:~plaxx/tomdroid/storage-redesign
Reviewer Review Type Date Requested Status
Benoit Garret Approve
Olivier Bilodeau (community) Approve
Review via email: mp+17110@code.launchpad.net

This proposal supersedes a proposal from 2009-10-05.

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

first of a series of small fixes to the storage-redesign branch

- its now using a thread to load a note
- loading from the web works again! even though no one cares ;)

_BUT_ the note's title appears at the top of the note

Anyway, more is to come, this was the critique of revno 148. I propose a merge because I think smaller is more manageable. If you don't have time for review, don't worry I'll stack additional changes to my branch later this week and I'll update the review.

Revision history for this message
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal

The main point of having an updateContent method was to make sure the xml content and the displayable content are always in sync. The caching thing was added as an afterthought and I agree it is not the best solution. What did you dislike in it: the fact that xml parsing happens in the Note object or the update mechanism itself? If it's the former, I'm not against delegating the xml parsing to another class, like NoteBuilder.

In fact, I don't like the idea of having two different methods to get and set what are in fact two representations of the same content. It would be far too easy to set one and not the other and wonder why the content that gets synced isn't the one that is displayed.

Here's what I'd also like to do (not related to my previous point):

1/ Remove the load note from web feature. It might seem backwards to remove features, but we both know that this one is quite useless and that no one will miss it. Moreover, it adds a fair bit of ugly code to handle a code path that will get removed sooner than later.

2/ Create a NoteManager to abstract the complexity of dealing with the content provider, with the following interfaces:
public Note getNote(UUID guid, Handler callback) // gets a note from the content provider
public Note getNote(URL url, Handler callback) // gets a note from a remote url
public void putNote(Note note) // puts a note in the content provider
public ListAdapter getListAdapter()
public ArrayList getTitles() // gets the titles of the notes present in the db, used in ViewNote.buildLinkifyPattern()
public TransformFilter getTitleToUrl() // returns the transform filter used for the note titles

Point 2 amounts to get rid of references to the content provider everywhere except in the NoteManager. This would clean the code a lot and avoid having connections to the content provider, which is far from optimal.

Your thoughts on this?

Revision history for this message
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal

I merged the revisions 158, 159 and 160.

Revision history for this message
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal

I pushed some updates in my branch:
Rev 161 and 162 respectively delete the load note from web feature and create a NoteManager.
Rev 163 is rev. 161 of your branch excepted that the NoteBuilder is used in Note instead of ViewNote.

Please let me know what is your opinion on these changes, I'm ready to rework them if they're not up to your standards.

Revision history for this message
Olivier Bilodeau (plaxx) wrote : Posted in a previous version of this proposal

On Sun, Oct 18, 2009 at 9:19 AM, Benoit Garret <
<email address hidden>> wrote:

> The main point of having an updateContent method was to make sure the xml
> content and the displayable content are always in sync. The caching thing
> was added as an afterthought and I agree it is not the best solution. What
> did you dislike in it: the fact that xml parsing happens in the Note object
> or the update mechanism itself? If it's the former, I'm not against
> delegating the xml parsing to another class, like NoteBuilder.
>
>
Yes, it was the former.

> In fact, I don't like the idea of having two different methods to get and
> set what are in fact two representations of the same content. It would be
> far too easy to set one and not the other and wonder why the content that
> gets synced isn't the one that is displayed.
>

Totally agree with you on this one.

>
> Here's what I'd also like to do (not related to my previous point):
>
> 1/ Remove the load note from web feature. It might seem backwards to remove
> features, but we both know that this one is quite useless and that no one
> will miss it. Moreover, it adds a fair bit of ugly code to handle a code
> path that will get removed sooner than later.
>

Yes, and I saw that you did remove it in a later message. Although, I would
have preferred a new branch or a patch (but I'm being an ass here). I'll
merge or cherry-pick, I agree with you: its time to get rid of that

>
> 2/ Create a NoteManager to abstract the complexity of dealing with the
> content provider, with the following interfaces:
> public Note getNote(UUID guid, Handler callback) // gets a note from the
> content provider
> public Note getNote(URL url, Handler callback) // gets a note from a remote
> url
> public void putNote(Note note) // puts a note in the content provider
> public ListAdapter getListAdapter()
> public ArrayList getTitles() // gets the titles of the notes present in the
> db, used in ViewNote.buildLinkifyPattern()
> public TransformFilter getTitleToUrl() // returns the transform filter used
> for the note titles
>
> Point 2 amounts to get rid of references to the content provider everywhere
> except in the NoteManager. This would clean the code a lot and avoid having
> connections to the content provider, which is far from optimal.
>

I don't mind, actually, I like it, its cleaner. Just being curious: how is
linking to the content provider inefficient?

Revision history for this message
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal

> > Point 2 amounts to get rid of references to the content provider everywhere
> > except in the NoteManager. This would clean the code a lot and avoid having
> > connections to the content provider, which is far from optimal.
> >
>
> I don't mind, actually, I like it, its cleaner. Just being curious: how is
> linking to the content provider inefficient?

Oops, I forgot the word multiple here. We won't get rid of the connection to the provider. I meant that having a single point for dealing with the content provider enables us to quickly check how much cursors are we using, etc.

Are you okay with setting your branch as merged?

Revision history for this message
Olivier Bilodeau (plaxx) wrote : Posted in a previous version of this proposal

I merged your branch already but I haven't pushed yet (need to fix
conflicts).

There are other minor items I want to change before merging your storage
branch in main. I'll work on these and once ready, I'll resubmit this merge
proposal.

--
Olivier

On Nov 13, 2009 12:39 AM, "Benoit Garret" <email address hidden>
wrote:

> > Point 2 amounts to get rid of references to the content provider
everywhere > > except in the No...
Oops, I forgot the word multiple here. We won't get rid of the connection to
the provider. I meant that having a single point for dealing with the
content provider enables us to quickly check how much cursors are we using,
etc.

Are you okay with setting your branch as merged?

-- https://code.launchpad.net/~plaxx/tomdroid/storage-redesign/+merge/12858You
are the owner of lp...

Revision history for this message
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal

> I merged your branch already but I haven't pushed yet (need to fix
> conflicts).
>
> There are other minor items I want to change before merging your storage
> branch in main. I'll work on these and once ready, I'll resubmit this merge
> proposal.

Awesome! Just let me know when you're ready.

Revision history for this message
Olivier Bilodeau (plaxx) wrote : Posted in a previous version of this proposal

I really like your NoteManager, it does remove a lot of Cursor complexity in the ui classes. Nice!

Also, the removal of "load from web" is very welcome.

I still have some other items I need to look at but its getting closer to merge.

Revision history for this message
Olivier Bilodeau (plaxx) wrote :

I would consider this ready for your storage-redesign branch.

If you think it's fine, accept it and propose a merge from your storage-redesign into main/.

review: Approve
lp:~plaxx/tomdroid/storage-redesign updated
169. By Olivier Bilodeau

Misc cleanup
- NoteManager's constructor no longer public (to prevent bypassing getInstance())
- Added comments
- Moved stuff around (static first)

Revision history for this message
Olivier Bilodeau (plaxx) wrote :

Just did a little commit with some minor cleanup.

Also, there's another thing with NoteManager. It needs initialization.. for a singleton class I don't think it's good. Especially when it's an activity class that is passed to it and that this activity is solely used to do managed cursor queries.

For the past half-hour I'm looking at other ways to do it to avoid side effects mentioned in the managedQuery page and I didn't find anything..

http://developer.android.com/reference/android/app/Activity.html#managedQuery%28android.net.Uri,%20java.lang.String[],%20java.lang.String,%20java.lang.String[],%20java.lang.String%29

Our manager cannot rely on a Cursor that can die at any minute because the activity associated to it (ui/Tomdroid) was the oldest memory eater out there and got killed.

Are you aware of any global mechanism that can allow any class to get it's way to query()? Otherwise, I'm afraid I think the best to avoid bugs in the long run is to either:
a) have an instance of NoteManager per activity (and send activity in the constructor) and drop the singleton code
b) all of NoteManager's methods should require an Activity in the parameter list so the proper cursor can be created

What do you prefer?

Revision history for this message
Benoit Garret (benoit.garret) wrote :

Hey, thanks for all the work. I won't have time today and tomorrow, I'll take a look at this on Sunday. Feel free to change anything you want until then. In the meantime, have a good weekend!

lp:~plaxx/tomdroid/storage-redesign updated
170. By Olivier Bilodeau

Removed the init phase of the NoteManager singleton to avoid problems with managed cursors.
Now every call to NoteManager provides an activity reference to use the cursor in the current active activity's context.

Revision history for this message
Benoit Garret (benoit.garret) wrote :

> Also, there's another thing with NoteManager. It needs initialization.. for a
> singleton class I don't think it's good. Especially when it's an activity
> class that is passed to it and that this activity is solely used to do managed
> cursor queries.
>
> For the past half-hour I'm looking at other ways to do it to avoid side
> effects mentioned in the managedQuery page and I didn't find anything..
>
> http://developer.android.com/reference/android/app/Activity.html#managedQuery%
> 28android.net.Uri,%20java.lang.String[],%20java.lang.String,%20java.lang.Strin
> g[],%20java.lang.String%29
>
> Our manager cannot rely on a Cursor that can die at any minute because the
> activity associated to it (ui/Tomdroid) was the oldest memory eater out there
> and got killed.
>
> Are you aware of any global mechanism that can allow any class to get it's way
> to query()? Otherwise, I'm afraid I think the best to avoid bugs in the long
> run is to either:
> a) have an instance of NoteManager per activity (and send activity in the
> constructor) and drop the singleton code
> b) all of NoteManager's methods should require an Activity in the parameter
> list so the proper cursor can be created
>
> What do you prefer?

It's perfectly possible to use query directly but the cursor won't be managed (and I guess we don't really want that).

I'd say solution b is the way to go, adding a parameter is not that cumbersome and we keep the advantages of the singleton.

Still reviewing the rest...

Revision history for this message
Benoit Garret (benoit.garret) wrote :

I'm done, everything you made seems sensible. I found a few outdated comments and useless methods but I made a commit in my branch. I also made the NoteManager methods static because they became stateless when you added the Activity as a parameter.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/org/tomdroid/Note.java'
2--- src/org/tomdroid/Note.java 2009-10-31 15:07:30 +0000
3+++ src/org/tomdroid/Note.java 2010-01-16 21:03:11 +0000
4@@ -27,7 +27,7 @@
5 import org.joda.time.DateTime;
6 import org.joda.time.format.DateTimeFormatter;
7 import org.joda.time.format.ISODateTimeFormat;
8-import org.tomdroid.util.NoteBuilder;
9+import org.tomdroid.util.NoteContentBuilder;
10
11 import android.os.Handler;
12 import android.text.SpannableStringBuilder;
13@@ -114,8 +114,11 @@
14 this.guid = UUID.fromString(guid);
15 }
16
17+ // TODO: should this handler passed around evolve into an observer pattern?
18 public SpannableStringBuilder getNoteContent(Handler handler) {
19- noteContent = new NoteBuilder().setCaller(handler).setInputSource(xmlContent).build();
20+
21+ // TODO not sure this is the right place to do this
22+ noteContent = new NoteContentBuilder().setCaller(handler).setInputSource(xmlContent).build();
23 return noteContent;
24 }
25
26@@ -126,7 +129,7 @@
27 public void setXmlContent(String xmlContent) {
28 this.xmlContent = xmlContent;
29 }
30-
31+
32 @Override
33 public String toString() {
34 // format date time according to XML standard
35
36=== modified file 'src/org/tomdroid/NoteManager.java'
37--- src/org/tomdroid/NoteManager.java 2009-10-25 17:00:11 +0000
38+++ src/org/tomdroid/NoteManager.java 2010-01-16 21:03:11 +0000
39@@ -1,3 +1,25 @@
40+/*
41+ * Tomdroid
42+ * Tomboy on Android
43+ * http://www.launchpad.net/tomdroid
44+ *
45+ * Copyright 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
46+ *
47+ * This file is part of Tomdroid.
48+ *
49+ * Tomdroid is free software: you can redistribute it and/or modify
50+ * it under the terms of the GNU General Public License as published by
51+ * the Free Software Foundation, either version 3 of the License, or
52+ * (at your option) any later version.
53+ *
54+ * Tomdroid is distributed in the hope that it will be useful,
55+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
56+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
57+ * GNU General Public License for more details.
58+ *
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 package org.tomdroid;
63
64 import org.tomdroid.ui.Tomdroid;
65@@ -23,9 +45,12 @@
66 private static final String TAG = "NoteManager";
67
68 private static NoteManager instance = null;
69- // the main activity this manager is linked to
70- private static Activity activity = null;
71+
72+ // instance properties
73+ private Cursor notesCursor;
74
75+ // singleton pattern
76+ // TODO verify this singleton with current java best practices
77 public static NoteManager getInstance()
78 {
79 if(instance == null) {
80@@ -39,22 +64,10 @@
81 return instance;
82 }
83
84- // instance properties
85- private Cursor notesCursor;
86-
87- public NoteManager() throws Exception
88- {
89- if(activity == null)
90- throw new Exception("init() has not been called.");
91- }
92-
93- public static void init(Activity a)
94- {
95- activity = a;
96- }
97+ private NoteManager() {}
98
99 // gets a note from the content provider
100- public Note getNote(Uri uri) {
101+ public Note getNote(Activity activity, Uri uri) {
102
103 Note note = null;
104
105@@ -77,7 +90,7 @@
106 }
107
108 // puts a note in the content provider
109- public void putNote(Note note) {
110+ public void putNote(Activity activity, Note note) {
111
112 // verify if the note is already in the content provider
113
114@@ -120,7 +133,7 @@
115 }
116 }
117
118- public ListAdapter getListAdapter() {
119+ public ListAdapter getListAdapter(Activity activity) {
120
121 // get a cursor representing all notes from the NoteProvider
122 Uri notes = Tomdroid.CONTENT_URI;
123@@ -133,13 +146,13 @@
124 }
125
126 // gets the titles of the notes present in the db, used in ViewNote.buildLinkifyPattern()
127- public Cursor getTitles() {
128+ public Cursor getTitles(Activity activity) {
129
130 // get a cursor containing the notes titles
131 return activity.managedQuery(Tomdroid.CONTENT_URI, TITLE_PROJECTION, null, null, null);
132 }
133
134- public int getNoteId(String title) {
135+ public int getNoteId(Activity activity, String title) {
136
137 int id = 0;
138
139
140=== modified file 'src/org/tomdroid/ui/Tomdroid.java'
141--- src/org/tomdroid/ui/Tomdroid.java 2009-10-25 17:00:11 +0000
142+++ src/org/tomdroid/ui/Tomdroid.java 2010-01-16 21:03:11 +0000
143@@ -3,7 +3,7 @@
144 * Tomboy on Android
145 * http://www.launchpad.net/tomdroid
146 *
147- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
148+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
149 *
150 * This file is part of Tomdroid.
151 *
152@@ -61,7 +61,7 @@
153 // TODO hardcoded for now
154 public static final String NOTES_PATH = "/sdcard/tomdroid/";
155 // Logging should be disabled for release builds
156- public static final boolean LOGGING_ENABLED = false;
157+ public static final boolean LOGGING_ENABLED = true;
158
159 // Logging info
160 private static final String TAG = "Tomdroid";
161@@ -82,7 +82,6 @@
162 public void onCreate(Bundle savedInstanceState) {
163 super.onCreate(savedInstanceState);
164
165- NoteManager.init(this);
166 setContentView(R.layout.main);
167
168 // did we already show the warning and got destroyed by android's activity killer?
169@@ -101,7 +100,7 @@
170 .show();
171 }
172
173- adapter = NoteManager.getInstance().getListAdapter();
174+ adapter = NoteManager.getInstance().getListAdapter(this);
175 setListAdapter(adapter);
176
177 // set the view shown when the list is empty
178@@ -122,20 +121,20 @@
179 public boolean onOptionsItemSelected(MenuItem item) {
180 switch (item.getItemId()) {
181 case R.id.menuSyncWithSD:
182-
183- // start loading local notes
184- if (LOGGING_ENABLED) Log.v(TAG, "Loading local notes");
185-
186- try {
187- File notesRoot = new File(Tomdroid.NOTES_PATH);
188-
189- if (!notesRoot.exists()) {
190- throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH);
191- }
192-
193- AsyncNoteLoaderAndParser asyncLoader = new AsyncNoteLoaderAndParser(notesRoot);
194+
195+ // start loading local notes
196+ if (LOGGING_ENABLED) Log.v(TAG, "Loading local notes");
197+
198+ try {
199+ File notesRoot = new File(Tomdroid.NOTES_PATH);
200+
201+ if (!notesRoot.exists()) {
202+ throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH);
203+ }
204+
205+ AsyncNoteLoaderAndParser asyncLoader = new AsyncNoteLoaderAndParser(this, notesRoot);
206 asyncLoader.readAndParseNotes();
207-
208+
209 } catch (FileNotFoundException e) {
210 //TODO put strings in an external resource
211 listEmptyView.setText(R.string.strListEmptyNoNotes);
212@@ -149,8 +148,8 @@
213 .show();
214 e.printStackTrace();
215 }
216-
217- return true;
218+
219+ return true;
220
221 case R.id.menuAbout:
222 showAboutDialog();
223
224=== modified file 'src/org/tomdroid/ui/ViewNote.java'
225--- src/org/tomdroid/ui/ViewNote.java 2009-10-31 15:07:30 +0000
226+++ src/org/tomdroid/ui/ViewNote.java 2010-01-16 21:03:11 +0000
227@@ -3,7 +3,7 @@
228 * Tomboy on Android
229 * http://www.launchpad.net/tomdroid
230 *
231- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
232+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
233 *
234 * This file is part of Tomdroid.
235 *
236@@ -28,10 +28,13 @@
237 import org.tomdroid.Note;
238 import org.tomdroid.NoteManager;
239 import org.tomdroid.R;
240-import org.tomdroid.util.NoteBuilder;
241+import org.tomdroid.util.NoteContentBuilder;
242
243 import android.app.Activity;
244+import android.app.AlertDialog;
245+import android.content.DialogInterface;
246 import android.content.Intent;
247+import android.content.DialogInterface.OnClickListener;
248 import android.database.Cursor;
249 import android.net.Uri;
250 import android.os.Bundle;
251@@ -75,7 +78,8 @@
252 // TODO validate the good action?
253 // intent.getAction()
254
255- note = NoteManager.getInstance().getNote(uri);
256+ // TODO verify that getNote is doing the proper validation
257+ note = NoteManager.getInstance().getNote(this, uri);
258
259 if(note != null) {
260
261@@ -85,7 +89,34 @@
262
263 // TODO send an error to the user
264 if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The note "+uri+" doesn't exist");
265+
266+ // TODO put error string in a translatable resource
267+ new AlertDialog.Builder(this)
268+ .setMessage("The requested note could not be found. If you see this error by " +
269+ "mistake and you are able to replicate it, please file a bug!")
270+ .setTitle("Error")
271+ .setNeutralButton("Ok", new OnClickListener() {
272+ public void onClick(DialogInterface dialog, int which) {
273+ dialog.dismiss();
274+ finish();
275+ }})
276+ .show();
277 }
278+ } else {
279+
280+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The Intent's data was null.");
281+
282+ // TODO put error string in a translatable resource
283+ new AlertDialog.Builder(this)
284+ .setMessage("The requested note could not be found. If you see this error by " +
285+ "mistake and you are able to replicate it, please file a bug!")
286+ .setTitle("Error")
287+ .setNeutralButton("Ok", new OnClickListener() {
288+ public void onClick(DialogInterface dialog, int which) {
289+ dialog.dismiss();
290+ finish();
291+ }})
292+ .show();
293 }
294 }
295
296@@ -126,8 +157,25 @@
297 @Override
298 public void handleMessage(Message msg) {
299
300- if(msg.what == NoteBuilder.PARSE_OK)
301+ //parsed ok - show
302+ if(msg.what == NoteContentBuilder.PARSE_OK) {
303 showNote();
304+
305+ //parsed not ok - error
306+ } else if(msg.what == NoteContentBuilder.PARSE_ERROR) {
307+
308+ // TODO put this String in a translatable resource
309+ new AlertDialog.Builder(ViewNote.this)
310+ .setMessage("The requested note could not be parsed. If you see this error by " +
311+ "mistake and you are able to replicate it, please file a bug!")
312+ .setTitle("Error")
313+ .setNeutralButton("Ok", new OnClickListener() {
314+ public void onClick(DialogInterface dialog, int which) {
315+ dialog.dismiss();
316+ finish();
317+ }})
318+ .show();
319+ }
320 }
321 };
322
323@@ -139,12 +187,13 @@
324 public Pattern buildNoteLinkifyPattern() {
325
326 StringBuilder sb = new StringBuilder();
327- Cursor cursor = NoteManager.getInstance().getTitles();
328+ Cursor cursor = NoteManager.getInstance().getTitles(this);
329
330 // cursor must not be null and must return more than 0 entry
331 if (!(cursor == null || cursor.getCount() == 0)) {
332
333 String title;
334+
335 cursor.moveToFirst();
336
337 do {
338@@ -176,8 +225,7 @@
339
340 public String transformUrl(Matcher m, String str) {
341
342- // FIXME if this activity is called from another app and Tomdroid was never launched, getting here will probably make it crash
343- int id = NoteManager.getInstance().getNoteId(str);
344+ int id = NoteManager.getInstance().getNoteId(ViewNote.this, str);
345
346 // return something like content://org.tomdroid.notes/notes/3
347 return Tomdroid.CONTENT_URI.toString()+"/"+id;
348
349=== modified file 'src/org/tomdroid/util/AsyncNoteLoaderAndParser.java'
350--- src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 2009-10-25 17:00:11 +0000
351+++ src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 2010-01-16 21:03:11 +0000
352@@ -3,7 +3,7 @@
353 * Tomboy on Android
354 * http://www.launchpad.net/tomdroid
355 *
356- * Copyright 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
357+ * Copyright 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
358 *
359 * This file is part of Tomdroid.
360 *
361@@ -25,36 +25,48 @@
362 import java.io.BufferedReader;
363 import java.io.File;
364 import java.io.FileInputStream;
365+import java.io.FileNotFoundException;
366 import java.io.FilenameFilter;
367 import java.io.IOException;
368 import java.io.InputStreamReader;
369+import java.io.Reader;
370 import java.util.concurrent.ExecutorService;
371 import java.util.concurrent.Executors;
372+import java.util.regex.Matcher;
373+import java.util.regex.Pattern;
374
375 import javax.xml.parsers.ParserConfigurationException;
376 import javax.xml.parsers.SAXParser;
377 import javax.xml.parsers.SAXParserFactory;
378
379+import org.xml.sax.InputSource;
380+import org.xml.sax.SAXException;
381+import org.xml.sax.XMLReader;
382+
383 import org.tomdroid.Note;
384 import org.tomdroid.NoteManager;
385 import org.tomdroid.ui.Tomdroid;
386 import org.tomdroid.xml.NoteHandler;
387-import org.xml.sax.InputSource;
388-import org.xml.sax.SAXException;
389-import org.xml.sax.XMLReader;
390
391+import android.app.Activity;
392 import android.util.Log;
393
394 public class AsyncNoteLoaderAndParser {
395 private final ExecutorService pool;
396 private final static int poolSize = 1;
397+ private Activity activity;
398 private File path;
399
400+ // regexp for <note-content..>...</note-content>
401+ private static Pattern note_content = Pattern.compile(".*(<note-content.*>.*<\\/note-content>).*", Pattern.CASE_INSENSITIVE+Pattern.DOTALL);
402+
403 // logging related
404 private final static String TAG = "AsyncNoteLoaderAndParser";
405
406- public AsyncNoteLoaderAndParser(File path) {
407+ public AsyncNoteLoaderAndParser(Activity a, File path) {
408+ this.activity = a;
409 this.path = path;
410+
411 pool = Executors.newFixedThreadPool(poolSize);
412 }
413
414@@ -86,6 +98,7 @@
415 // the note to be loaded and parsed
416 private Note note = new Note();
417 private File file;
418+ final char[] buffer = new char[0x10000];
419
420 public Worker(File f) {
421 file = f;
422@@ -96,7 +109,7 @@
423 note.setFileName(file.getAbsolutePath());
424 // the note guid is not stored in the xml but in the filename
425 note.setGuid(file.getName().replace(".note", ""));
426-
427+
428 try {
429 // Parsing
430 // XML
431@@ -111,13 +124,12 @@
432 NoteHandler xmlHandler = new NoteHandler(note);
433 xr.setContentHandler(xmlHandler);
434
435-
436- // Create the proper input source based on if its a local note or a web note
437- FileInputStream fin = new FileInputStream(file);
438- BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192);
439- InputSource is = new InputSource(in);
440-
441- if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note");
442+ // Create the proper input source
443+ FileInputStream fin = new FileInputStream(file);
444+ BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192);
445+ InputSource is = new InputSource(in);
446+
447+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "parsing note");
448 xr.parse(is);
449
450 // TODO wrap and throw a new exception here
451@@ -128,8 +140,38 @@
452 } catch (IOException e) {
453 e.printStackTrace();
454 }
455-
456- NoteManager.getInstance().putNote(note);
457+
458+ // extract the <note-content..>...</note-content>
459+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "retrieving what is inside of note-content");
460+
461+ // FIXME here we are re-reading the whole note just to grab note-content out, there is probably a best way to do this
462+ StringBuilder out = new StringBuilder();
463+ try {
464+ int read;
465+ Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
466+ //Reader reader = new InputStreamReader(fin, "UTF-8");
467+ do {
468+ read = reader.read(buffer, 0, buffer.length);
469+ if (read>0) {
470+ out.append(buffer, 0, read);
471+ }
472+ }
473+ while (read>=0);
474+
475+ Matcher m = note_content.matcher(out.toString());
476+ if (m.find()) {
477+ note.setXmlContent(m.group());
478+ } else {
479+ if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to grab the note-content out of a note");
480+ }
481+
482+ } catch (IOException e) {
483+ // TODO handle properly
484+ e.printStackTrace();
485+ if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to read the note");
486+ }
487+
488+ NoteManager.getInstance().putNote(AsyncNoteLoaderAndParser.this.activity, note);
489 }
490 }
491 }
492
493=== renamed file 'src/org/tomdroid/util/NoteBuilder.java' => 'src/org/tomdroid/util/NoteContentBuilder.java'
494--- src/org/tomdroid/util/NoteBuilder.java 2009-10-31 15:07:30 +0000
495+++ src/org/tomdroid/util/NoteContentBuilder.java 2010-01-16 21:03:11 +0000
496@@ -3,7 +3,7 @@
497 * Tomboy on Android
498 * http://www.launchpad.net/tomdroid
499 *
500- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
501+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
502 *
503 * This file is part of Tomdroid.
504 *
505@@ -30,14 +30,13 @@
506 import org.tomdroid.ui.Tomdroid;
507 import org.tomdroid.xml.NoteContentHandler;
508 import org.xml.sax.InputSource;
509-import org.xml.sax.XMLReader;
510
511 import android.os.Handler;
512 import android.os.Message;
513 import android.text.SpannableStringBuilder;
514 import android.util.Log;
515
516-public class NoteBuilder implements Runnable {
517+public class NoteContentBuilder implements Runnable {
518
519 public static final int PARSE_OK = 0;
520 public static final int PARSE_ERROR = 1;
521@@ -54,24 +53,17 @@
522 private Thread runner;
523 private Handler parentHandler;
524
525- public NoteBuilder () {}
526+ public NoteContentBuilder () {}
527
528- public NoteBuilder setCaller(Handler parent) {
529+ public NoteContentBuilder setCaller(Handler parent) {
530
531 parentHandler = parent;
532 return this;
533 }
534
535- public NoteBuilder setInputSource(String nc) {
536+ public NoteContentBuilder setInputSource(String nc) {
537
538- //FIXME: I would pay a beer to get rid of that ugliness; I can't believe we cannot parse a partial XML tree using SAX
539- // Create a valid xml document
540- String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
541- xml += "<note-content xmlns:link=\"http://beatniksoftware.com/tomboy/link\" xmlns:size=\"http://beatniksoftware.com/tomboy/size\" xmlns=\"http://beatniksoftware.com/tomboy\">";
542- xml += nc;
543- xml += "</note-content>";
544-
545- noteContentIs = new InputSource(new StringReader(xml));
546+ noteContentIs = new InputSource(new StringReader(nc));
547 return this;
548 }
549
550@@ -91,17 +83,14 @@
551 // XML
552 // Get a SAXParser from the SAXPArserFactory
553 SAXParserFactory spf = SAXParserFactory.newInstance();
554+
555+ // trashing the namespaces but keep prefixes (since we don't have the xml header)
556+ spf.setFeature("http://xml.org/sax/features/namespaces", false);
557+ spf.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
558 SAXParser sp = spf.newSAXParser();
559-
560- // Get the XMLReader of the SAXParser we created
561- XMLReader xr = sp.getXMLReader();
562-
563- // Create a new ContentHandler, send it this note to fill and apply it to the XML-Reader
564- NoteContentHandler xmlHandler = new NoteContentHandler(noteContent);
565- xr.setContentHandler(xmlHandler);
566-
567+
568 if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note");
569- xr.parse(noteContentIs);
570+ sp.parse(noteContentIs, new NoteContentHandler(noteContent));
571 } catch (Exception e) {
572 e.printStackTrace();
573 // TODO handle error in a more granular way
574
575=== removed file 'src/org/tomdroid/util/XmlUtils.java'
576--- src/org/tomdroid/util/XmlUtils.java 2009-09-01 14:27:46 +0000
577+++ src/org/tomdroid/util/XmlUtils.java 1970-01-01 00:00:00 +0000
578@@ -1,36 +0,0 @@
579-package org.tomdroid.util;
580-
581-public class XmlUtils {
582-
583- /**
584- * Useful to replace the characters forbidden in xml by their escaped counterparts
585- * Ex: &amp; -> &amp;amp;
586- *
587- * @param input the string to escape
588- * @return the escaped string
589- */
590- public static String escape(String input) {
591- return input
592- .replace("&", "&amp;")
593- .replace("<", "&lt;")
594- .replace(">", "&gt;")
595- .replace("\"", "&quot;")
596- .replace("\'", "&apos;");
597- }
598-
599- /**
600- * Useful to replace the escaped characters their unescaped counterparts
601- * Ex: &amp;amp; -> &amp;
602- *
603- * @param input the string to unescape
604- * @return the unescaped string
605- */
606- public static String unescape(String input) {
607- return input
608- .replace("&amp;", "&")
609- .replace("&lt;", "<")
610- .replace("&gt;", ">")
611- .replace("&quot;", "\"")
612- .replace("&apos;", "\'");
613- }
614-}
615
616=== modified file 'src/org/tomdroid/xml/NoteContentHandler.java'
617--- src/org/tomdroid/xml/NoteContentHandler.java 2009-09-01 14:27:46 +0000
618+++ src/org/tomdroid/xml/NoteContentHandler.java 2010-01-16 21:03:11 +0000
619@@ -3,7 +3,7 @@
620 * Tomboy on Android
621 * http://www.launchpad.net/tomdroid
622 *
623- * Copyright 2008 Olivier Bilodeau <olivier@bottomlesspit.org>
624+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
625 *
626 * This file is part of Tomdroid.
627 *
628@@ -64,11 +64,9 @@
629 private final static String STRIKETHROUGH = "strikethrough";
630 private final static String HIGHLIGHT = "highlight";
631 private final static String MONOSPACE = "monospace";
632- // Sizes are using a namespace identifier size. Ex: <size:small></size:small>
633- private final static String NS_SIZE = "http://beatniksoftware.com/tomboy/size";
634- private final static String SMALL = "small";
635- private final static String LARGE = "large";
636- private final static String HUGE = "huge";
637+ private final static String SMALL = "size:small";
638+ private final static String LARGE = "size:large";
639+ private final static String HUGE = "size:huge";
640 // Bullet list-related
641 private final static String LIST = "list";
642 private final static String LIST_ITEM = "list-item";
643@@ -84,7 +82,7 @@
644 @Override
645 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
646
647- if (localName.equals(NOTE_CONTENT)) {
648+ if (name.equals(NOTE_CONTENT)) {
649
650 // we are under the note-content tag
651 // we will append all its nested tags so I create a string builder to do that
652@@ -94,28 +92,25 @@
653 // if we are in note-content, keep and convert formatting tags
654 // TODO is XML CaSe SeNsItIve? if not change equals to equalsIgnoreCase and apply to endElement()
655 if (inNoteContentTag) {
656- if (localName.equals(BOLD)) {
657+ if (name.equals(BOLD)) {
658 inBoldTag = true;
659- } else if (localName.equals(ITALIC)) {
660+ } else if (name.equals(ITALIC)) {
661 inItalicTag = true;
662- } else if (localName.equals(STRIKETHROUGH)) {
663+ } else if (name.equals(STRIKETHROUGH)) {
664 inStrikeTag = true;
665- } else if (localName.equals(HIGHLIGHT)) {
666+ } else if (name.equals(HIGHLIGHT)) {
667 inHighlighTag = true;
668- } else if (localName.equals(MONOSPACE)) {
669+ } else if (name.equals(MONOSPACE)) {
670 inMonospaceTag = true;
671- } else if (uri.equals(NS_SIZE)) {
672- // now check for the different possible sizes
673- if (localName.equals(SMALL)) {
674- inSizeSmallTag = true;
675- } else if (localName.equals(LARGE)) {
676- inSizeLargeTag = true;
677- } else if (localName.equals(HUGE)) {
678- inSizeHugeTag = true;
679- }
680- } else if (localName.equals(LIST)) {
681+ } else if (name.equals(SMALL)) {
682+ inSizeSmallTag = true;
683+ } else if (name.equals(LARGE)) {
684+ inSizeLargeTag = true;
685+ } else if (name.equals(HUGE)) {
686+ inSizeHugeTag = true;
687+ } else if (name.equals(LIST)) {
688 inListLevel++;
689- } else if (localName.equals(LIST_ITEM)) {
690+ } else if (name.equals(LIST_ITEM)) {
691 inListItem = true;
692 }
693 }
694@@ -126,34 +121,31 @@
695 public void endElement(String uri, String localName, String name)
696 throws SAXException {
697
698- if (localName.equals(NOTE_CONTENT)) {
699+ if (name.equals(NOTE_CONTENT)) {
700 inNoteContentTag = false;
701 }
702
703 // if we are in note-content, keep and convert formatting tags
704 if (inNoteContentTag) {
705- if (localName.equals(BOLD)) {
706+ if (name.equals(BOLD)) {
707 inBoldTag = false;
708- } else if (localName.equals(ITALIC)) {
709+ } else if (name.equals(ITALIC)) {
710 inItalicTag = false;
711- } else if (localName.equals(STRIKETHROUGH)) {
712+ } else if (name.equals(STRIKETHROUGH)) {
713 inStrikeTag = false;
714- } else if (localName.equals(HIGHLIGHT)) {
715+ } else if (name.equals(HIGHLIGHT)) {
716 inHighlighTag = false;
717- } else if (localName.equals(MONOSPACE)) {
718+ } else if (name.equals(MONOSPACE)) {
719 inMonospaceTag = false;
720- } else if (uri.equals(NS_SIZE)) {
721- // now check for the different possible sizes
722- if (localName.equals(SMALL)) {
723- inSizeSmallTag = false;
724- } else if (localName.equals(LARGE)) {
725- inSizeLargeTag = false;
726- } else if (localName.equals(HUGE)) {
727- inSizeHugeTag = false;
728- }
729- } else if (localName.equals(LIST)) {
730+ } else if (name.equals(SMALL)) {
731+ inSizeSmallTag = false;
732+ } else if (name.equals(LARGE)) {
733+ inSizeLargeTag = false;
734+ } else if (name.equals(HUGE)) {
735+ inSizeHugeTag = false;
736+ } else if (name.equals(LIST)) {
737 inListLevel--;
738- } else if (localName.equals(LIST_ITEM)) {
739+ } else if (name.equals(LIST_ITEM)) {
740 inListItem = false;
741 }
742 }
743
744=== modified file 'src/org/tomdroid/xml/NoteHandler.java'
745--- src/org/tomdroid/xml/NoteHandler.java 2009-09-01 14:27:46 +0000
746+++ src/org/tomdroid/xml/NoteHandler.java 2010-01-16 21:03:11 +0000
747@@ -3,7 +3,7 @@
748 * Tomboy on Android
749 * http://www.launchpad.net/tomdroid
750 *
751- * Copyright 2008 Olivier Bilodeau <olivier@bottomlesspit.org>
752+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
753 *
754 * This file is part of Tomdroid.
755 *
756@@ -23,7 +23,6 @@
757 package org.tomdroid.xml;
758
759 import org.tomdroid.Note;
760-import org.tomdroid.util.XmlUtils;
761 import org.xml.sax.Attributes;
762 import org.xml.sax.SAXException;
763 import org.xml.sax.helpers.DefaultHandler;
764@@ -38,22 +37,11 @@
765 // position keepers
766 private boolean inTitleTag = false;
767 private boolean inLastChangeDateTag = false;
768- private boolean inNoteContentTag = false;
769
770 // -- Tomboy's notes XML tags names --
771 // Metadata related
772 private final static String TITLE = "title";
773 private final static String LAST_CHANGE_DATE = "last-change-date";
774- // Style related
775- private final static String NOTE_CONTENT = "note-content";
776-
777- private final static String NS_SIZE = "http://beatniksoftware.com/tomboy/size";
778- private final static String PREFIX_SIZE = "size";
779- private final static String NS_LINK = "http://beatniksoftware.com/tomboy/link";
780- private final static String PREFIX_LINK = "link";
781-
782- // accumulate notecontent is this var since it spans multiple xml tags
783- private StringBuilder xmlContent = new StringBuilder();
784
785 // link to model
786 private Note note;
787@@ -65,72 +53,30 @@
788 @Override
789 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
790
791- // TODO validate top-level tag for tomboy notes and throw exception if its the wrong version number (maybe offer to parse also?)
792-
793- if (localName.equals(NOTE_CONTENT)) {
794+ // TODO validate top-level tag for tomboy notes and throw exception if its the wrong version number (maybe offer to parse also?)
795
796- // we are under the note-content tag
797- // we will append all its nested tags so I create a string builder to do that
798- inNoteContentTag = true;
799- } else if (localName.equals(TITLE)) {
800+ if (localName.equals(TITLE)) {
801 inTitleTag = true;
802 } else if (localName.equals(LAST_CHANGE_DATE)) {
803 inLastChangeDateTag = true;
804 }
805
806- // if we are in note-content, recreate the xml
807- // we're not adding the note-content tags to the xml content
808- if (inNoteContentTag && !localName.equals(NOTE_CONTENT)) {
809-
810- String tag = "<";
811-
812- if (uri != null) {
813- if (uri.equals(NS_LINK)) {
814- tag += PREFIX_LINK+":";
815- } else if (uri.equals(NS_SIZE)) {
816- tag += PREFIX_SIZE+":";
817- }
818- }
819-
820- tag += localName+">";
821- xmlContent.append(tag);
822- }
823 }
824
825 @Override
826 public void endElement(String uri, String localName, String name)
827 throws SAXException {
828
829- if (localName.equals(NOTE_CONTENT)) {
830- inNoteContentTag = false;
831- } else if (localName.equals(TITLE)) {
832+ if (localName.equals(TITLE)) {
833 inTitleTag = false;
834 } else if (localName.equals(LAST_CHANGE_DATE)) {
835 inLastChangeDateTag = false;
836 }
837-
838- // if we are in note-content, recreate the xml
839- if (inNoteContentTag) {
840-
841- String tag = "</";
842-
843- if (uri != null) {
844- if (uri.equals(NS_LINK)) {
845- tag += PREFIX_LINK+":";
846- } else if (uri.equals(NS_SIZE)) {
847- tag += PREFIX_SIZE+":";
848- }
849- }
850-
851- tag += localName+">";
852- xmlContent.append(tag);
853- }
854 }
855
856 @Override
857 public void endDocument() throws SAXException {
858 super.endDocument();
859- note.setXmlContent(xmlContent.toString());
860 }
861
862 // FIXME we'll have to think about how we handle the title soon.. IMHO there's a problem with duplicating the data from the <title> tag and also putting it straight into the note.. this will have to be reported to tomboy
863@@ -148,10 +94,5 @@
864 // DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
865 // note.setLastChangeDate(fmt.parseDateTime(currentString));
866 }
867-
868- if (inNoteContentTag) {
869- // while we are in note-content, append
870- xmlContent.append(XmlUtils.escape(currentString));
871- }
872 }
873 }

Subscribers

People subscribed via source and target branches