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

Proposed by Benoit Garret
Status: Merged
Merged at revision: not available
Proposed branch: lp:~benoit.garret/tomdroid/storage-redesign
Merge into: lp:~tomdroid-maintainers/tomdroid/main
Diff against target: 2075 lines (+648/-857)
16 files modified
AndroidManifest.xml (+1/-1)
doc/dev/TODO (+3/-0)
res/layout/load_web_note_dialog.xml (+0/-63)
res/menu/main.xml (+1/-5)
res/values/strings.xml (+3/-3)
src/org/tomdroid/Note.java (+28/-18)
src/org/tomdroid/NoteCollection.java (+0/-140)
src/org/tomdroid/NoteManager.java (+154/-0)
src/org/tomdroid/NoteProvider.java (+15/-1)
src/org/tomdroid/ui/LoadWebNoteDialog.java (+0/-95)
src/org/tomdroid/ui/Tomdroid.java (+33/-138)
src/org/tomdroid/ui/ViewNote.java (+118/-106)
src/org/tomdroid/util/AsyncNoteLoaderAndParser.java (+53/-41)
src/org/tomdroid/util/NoteContentBuilder.java (+35/-77)
src/org/tomdroid/xml/NoteContentHandler.java (+200/-0)
src/org/tomdroid/xml/NoteHandler.java (+4/-169)
To merge this branch: bzr merge lp:~benoit.garret/tomdroid/storage-redesign
Reviewer Review Type Date Requested Status
Olivier Bilodeau Approve
Review via email: mp+10978@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Benoit Garret (benoit.garret) wrote :

I'm finished with the work on this branch. I believe this is a good foundation for the snowy sync and other future features.

I tested it as best as I could fixing the bugs along the way, I don't think there should be many left.

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

Big rough comment I quickly wrote down when I checked the commits. Its great work! Don't let my bad comments depress you in any way, I am a picky bastard. All of these items are easy to fix. Its just a matter of not losing track.

features
* remove close button, revno 142
* menuSyncWithSD remplaces automatic load, revno 143 (what about tango icon? for consistency)
* storage-redesign, revno 141, 144, 145, 146 (needs rework), 147, 148, 149, 150 (fixes problem introduced in 148), 151, 152, 153

foreseable bugs
* it doesn't do the right thing with a tomdroid/ filled sdcard right now (upgrade path broken)
* no mechansim to add notes in the list activity as of revno 144 (but its not necessary yet)
* revno 146, links directly from an activity to an external class that can take its time, bad. Always thread work that can be blocking (see http://developer.android.com/guide/topics/fundamentals.html)

open design questions
* who should responsable of adding a note to the NoteProvider? Note, Async.. or NoteProvider? see revno 147
* Tomdroid UI with file exceptions.. I don't like that, I think we should abstract that away using a static class in util package maybe? revno 149
* is Java's guid methods working the same way as mono ones? revno 153, 154

issues
* revno 148, what's up with NoteHandler's job?
* revno 149, onListItemClick, we should do ID instead of filenames now? (maybe fixed by 151)

revno 152
* Note = new note() and setting content inside of ViewNote, that's not the spirit, ViewNote should fetch a note based on some criteria and ask it to show itself, it shouldn't have to deal with a note's details
* buildlinkify pattern, should it really end up there?

why is revno155 and 156 necessary? what is it solving?
revno 157 - review some TODO task withdrawn in this commit, are the TODO items removed really dealt with?

TODO
Integrate changes into ChangeLog / NEWS

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

I just cherrypicked the remove close commit.

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

I created a branch to re-work only your sd card changes: lp:~plaxx/tomdroid/load-from-sdcard

Only items remaining:
* do you have an SVG source for your refresh (sync) icon (for data/icon-src/)? what is it license?
* I used grayscaled tango icons so far, do you like tango's action/view-refresh: http://commons.wikimedia.org/wiki/File:View-refresh.svg ? If not, its ok, I *really* don't mind keeping yours, I'm just being a consistency freak ;)

Aside from these, its complete.

I branched out of revno 143 of your storage-redesign branch to avoid cruft and reverted the db change so I don't suggest a merge back in your storage-redesign but a merge straight to main/ instead. Give it an eyeball and let me know. If you want, branch it and do some more fixing.

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

> Only items remaining:
> * do you have an SVG source for your refresh (sync) icon (for data/icon-src/)?
> what is it license?

I picked the icon in android git (http://android.git.kernel.org/), I don't know what license it is published under.
You can find it here: http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/drawable/ic_menu_refresh.png;h=77d70dd4f0534271b71ef4eb87f5a7a917d944fa;hb=HEAD

> * I used grayscaled tango icons so far, do you like tango's action/view-
> refresh: http://commons.wikimedia.org/wiki/File:View-refresh.svg ? If not, its
> ok, I *really* don't mind keeping yours, I'm just being a consistency freak ;)

I'm fine with your icon, the one I put was merely a placeholder before finding something that was more consistent with the other ones in Tomdroid.

And I agree with you, consistency should be enforced as much as we can.

Revision history for this message
Benoit Garret (benoit.garret) wrote :
Download full text (4.0 KiB)

> Big rough comment I quickly wrote down when I checked the commits. Its great
> work! Don't let my bad comments depress you in any way, I am a picky bastard.
> All of these items are easy to fix. Its just a matter of not losing track.

I'm aware that the branch could use some work as I'm far from being an experienced programmer. But I'm comfortable with critics, keep them coming ;-).

> foreseable bugs
> * it doesn't do the right thing with a tomdroid/ filled sdcard right now
> (upgrade path broken)

The current behaviour is to overwrite the existing note with the same guid. This means it replaces the notes in the database with the ones on the SD card.

The problem I can see with this approach is that it doesn't delete the notes that don't exist anymore on the SD card. Apart from that, I don't see what would be broken in case of an upgrade.

> * no mechansim to add notes in the list activity as of revno 144 (but its not
> necessary yet)

The NoteProvider is used as the list store. This means the main list automatically picks up the notes that are inserted in the provider, there is no need to add them manually.

> * revno 146, links directly from an activity to an external class that can
> take its time, bad. Always thread work that can be blocking (see
> http://developer.android.com/guide/topics/fundamentals.html)

Agreed, I put a TODO to put this code in a thread but I forgot about it.

> open design questions
> * who should responsable of adding a note to the NoteProvider? Note, Async..
> or NoteProvider? see revno 147

Good question. What I have in mind is an abstraction of the provider which is currently a bit cumbersome to use. Something like a NoteManager, responsible for dealing with the provider (ie. inserting notes, getting a Note object from the provider, etc).

This would make these operations easier to do as its interfaces would only deal with Note objects, but I don't know if adding something like this would be overkill or not.

> * Tomdroid UI with file exceptions.. I don't like that, I think we should
> abstract that away using a static class in util package maybe? revno 149

I completely agree with that. Moreover, I'm not comfortable with presenting the errors as modal popups in the application. Something less intrusive would be to use the notification area and only present the full error message if the user clicks on the notification.

> * is Java's guid methods working the same way as mono ones? revno 153, 154

I believe so, the java and mono implementation are based on a RFC. The mono one is based on the draft[0] while the java one is based on the actual RFC[1].

[0]http://anonsvn.mono-project.com/viewvc/trunk/mcs/class/corlib/System/Guid.cs?revision=HEAD
[1]http://developer.android.com/reference/java/util/UUID.html

> issues
> * revno 148, what's up with NoteHandler's job?

Synchronization services like Snowy expect the note content to be sent as raw xml so I thought it would be a good idea to store it in the database.

The SpannableStringBuilder is now created lazily in the Note object which means that once the initial import is done, no xml parsing is required to show the main list.

> * revno 149, onListItemClick, we ...

Read more...

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

On Fri, Oct 2, 2009 at 2:21 PM, Benoit Garret <
<email address hidden>> wrote:

>
> > foreseable bugs
> > * it doesn't do the right thing with a tomdroid/ filled sdcard right now
> > (upgrade path broken)
>

> The current behaviour is to overwrite the existing note with the same guid.
> This means it replaces the notes in the database with the ones on the SD
> card.
>
>
I wasn't clear enough, what I meant was that when I first opened the app, it
was saying "no notes" although I knew that I had notes on the sd card. [Our
two] Users upgrading wouldn't expect that. In the sdcard branch, I added a
message that says that you need to sync first so this is fixed.

> The problem I can see with this approach is that it doesn't delete the
> notes that don't exist anymore on the SD card. Apart from that, I don't see
> what would be broken in case of an upgrade.
>

I added a TODO task in doc/dev/TODO to track that but I agree with you, this
is low-priority.

>
> > * no mechansim to add notes in the list activity as of revno 144 (but its
> not
> > necessary yet)
>
> The NoteProvider is used as the list store. This means the main list
> automatically picks up the notes that are inserted in the provider, there is
> no need to add them manually.
>

ok but the query on the provider is done at onCreate() time. What happens if
I add a note (I know this can't happen now but let's say it can) and come
back to the list without a re-run of onCreate() because the app was still in
memory. Will this be handled correctly? If you don't know, don't worry, we
will deal with it when the problem will surface.

[a few hours later]

Just did a propose for merge on your branch with some changes:
https://code.launchpad.net/~plaxx/tomdroid/storage-redesign/+merge/12858
look at it while its manageable and i'll do some more reviewing when I have
time for it (maybe tomorrow night if not then it'll be next weekend..)

--
Olivier Bilodeau <email address hidden>

158. By Benoit Garret

Merged main (incl. changes from the load-from-sdcard branch)

159. By Benoit Garret

Merge lp:~plaxx/tomdroid/storage-redesign, rev. 159 & 160.

160. By Benoit Garret

Remove the NoteCollection, it isn't used anywhere.

161. By Benoit Garret

Remove the load note from web feature.

162. By Benoit Garret

Created a NoteManager to handle the interactions with the content provider.

163. By Benoit Garret

Note.java: use NoteBuilder to parse the xml content
ViewNote.java: handle the asynchronous nature of Note.getNoteContent
NoteBuiler.java: handle the note content instead of the full note (ie. return a SpannableStringBuilder instead of a Note)

164. By Benoit Garret

Merged Olivier's branch

165. By Benoit Garret

Miscellaneous cleaning.

166. By Benoit Garret

Make the NoteManager methods static (the NoteManager didn't really have a state)

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

Wow! I can't believe how much cruft was removed by this work. There were ugly pieces I did that we got rid of thanks to you!

Ok, I finally merged, here are the few things I've changed in the merge commit:

- added a toaster dialog to say that /sdcard/tomdroid/ is empty if you do an sdcard sync with nothing in that folder
- added/removed comments
- ViewNote's inner classes now private (instead of public and package)

I'm very satisfied of what you did. Thanks again!

Now, if you pull this new main into web-sync, how much effort would it take you to have it working and manageable / reviewable for someone like me? :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'AndroidManifest.xml'
2--- AndroidManifest.xml 2009-10-05 04:28:32 +0000
3+++ AndroidManifest.xml 2010-01-17 18:10:19 +0000
4@@ -26,7 +26,7 @@
5 </intent-filter>
6 </activity>
7
8- <activity android:name=".ui.LoadWebNoteDialog" android:theme="@android:style/Theme.Dialog"></activity>
9+
10
11 <provider android:name="NoteProvider"
12 android:authorities="org.tomdroid.notes"
13
14=== modified file 'doc/dev/TODO'
15--- doc/dev/TODO 2009-06-25 01:17:57 +0000
16+++ doc/dev/TODO 2010-01-17 18:10:19 +0000
17@@ -16,6 +16,9 @@
18 Port threading to Android's mechanism (maybe wait to check for 1.5's new API):
19 - Check Handler / Looper / AsyncQueryHandler classes
20
21+SD card sync:
22+- Syncing from SD card only adds notes from the SD card and overwrite on identical guid. At some point, we'll have to consider deletions.
23+
24 File format: (to be in sync with Tomdroid's)
25 - Why is title repeated in note text?
26 - check everrnote dtd for inspiration: http://www.evernote.com/about/developer/
27
28=== removed file 'res/layout/load_web_note_dialog.xml'
29--- res/layout/load_web_note_dialog.xml 2009-06-21 20:21:30 +0000
30+++ res/layout/load_web_note_dialog.xml 1970-01-01 00:00:00 +0000
31@@ -1,63 +0,0 @@
32-<?xml version="1.0" encoding="utf-8"?>
33-<!--
34- Tomdroid
35- Tomboy on Android
36- http://www.launchpad.net/tomdroid
37-
38- Copyright 2008 Olivier Bilodeau <olivier@bottomlesspit.org>
39-
40- This file is part of Tomdroid.
41-
42- Tomdroid is free software: you can redistribute it and/or modify
43- it under the terms of the GNU General Public License as published by
44- the Free Software Foundation, either version 3 of the License, or
45- (at your option) any later version.
46-
47- Tomdroid is distributed in the hope that it will be useful,
48- but WITHOUT ANY WARRANTY; without even the implied warranty of
49- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50- GNU General Public License for more details.
51-
52- You should have received a copy of the GNU General Public License
53- along with Tomdroid. If not, see <http://www.gnu.org/licenses/>.
54--->
55-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
56- android:orientation="vertical"
57- android:layout_width="fill_parent"
58- android:layout_height="wrap_content"
59- android:padding="10dip"
60- >
61-
62- <TextView android:id="@+id/strURL"
63- android:layout_width="fill_parent"
64- android:layout_height="wrap_content"
65- android:text="@string/strURL"
66- />
67-
68- <EditText android:id="@+id/txtURL"
69- android:text="http://"
70- android:singleLine="true"
71- android:nextFocusDown="@+id/btnOk"
72- android:layout_width="fill_parent"
73- android:layout_height="wrap_content"
74- android:layout_below="@+id/strURL"
75- />
76-
77- <Button android:id="@+id/btnOk"
78- android:layout_width="wrap_content"
79- android:layout_height="wrap_content"
80- android:layout_below="@+id/txtURL"
81- android:text="@string/btnOk"
82- android:layout_alignParentRight="true"
83- android:layout_marginLeft="10pt"
84- />
85-
86- <Button android:id="@+id/btnCancel"
87- android:layout_width="wrap_content"
88- android:layout_height="wrap_content"
89- android:layout_toLeftOf="@+id/btnOk"
90- android:layout_alignTop="@+id/btnOk"
91- android:text="@string/btnCancel"
92- />
93-
94-</RelativeLayout>
95\ No newline at end of file
96
97=== modified file 'res/menu/main.xml'
98--- res/menu/main.xml 2009-10-03 16:26:37 +0000
99+++ res/menu/main.xml 2010-01-17 18:10:19 +0000
100@@ -23,11 +23,7 @@
101 -->
102 <menu xmlns:android="http://schemas.android.com/apk/res/android">
103
104- <item
105- android:icon="@drawable/icon_load_from_web"
106- android:title="@string/menuLoadWebNote"
107- android:id="@+id/menuLoadWebNote"
108- />
109+
110
111 <item
112 android:icon="@drawable/icon_about"
113
114=== modified file 'res/values/strings.xml'
115--- res/values/strings.xml 2009-10-03 16:26:37 +0000
116+++ res/values/strings.xml 2010-01-17 18:10:19 +0000
117@@ -32,7 +32,7 @@
118 There are no notes in Tomdroid\'s database. Make sure you copied Tomboy\'s note files (*.note) in
119 the tomdroid/ directory on your sdcard then press \"Menu\" and \"Sync with SD Card\".
120 </string>
121- <string name="menuLoadWebNote">Note from the Web</string>
122+
123 <string name="menuSyncWithSD">Sync with SD Card</string>
124 <string name="menuAbout">About</string>
125 <string name="strWelcome">
126@@ -54,8 +54,8 @@
127 </string>
128
129 <!-- load_web_note_dialog.xml -->
130- <string name="strLoadFromWebTitle">Note from the Web</string>
131- <string name="strURL">Enter the Note\'s URL:</string>
132+
133+
134 <!-- TODO are these really needed!? -->
135 <string name="btnOk">Ok</string>
136 <string name="btnCancel">Cancel</string>
137
138=== modified file 'src/org/tomdroid/Note.java'
139--- src/org/tomdroid/Note.java 2009-06-21 20:25:16 +0000
140+++ src/org/tomdroid/Note.java 2010-01-17 18:10:19 +0000
141@@ -22,27 +22,26 @@
142 */
143 package org.tomdroid;
144
145+import java.util.UUID;
146+
147 import org.joda.time.DateTime;
148 import org.joda.time.format.DateTimeFormatter;
149 import org.joda.time.format.ISODateTimeFormat;
150+import org.tomdroid.util.NoteContentBuilder;
151
152-import android.text.Spannable;
153+import android.os.Handler;
154 import android.text.SpannableStringBuilder;
155-import android.text.style.StyleSpan;
156
157 public class Note {
158
159 // Static references to fields (used in Bundles, ContentResolvers, etc.)
160 public static final String ID = "_id";
161+ public static final String GUID = "guid";
162 public static final String TITLE = "title";
163 public static final String MODIFIED_DATE = "modified_date";
164 public static final String URL = "url";
165 public static final String FILE = "file";
166- public static final String NOTE_CONTENT = "note-content";
167- public static final int NOTE_RECEIVED_AND_VALID = 1;
168- public static final int NO_NOTES = 2;
169- public static final int NOTE_BADURL_OR_PARSING_ERROR = 3;
170- public static final String[] PROJECTION = { Note.ID, Note.TITLE, Note.FILE, Note.MODIFIED_DATE };
171+ public static final String NOTE_CONTENT = "content";
172
173 // Logging info
174 private static final String TAG = "Note";
175@@ -56,12 +55,14 @@
176 public static final float NOTE_SIZE_HUGE_FACTOR = 1.6f;
177
178 // Members
179- private SpannableStringBuilder noteContent = new SpannableStringBuilder();
180+ private SpannableStringBuilder noteContent;
181+ private String xmlContent;
182 private String url;
183 private String fileName;
184 private String title;
185 private DateTime lastChangeDate;
186 private int dbId;
187+ private UUID guid;
188
189 public Note() {}
190
191@@ -105,19 +106,28 @@
192 this.dbId = id;
193 }
194
195- public SpannableStringBuilder getNoteContent() {
196+ public UUID getGuid() {
197+ return guid;
198+ }
199+
200+ public void setGuid(String guid) {
201+ this.guid = UUID.fromString(guid);
202+ }
203+
204+ // TODO: should this handler passed around evolve into an observer pattern?
205+ public SpannableStringBuilder getNoteContent(Handler handler) {
206+
207+ // TODO not sure this is the right place to do this
208+ noteContent = new NoteContentBuilder().setCaller(handler).setInputSource(xmlContent).build();
209 return noteContent;
210 }
211-
212- public void setNoteContent(SpannableStringBuilder noteContent) {
213- this.noteContent = noteContent;
214+
215+ public String getXmlContent() {
216+ return xmlContent;
217 }
218-
219- public SpannableStringBuilder getDisplayableNoteContent() {
220- SpannableStringBuilder sNoteContent = new SpannableStringBuilder(getNoteContent());
221-
222- sNoteContent.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 17, 35, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
223- return sNoteContent;
224+
225+ public void setXmlContent(String xmlContent) {
226+ this.xmlContent = xmlContent;
227 }
228
229 @Override
230
231=== removed file 'src/org/tomdroid/NoteCollection.java'
232--- src/org/tomdroid/NoteCollection.java 2009-06-21 22:20:10 +0000
233+++ src/org/tomdroid/NoteCollection.java 1970-01-01 00:00:00 +0000
234@@ -1,140 +0,0 @@
235-/*
236- * Tomdroid
237- * Tomboy on Android
238- * http://www.launchpad.net/tomdroid
239- *
240- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
241- *
242- * This file is part of Tomdroid.
243- *
244- * Tomdroid is free software: you can redistribute it and/or modify
245- * it under the terms of the GNU General Public License as published by
246- * the Free Software Foundation, either version 3 of the License, or
247- * (at your option) any later version.
248- *
249- * Tomdroid is distributed in the hope that it will be useful,
250- * but WITHOUT ANY WARRANTY; without even the implied warranty of
251- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
252- * GNU General Public License for more details.
253- *
254- * You should have received a copy of the GNU General Public License
255- * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>.
256- */
257-package org.tomdroid;
258-
259-import java.io.File;
260-import java.io.FileNotFoundException;
261-import java.util.ArrayList;
262-import java.util.Iterator;
263-import java.util.List;
264-import java.util.regex.Pattern;
265-
266-import org.tomdroid.ui.Tomdroid;
267-import org.tomdroid.util.AsyncNoteLoaderAndParser;
268-
269-import android.os.Handler;
270-import android.util.Log;
271-
272-// TODO Transform the NoteCollection into a Provider (see .../android-sdk-linux_x86-1.0_r1/docs/devel/data/contentproviders.html#creatingacontentprovider)
273-// this would be more android-like
274-public class NoteCollection {
275-
276- // TODO This is not efficient, I maintain two list, one for the UI and the other for the actual data
277- // the collection of notes
278- private List<Note> notes = new ArrayList<Note>();
279-
280- // Logging info
281- private static final String TAG = "NoteCollection";
282-
283- public List<Note> getNotes() {
284- return notes;
285- }
286-
287- public void setNotes(List<Note> notes) {
288- this.notes = notes;
289- }
290-
291- public void addNote(Note note) {
292- notes.add(note);
293- }
294-
295- public boolean isEmpty() {
296- return notes.isEmpty();
297- }
298-
299- // TODO there is most likely a better way to do this
300- // TODO how does Tomboy deals with notes with duplicate titles? I have to check that out
301- public synchronized Note findNoteFromTitle(String title) {
302- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"searching for note with title: "+title);
303- Iterator<Note> i = notes.iterator();
304- while(i.hasNext()) {
305- Note curNote = i.next();
306- if (curNote.getTitle().equalsIgnoreCase(title)) {
307- return curNote;
308- }
309- }
310- return null;
311- }
312-
313- // TODO there is most likely a better way to do this
314- public Note findNoteFromFilename(String filename) {
315- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"searching for note with filename: "+filename);
316- Iterator<Note> i = notes.iterator();
317- while(i.hasNext()) {
318- Note curNote = i.next();
319- if (curNote.getFileName().equals(filename)) {
320- return curNote;
321- }
322- }
323- return null;
324- }
325-
326- // TODO also throw a empty exception that we will catch in tomdroid and display the empty notelist msg
327- public void loadNotes(Handler hndl) throws FileNotFoundException {
328- File notesRoot = new File(Tomdroid.NOTES_PATH);
329-
330- if (!notesRoot.exists()) {
331- throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH);
332- }
333-
334- AsyncNoteLoaderAndParser asyncLoader = new AsyncNoteLoaderAndParser(notesRoot, this, hndl);
335- asyncLoader.readAndParseNotes();
336- }
337-
338- /**
339- * Builds a regular expression pattern that will match any of the note title currently in the collection.
340- * Useful for the Linkify to create the links to the notes.
341- * @return regexp pattern
342- */
343- public synchronized Pattern buildNoteLinkifyPattern() {
344-
345- StringBuilder sb = new StringBuilder();
346-
347- for (Note n : notes) {
348- // Pattern.quote() here make sure that special characters in the note's title are properly escaped
349- sb.append("("+Pattern.quote(n.getTitle())+")|");
350- }
351-
352- // get rid of the last | that is not needed (I know, its ugly.. better idea?)
353- String pt = sb.substring(0, sb.length()-1);
354-
355- // return a compiled match pattern
356- return Pattern.compile(pt);
357- }
358-
359- // singleton pattern
360- // TODO verify this singleton, I have no net access and I'm not quite sure I nailed it
361- private static NoteCollection nc;
362-
363- // FIXME the contract provided by this singleton is not correct.
364- // If we instantiate this singleton, we expect it to be able to search through notes, which would not be the case
365- // since the loadNotes is called by Tomdroid and not the constructor or something else.
366- public static NoteCollection getInstance() {
367- if (nc == null) {
368- nc = new NoteCollection();
369- }
370- return nc;
371- }
372-
373-
374-}
375
376=== added file 'src/org/tomdroid/NoteManager.java'
377--- src/org/tomdroid/NoteManager.java 1970-01-01 00:00:00 +0000
378+++ src/org/tomdroid/NoteManager.java 2010-01-17 18:10:19 +0000
379@@ -0,0 +1,154 @@
380+/*
381+ * Tomdroid
382+ * Tomboy on Android
383+ * http://www.launchpad.net/tomdroid
384+ *
385+ * Copyright 2009, 2010 Benoit Garret <benoit.garret_launchpad@gadz.org>, Olivier Bilodeau <olivier@bottomlesspit.org>
386+ *
387+ * This file is part of Tomdroid.
388+ *
389+ * Tomdroid is free software: you can redistribute it and/or modify
390+ * it under the terms of the GNU General Public License as published by
391+ * the Free Software Foundation, either version 3 of the License, or
392+ * (at your option) any later version.
393+ *
394+ * Tomdroid is distributed in the hope that it will be useful,
395+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
396+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
397+ * GNU General Public License for more details.
398+ *
399+ * You should have received a copy of the GNU General Public License
400+ * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>.
401+ */
402+package org.tomdroid;
403+
404+import org.tomdroid.ui.Tomdroid;
405+
406+import android.app.Activity;
407+import android.content.ContentResolver;
408+import android.content.ContentValues;
409+import android.database.Cursor;
410+import android.net.Uri;
411+import android.util.Log;
412+import android.widget.ListAdapter;
413+import android.widget.SimpleCursorAdapter;
414+
415+public class NoteManager {
416+
417+ public static final String[] FULL_PROJECTION = { Note.ID, Note.TITLE, Note.FILE, Note.NOTE_CONTENT, Note.MODIFIED_DATE };
418+ public static final String[] LIST_PROJECTION = { Note.ID, Note.TITLE };
419+ public static final String[] TITLE_PROJECTION = { Note.TITLE };
420+ public static final String[] ID_PROJECTION = { Note.ID };
421+ public static final String[] EMPTY_PROJECTION = {};
422+
423+ // static properties
424+ private static final String TAG = "NoteManager";
425+
426+ // gets a note from the content provider
427+ public static Note getNote(Activity activity, Uri uri) {
428+
429+ Note note = null;
430+
431+ // can we find a matching note?
432+ Cursor cursor = activity.managedQuery(uri, FULL_PROJECTION, null, null, null);
433+ // cursor must not be null and must return more than 0 entry
434+ if (!(cursor == null || cursor.getCount() == 0)) {
435+
436+ // create the note from the cursor
437+ cursor.moveToFirst();
438+ String noteContent = cursor.getString(cursor.getColumnIndexOrThrow(Note.NOTE_CONTENT));
439+ String noteTitle = cursor.getString(cursor.getColumnIndexOrThrow(Note.TITLE));
440+
441+ note = new Note();
442+ note.setXmlContent(noteContent);
443+ note.setTitle(noteTitle);
444+ }
445+
446+ return note;
447+ }
448+
449+ // puts a note in the content provider
450+ public static void putNote(Activity activity, Note note) {
451+
452+ // verify if the note is already in the content provider
453+
454+ // TODO make the query prettier (use querybuilder)
455+ Uri notes = Tomdroid.CONTENT_URI;
456+ String[] whereArgs = new String[1];
457+ whereArgs[0] = note.getGuid().toString();
458+
459+ // The note identifier is the guid
460+ ContentResolver cr = activity.getContentResolver();
461+ Cursor managedCursor = cr.query(notes,
462+ EMPTY_PROJECTION,
463+ Note.GUID + "= ?",
464+ whereArgs,
465+ null);
466+ activity.startManagingCursor(managedCursor);
467+
468+ // Preparing the values to be either inserted or updated
469+ // depending on the result of the previous query
470+ ContentValues values = new ContentValues();
471+ values.put(Note.TITLE, note.getTitle());
472+ values.put(Note.FILE, note.getFileName());
473+ values.put(Note.GUID, note.getGuid().toString());
474+ values.put(Note.NOTE_CONTENT, note.getXmlContent());
475+
476+ if (managedCursor.getCount() == 0) {
477+
478+ // This note is not in the database yet we need to insert it
479+ if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"A new note has been detected (not yet in db)");
480+
481+ Uri uri = cr.insert(Tomdroid.CONTENT_URI, values);
482+
483+ if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"Note inserted in content provider. ID: "+uri+" TITLE:"+note.getTitle()+" GUID:"+note.getGuid());
484+ } else {
485+
486+ // Overwrite the previous note if it exists
487+ cr.update(Tomdroid.CONTENT_URI, values, Note.GUID+" = ?", whereArgs);
488+
489+ if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"Note updated in content provider. TITLE:"+note.getTitle()+" GUID:"+note.getGuid());
490+ }
491+ }
492+
493+ public static ListAdapter getListAdapter(Activity activity) {
494+
495+ // get a cursor representing all notes from the NoteProvider
496+ Uri notes = Tomdroid.CONTENT_URI;
497+ Cursor notesCursor = activity.managedQuery(notes, LIST_PROJECTION, null, null, null);
498+
499+ // set up an adapter binding the TITLE field of the cursor to the list item
500+ String[] from = new String[] { Note.TITLE };
501+ int[] to = new int[] { R.id.note_title };
502+ return new SimpleCursorAdapter(activity, R.layout.main_list_item, notesCursor, from, to);
503+ }
504+
505+ // gets the titles of the notes present in the db, used in ViewNote.buildLinkifyPattern()
506+ public static Cursor getTitles(Activity activity) {
507+
508+ // get a cursor containing the notes titles
509+ return activity.managedQuery(Tomdroid.CONTENT_URI, TITLE_PROJECTION, null, null, null);
510+ }
511+
512+ public static int getNoteId(Activity activity, String title) {
513+
514+ int id = 0;
515+
516+ // get the notes ids
517+ String[] whereArgs = { title };
518+ Cursor cursor = activity.managedQuery(Tomdroid.CONTENT_URI, ID_PROJECTION, Note.TITLE+"=?", whereArgs, null);
519+
520+ // cursor must not be null and must return more than 0 entry
521+ if (!(cursor == null || cursor.getCount() == 0)) {
522+
523+ cursor.moveToFirst();
524+ id = cursor.getInt(cursor.getColumnIndexOrThrow(Note.ID));
525+ }
526+ else {
527+ // TODO send an error to the user
528+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes");
529+ }
530+
531+ return id;
532+ }
533+}
534
535=== modified file 'src/org/tomdroid/NoteProvider.java'
536--- src/org/tomdroid/NoteProvider.java 2009-09-29 04:16:27 +0000
537+++ src/org/tomdroid/NoteProvider.java 2010-01-17 18:10:19 +0000
538@@ -43,6 +43,7 @@
539 package org.tomdroid;
540
541 import java.util.HashMap;
542+import java.util.UUID;
543
544 import org.tomdroid.ui.Tomdroid;
545
546@@ -67,7 +68,7 @@
547 // --
548 private static final String DATABASE_NAME = "tomdroid-notes.db";
549 private static final String DB_TABLE_NOTES = "notes";
550- private static final int DB_VERSION = 1;
551+ private static final int DB_VERSION = 2;
552 // TODO once properly implemented, sort by: KEY_MODIFIED_DATE + " DESC"
553 private static final String DEFAULT_SORT_ORDER = Note.ID;
554
555@@ -95,8 +96,10 @@
556 public void onCreate(SQLiteDatabase db) {
557 db.execSQL("CREATE TABLE " + DB_TABLE_NOTES + " ("
558 + Note.ID + " INTEGER PRIMARY KEY,"
559+ + Note.GUID + " TEXT,"
560 + Note.TITLE + " TEXT,"
561 + Note.FILE + " TEXT,"
562+ + Note.NOTE_CONTENT + " TEXT,"
563 + Note.MODIFIED_DATE + " INTEGER"
564 + ");");
565 }
566@@ -204,6 +207,11 @@
567 if (values.containsKey(Note.MODIFIED_DATE) == false) {
568 values.put(Note.MODIFIED_DATE, now);
569 }
570+
571+ // The guid is the unique identifier for a note so it has to be set.
572+ if (values.containsKey(Note.GUID) == false) {
573+ values.put(Note.GUID, UUID.randomUUID().toString());
574+ }
575
576 // TODO does this make sense?
577 if (values.containsKey(Note.TITLE) == false) {
578@@ -214,6 +222,10 @@
579 if (values.containsKey(Note.FILE) == false) {
580 values.put(Note.FILE, "");
581 }
582+
583+ if (values.containsKey(Note.NOTE_CONTENT) == false) {
584+ values.put(Note.NOTE_CONTENT, "");
585+ }
586
587 SQLiteDatabase db = dbHelper.getWritableDatabase();
588 long rowId = db.insert(DB_TABLE_NOTES, Note.FILE, values); // not so sure I did the right thing here
589@@ -280,8 +292,10 @@
590
591 notesProjectionMap = new HashMap<String, String>();
592 notesProjectionMap.put(Note.ID, Note.ID);
593+ notesProjectionMap.put(Note.GUID, Note.GUID);
594 notesProjectionMap.put(Note.TITLE, Note.TITLE);
595 notesProjectionMap.put(Note.FILE, Note.FILE);
596+ notesProjectionMap.put(Note.NOTE_CONTENT, Note.NOTE_CONTENT);
597 notesProjectionMap.put(Note.MODIFIED_DATE, Note.MODIFIED_DATE);
598 }
599 }
600
601=== removed file 'src/org/tomdroid/ui/LoadWebNoteDialog.java'
602--- src/org/tomdroid/ui/LoadWebNoteDialog.java 2009-04-06 22:27:27 +0000
603+++ src/org/tomdroid/ui/LoadWebNoteDialog.java 1970-01-01 00:00:00 +0000
604@@ -1,95 +0,0 @@
605-/*
606- * Tomdroid
607- * Tomboy on Android
608- * http://www.launchpad.net/tomdroid
609- *
610- * Copyright 2008 Olivier Bilodeau <olivier@bottomlesspit.org>
611- *
612- * This file is part of Tomdroid.
613- *
614- * Tomdroid is free software: you can redistribute it and/or modify
615- * it under the terms of the GNU General Public License as published by
616- * the Free Software Foundation, either version 3 of the License, or
617- * (at your option) any later version.
618- *
619- * Tomdroid is distributed in the hope that it will be useful,
620- * but WITHOUT ANY WARRANTY; without even the implied warranty of
621- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
622- * GNU General Public License for more details.
623- *
624- * You should have received a copy of the GNU General Public License
625- * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>.
626- */
627-package org.tomdroid.ui;
628-
629-import org.tomdroid.R;
630-
631-import android.app.Activity;
632-import android.content.Intent;
633-import android.os.Bundle;
634-import android.view.View;
635-import android.view.View.OnClickListener;
636-import android.widget.Button;
637-import android.widget.EditText;
638-
639-/**
640- * This class is in charge of returning only the string of the URL to fetch from.
641- */
642-// TODO change superclass to a dialog (see .../docs/kb/commontasks.html#implementcallbacks)
643-public class LoadWebNoteDialog extends Activity {
644-
645- // UI elements
646- private EditText txtURL;
647-
648- @Override
649- protected void onCreate(Bundle savedInstanceState) {
650- super.onCreate(savedInstanceState);
651-
652- setContentView(R.layout.load_web_note_dialog);
653- setTitle(R.string.strLoadFromWebTitle);
654-
655- // Connect UI elements to variables
656- txtURL = (EditText) findViewById(R.id.txtURL);
657- // txtURL.setText("http://www.bottomlesspit.org/files/note.xml");
658- Button btnOk = (Button)findViewById(R.id.btnOk);
659- Button btnCancel = (Button)findViewById(R.id.btnCancel);
660-
661- // Annon inner-class for button listener
662- btnOk.setOnClickListener(new OnClickListener() {
663-
664- public void onClick(View v)
665- {
666- okClicked(txtURL.getText().toString());
667- }
668- });
669-
670- // Annon inner-class for button listener
671- btnCancel.setOnClickListener(new OnClickListener() {
672-
673- public void onClick(View v)
674- {
675- cancelClicked();
676- }
677- });
678-
679-
680- }
681-
682- private void okClicked(String url) {
683-
684- Bundle bundle = new Bundle();
685- bundle.putString(Tomdroid.RESULT_URL_TO_LOAD, url);
686-
687- Intent i = new Intent();
688- i.putExtras(bundle);
689- setResult(RESULT_OK, i);
690- finish();
691- }
692-
693- private void cancelClicked() {
694-
695- setResult(RESULT_CANCELED);
696- finish();
697- }
698-
699-}
700
701=== modified file 'src/org/tomdroid/ui/Tomdroid.java'
702--- src/org/tomdroid/ui/Tomdroid.java 2009-10-03 16:26:37 +0000
703+++ src/org/tomdroid/ui/Tomdroid.java 2010-01-17 18:10:19 +0000
704@@ -3,7 +3,7 @@
705 * Tomboy on Android
706 * http://www.launchpad.net/tomdroid
707 *
708- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
709+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
710 *
711 * This file is part of Tomdroid.
712 *
713@@ -22,15 +22,16 @@
714 */
715 package org.tomdroid.ui;
716
717+import java.io.File;
718 import java.io.FileNotFoundException;
719
720 import org.tomdroid.Note;
721-import org.tomdroid.NoteCollection;
722+import org.tomdroid.NoteManager;
723 import org.tomdroid.R;
724+import org.tomdroid.util.AsyncNoteLoaderAndParser;
725
726 import android.app.AlertDialog;
727 import android.app.ListActivity;
728-import android.content.ContentValues;
729 import android.content.DialogInterface;
730 import android.content.Intent;
731 import android.content.DialogInterface.OnClickListener;
732@@ -38,14 +39,12 @@
733 import android.database.Cursor;
734 import android.net.Uri;
735 import android.os.Bundle;
736-import android.os.Handler;
737-import android.os.Message;
738 import android.util.Log;
739 import android.view.Menu;
740 import android.view.MenuInflater;
741 import android.view.MenuItem;
742 import android.view.View;
743-import android.widget.ArrayAdapter;
744+import android.widget.ListAdapter;
745 import android.widget.ListView;
746 import android.widget.TextView;
747
748@@ -62,24 +61,14 @@
749 // TODO hardcoded for now
750 public static final String NOTES_PATH = "/sdcard/tomdroid/";
751 // Logging should be disabled for release builds
752- public static final boolean LOGGING_ENABLED = false;
753+ public static final boolean LOGGING_ENABLED = true;
754
755 // Logging info
756 private static final String TAG = "Tomdroid";
757
758- // data keys
759- public static final String RESULT_URL_TO_LOAD = "urlToLoad";
760-
761- // Activity result resources
762- private static final int ACTIVITY_GET_URL=0;
763- private static final int ACTIVITY_VIEW=1;
764-
765- // domain elements
766- private NoteCollection localNotes;
767-
768 // UI to data model glue
769- private ArrayAdapter<String> notesListAdapter;
770 private TextView listEmptyView;
771+ private ListAdapter adapter;
772
773 // Bundle keys for saving state
774 private static final String WARNING_SHOWN = "w";
775@@ -111,15 +100,12 @@
776 .show();
777 }
778
779- // listAdapter that binds the UI to the notes names
780- notesListAdapter = new ArrayAdapter<String>(this, R.layout.main_list_item);
781- setListAdapter(notesListAdapter);
782+ adapter = NoteManager.getListAdapter(this);
783+ setListAdapter(adapter);
784
785 // set the view shown when the list is empty
786 listEmptyView = (TextView)findViewById(R.id.list_empty);
787 getListView().setEmptyView(listEmptyView);
788-
789- localNotes = NoteCollection.getInstance();
790 }
791
792 @Override
793@@ -134,17 +120,21 @@
794 @Override
795 public boolean onOptionsItemSelected(MenuItem item) {
796 switch (item.getItemId()) {
797- case R.id.menuLoadWebNote:
798- showLoadWebNoteDialog();
799- return true;
800-
801 case R.id.menuSyncWithSD:
802-
803- // start loading local notes
804- if (LOGGING_ENABLED) Log.v(TAG, "Loading local notes");
805-
806- try {
807- localNotes.loadNotes(handler);
808+
809+ // start loading local notes
810+ if (LOGGING_ENABLED) Log.v(TAG, "Loading local notes");
811+
812+ try {
813+ File notesRoot = new File(Tomdroid.NOTES_PATH);
814+
815+ if (!notesRoot.exists()) {
816+ throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH);
817+ }
818+
819+ AsyncNoteLoaderAndParser asyncLoader = new AsyncNoteLoaderAndParser(this, notesRoot);
820+ asyncLoader.readAndParseNotes();
821+
822 } catch (FileNotFoundException e) {
823 //TODO put strings in an external resource
824 listEmptyView.setText(R.string.strListEmptyNoNotes);
825@@ -158,8 +148,8 @@
826 .show();
827 e.printStackTrace();
828 }
829-
830- return true;
831+
832+ return true;
833
834 case R.id.menuAbout:
835 showAboutDialog();
836@@ -216,109 +206,14 @@
837 }
838
839 @Override
840- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
841- super.onActivityResult(requestCode, resultCode, data);
842-
843- switch(requestCode) {
844- case ACTIVITY_GET_URL:
845- if (resultCode == RESULT_OK) {
846- String url = data.getExtras().getString(RESULT_URL_TO_LOAD);
847- loadNoteFromURL(url);
848- }
849- }
850- }
851-
852- private Handler handler = new Handler() {
853-
854- @Override
855- public void handleMessage(Message msg) {
856-
857- // thread is done fetching a note and parsing went well
858- if (msg.what == Note.NOTE_RECEIVED_AND_VALID) {
859-
860- // update the note list with this newly parsed note
861- updateNoteListWith(msg.getData().getString(Note.TITLE));
862-
863- } else if (msg.what == Note.NO_NOTES) {
864-
865- // if there are no notes, say so in the list_empty message
866- listEmptyView.setText(R.string.strListEmptyNoNotes);
867- }
868- }
869- };
870-
871- @Override
872 protected void onListItemClick(ListView l, View v, int position, long id) {
873-
874- // get the clicked note
875- Note n = localNotes.findNoteFromTitle(notesListAdapter.getItem(position));
876-
877- Intent i = new Intent(Tomdroid.this, ViewNote.class);
878- i.putExtra(Note.FILE, n.getFileName());
879- startActivityForResult(i, ACTIVITY_VIEW);
880-
881- }
882-
883- private void showLoadWebNoteDialog() {
884-
885- Intent i = new Intent(Tomdroid.this, LoadWebNoteDialog.class);
886- startActivityForResult(i, ACTIVITY_GET_URL);
887- }
888-
889- private void loadNoteFromURL(String url) {
890-
891- Intent i = new Intent(Tomdroid.this, ViewNote.class);
892- i.putExtra(Note.URL, url);
893- startActivity(i);
894- }
895-
896- private void updateNoteListWith(String noteTitle) {
897-
898- // add note to the note list
899- notesListAdapter.add(noteTitle);
900-
901- // get the note instance we will work with that instead from now on
902- Note note = localNotes.findNoteFromTitle(noteTitle);
903-
904- // verify if the note is already in the content provider
905- String[] projection = new String[] {
906- Note.ID,
907- Note.TITLE,
908- };
909-
910- // TODO I could see a problem where someone delete a note and recreate one with the same title.
911- // It would been seen as not new although it is (it will have a new filename)
912- // TODO make the query prettier (use querybuilder)
913- Uri notes = Tomdroid.CONTENT_URI;
914- String[] whereArgs = new String[1];
915- whereArgs[0] = noteTitle;
916- Cursor managedCursor = managedQuery( notes,
917- projection,
918- Note.TITLE + "= ?",
919- whereArgs,
920- Note.TITLE + " ASC");
921- if (managedCursor.getCount() == 0) {
922-
923- // This note is not in the database yet we need to insert it
924- if (LOGGING_ENABLED) Log.v(TAG,"A new note has been detected (not yet in db)");
925-
926- // This add the note to the content Provider
927- // TODO PoC code that should be removed in next iteration's refactoring (no notecollection, everything should come from the provider I guess?)
928- ContentValues values = new ContentValues();
929- values.put(Note.TITLE, note.getTitle());
930- values.put(Note.FILE, note.getFileName());
931- Uri uri = getContentResolver().insert(CONTENT_URI, values);
932- // now that we inserted the note put its ID in the note itself
933- note.setDbId(Integer.parseInt(uri.getLastPathSegment()));
934-
935- if (LOGGING_ENABLED) Log.v(TAG,"Note inserted in content provider. ID: "+uri+" TITLE:"+noteTitle+" ID:"+note.getDbId());
936- } else {
937-
938- // find out the note's id and put it in the note
939- if (managedCursor.moveToFirst()) {
940- int idColumn = managedCursor.getColumnIndex(Note.ID);
941- note.setDbId(managedCursor.getInt(idColumn));
942- }
943- }
944+
945+ Cursor item = (Cursor)adapter.getItem(position);
946+ int noteId = item.getInt(
947+ item.getColumnIndexOrThrow(Note.ID));
948+
949+ Uri intentUri = Uri.parse(Tomdroid.CONTENT_URI+"/"+noteId);
950+ Intent i = new Intent(Intent.ACTION_VIEW, intentUri, this, ViewNote.class);
951+ startActivity(i);
952 }
953 }
954
955=== modified file 'src/org/tomdroid/ui/ViewNote.java'
956--- src/org/tomdroid/ui/ViewNote.java 2009-06-21 20:25:16 +0000
957+++ src/org/tomdroid/ui/ViewNote.java 2010-01-17 18:10:19 +0000
958@@ -3,7 +3,7 @@
959 * Tomboy on Android
960 * http://www.launchpad.net/tomdroid
961 *
962- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
963+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
964 *
965 * This file is part of Tomdroid.
966 *
967@@ -22,14 +22,13 @@
968 */
969 package org.tomdroid.ui;
970
971-import java.net.MalformedURLException;
972-import java.net.URL;
973 import java.util.regex.Matcher;
974+import java.util.regex.Pattern;
975
976 import org.tomdroid.Note;
977-import org.tomdroid.NoteCollection;
978+import org.tomdroid.NoteManager;
979 import org.tomdroid.R;
980-import org.tomdroid.util.NoteBuilder;
981+import org.tomdroid.util.NoteContentBuilder;
982
983 import android.app.Activity;
984 import android.app.AlertDialog;
985@@ -41,6 +40,7 @@
986 import android.os.Bundle;
987 import android.os.Handler;
988 import android.os.Message;
989+import android.text.SpannableStringBuilder;
990 import android.text.util.Linkify;
991 import android.text.util.Linkify.TransformFilter;
992 import android.util.Log;
993@@ -50,14 +50,12 @@
994 // TODO this class is starting to smell
995 public class ViewNote extends Activity {
996
997- private String url;
998- private String file;
999-
1000 // UI elements
1001 private TextView content;
1002
1003 // Model objects
1004 private Note note;
1005+ private SpannableStringBuilder noteContent;
1006
1007 // Logging info
1008 private static final String TAG = "ViewNote";
1009@@ -68,60 +66,11 @@
1010 super.onCreate(savedInstanceState);
1011
1012 setContentView(R.layout.note_view);
1013-
1014 content = (TextView) findViewById(R.id.content);
1015-
1016 final Intent intent = getIntent();
1017-
1018 Uri uri = intent.getData();
1019- if (uri == null) {
1020-
1021- // we were not fired by an Intent-filter so two choice here:
1022- // get external web url or filename
1023- Bundle extras = intent.getExtras();
1024- if (extras != null) {
1025-
1026- // lets grab both variables and test against null later
1027- url = extras.getString(Note.URL);
1028- file = extras.getString(Note.FILE);
1029-
1030- // Based on what was sent in the bundle, we either load from file or url
1031- if (url != null) {
1032-
1033- if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"ViewNote started: Loading a note from Web URL.");
1034-
1035- try {
1036-
1037- note = new NoteBuilder().setCaller(handler).setInputSource(new URL(url)).build();
1038- } catch (MalformedURLException e) {
1039- // TODO catch correctly
1040- e.printStackTrace();
1041-
1042- // TODO put error string in a translatable resource
1043- new AlertDialog.Builder(this)
1044- .setMessage("Invalid URL")
1045- .setTitle("Error")
1046- .setNeutralButton("Ok", new OnClickListener() {
1047- public void onClick(DialogInterface dialog, int which) {
1048- dialog.dismiss();
1049- finish();
1050- }})
1051- .show();
1052- }
1053-
1054- } else if (file != null) {
1055-
1056- if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"ViewNote started: Loading a note based on a filename.");
1057- note = NoteCollection.getInstance().findNoteFromFilename(file);
1058- showNote();
1059- } else {
1060-
1061- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"ViewNote started: Bundle's content was not helpful to find which note to load..");
1062- }
1063- } else {
1064- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"ViewNote started: No extra information in the bundle, we don't know what to load");
1065- }
1066- } else {
1067+
1068+ if (uri != null) {
1069
1070 // We were triggered by an Intent URI
1071 if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "ViewNote started: Intent-filter triggered.");
1072@@ -129,21 +78,45 @@
1073 // TODO validate the good action?
1074 // intent.getAction()
1075
1076- // can we find a matching note?
1077- Cursor cursor = managedQuery(uri, Note.PROJECTION, null, null, null);
1078- // cursor must not be null and must return more than 0 entry
1079- if (!(cursor == null || cursor.getCount() == 0)) {
1080+ // TODO verify that getNote is doing the proper validation
1081+ note = NoteManager.getNote(this, uri);
1082+
1083+ if(note != null) {
1084
1085- cursor.moveToFirst();
1086- String noteFilename = cursor.getString(cursor.getColumnIndexOrThrow(Note.FILE));
1087- note = NoteCollection.getInstance().findNoteFromFilename(noteFilename);
1088- showNote();
1089+ noteContent = note.getNoteContent(handler);
1090
1091 } else {
1092
1093 // TODO send an error to the user
1094- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes");
1095+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The note "+uri+" doesn't exist");
1096+
1097+ // TODO put error string in a translatable resource
1098+ new AlertDialog.Builder(this)
1099+ .setMessage("The requested note could not be found. If you see this error by " +
1100+ "mistake and you are able to replicate it, please file a bug!")
1101+ .setTitle("Error")
1102+ .setNeutralButton("Ok", new OnClickListener() {
1103+ public void onClick(DialogInterface dialog, int which) {
1104+ dialog.dismiss();
1105+ finish();
1106+ }})
1107+ .show();
1108 }
1109+ } else {
1110+
1111+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "The Intent's data was null.");
1112+
1113+ // TODO put error string in a translatable resource
1114+ new AlertDialog.Builder(this)
1115+ .setMessage("The requested note could not be found. If you see this error by " +
1116+ "mistake and you are able to replicate it, please file a bug!")
1117+ .setTitle("Error")
1118+ .setNeutralButton("Ok", new OnClickListener() {
1119+ public void onClick(DialogInterface dialog, int which) {
1120+ dialog.dismiss();
1121+ finish();
1122+ }})
1123+ .show();
1124 }
1125 }
1126
1127@@ -160,55 +133,41 @@
1128 return true;
1129 }
1130
1131- private void showNote() {
1132+ private void showNote() {
1133 // show the note (spannable makes the TextView able to output styled text)
1134- content.setText(note.getNoteContent(), TextView.BufferType.SPANNABLE);
1135+ content.setText(noteContent, TextView.BufferType.SPANNABLE);
1136+ setTitle(note.getTitle());
1137
1138 // add links to stuff that is understood by Android
1139 // TODO this is SLOWWWW!!!!
1140 Linkify.addLinks(content, Linkify.ALL);
1141
1142 // This will create a link every time a note title is found in the text.
1143- // The pattern is built by NoteCollection and contains a very dumb (title1)|(title2) escaped correctly
1144- // Then we tranform the url from the note name to the note id to avoid characters that mess up with the URI (ex: ?)
1145+ // The pattern contains a very dumb (title1)|(title2) escaped correctly
1146+ // Then we transform the url from the note name to the note id to avoid characters that mess up with the URI (ex: ?)
1147 Linkify.addLinks(content,
1148- NoteCollection.getInstance().buildNoteLinkifyPattern(),
1149+ buildNoteLinkifyPattern(),
1150 Tomdroid.CONTENT_URI+"/",
1151 null,
1152-
1153- // custom transform filter that takes the note's title part of the URI and translate it into the note id
1154- // this was done to avoid problems with invalid characters in URI (ex: ? is the query separator but could be in a note title)
1155- new TransformFilter() {
1156-
1157- public String transformUrl(Matcher m, String str) {
1158-
1159- // FIXME if this activity is called from another app and Tomdroid was never launched, getting here will probably make it crash
1160- int id = 0;
1161- try {
1162- id = NoteCollection.getInstance().findNoteFromTitle(str).getDbId();
1163- } catch (Exception e) {
1164- if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "NoteCollection accessed but it was not ready for it..");
1165- }
1166-
1167- // return something like content://org.tomdroid.notes/notes/3
1168- return Tomdroid.CONTENT_URI.toString()+"/"+id;
1169- }
1170- });
1171+ noteTitleTransformFilter);
1172 }
1173-
1174- private Handler handler = new Handler() {
1175-
1176- @Override
1177- public void handleMessage(Message msg) {
1178-
1179- // thread is done fetching note and parsing went well
1180- if (msg.what == Note.NOTE_RECEIVED_AND_VALID) {
1181- showNote();
1182- } else if (msg.what == Note.NOTE_BADURL_OR_PARSING_ERROR) {
1183-
1184- // TODO put this String in a translatable resource
1185+
1186+ public Handler handler = new Handler() {
1187+
1188+ @Override
1189+ public void handleMessage(Message msg) {
1190+
1191+ //parsed ok - show
1192+ if(msg.what == NoteContentBuilder.PARSE_OK) {
1193+ showNote();
1194+
1195+ //parsed not ok - error
1196+ } else if(msg.what == NoteContentBuilder.PARSE_ERROR) {
1197+
1198+ // TODO put this String in a translatable resource
1199 new AlertDialog.Builder(ViewNote.this)
1200- .setMessage("Error loading note from the Web due to a network error or a parsing error.")
1201+ .setMessage("The requested note could not be parsed. If you see this error by " +
1202+ "mistake and you are able to replicate it, please file a bug!")
1203 .setTitle("Error")
1204 .setNeutralButton("Ok", new OnClickListener() {
1205 public void onClick(DialogInterface dialog, int which) {
1206@@ -218,5 +177,58 @@
1207 .show();
1208 }
1209 }
1210- };
1211+ };
1212+
1213+ /**
1214+ * Builds a regular expression pattern that will match any of the note title currently in the collection.
1215+ * Useful for the Linkify to create the links to the notes.
1216+ * @return regexp pattern
1217+ */
1218+ public Pattern buildNoteLinkifyPattern() {
1219+
1220+ StringBuilder sb = new StringBuilder();
1221+ Cursor cursor = NoteManager.getTitles(this);
1222+
1223+ // cursor must not be null and must return more than 0 entry
1224+ if (!(cursor == null || cursor.getCount() == 0)) {
1225+
1226+ String title;
1227+
1228+ cursor.moveToFirst();
1229+
1230+ do {
1231+ title = cursor.getString(cursor.getColumnIndexOrThrow(Note.TITLE));
1232+
1233+ // Pattern.quote() here make sure that special characters in the note's title are properly escaped
1234+ sb.append("("+Pattern.quote(title)+")|");
1235+
1236+ } while (cursor.moveToNext());
1237+
1238+ // get rid of the last | that is not needed (I know, its ugly.. better idea?)
1239+ String pt = sb.substring(0, sb.length()-1);
1240+
1241+ // return a compiled match pattern
1242+ return Pattern.compile(pt);
1243+
1244+ } else {
1245+
1246+ // TODO send an error to the user
1247+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes");
1248+ }
1249+
1250+ return null;
1251+ }
1252+
1253+ // custom transform filter that takes the note's title part of the URI and translate it into the note id
1254+ // this was done to avoid problems with invalid characters in URI (ex: ? is the query separator but could be in a note title)
1255+ TransformFilter noteTitleTransformFilter = new TransformFilter() {
1256+
1257+ public String transformUrl(Matcher m, String str) {
1258+
1259+ int id = NoteManager.getNoteId(ViewNote.this, str);
1260+
1261+ // return something like content://org.tomdroid.notes/notes/3
1262+ return Tomdroid.CONTENT_URI.toString()+"/"+id;
1263+ }
1264+ };
1265 }
1266
1267=== modified file 'src/org/tomdroid/util/AsyncNoteLoaderAndParser.java'
1268--- src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 2009-06-21 21:28:14 +0000
1269+++ src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 2010-01-17 18:10:19 +0000
1270@@ -3,7 +3,7 @@
1271 * Tomboy on Android
1272 * http://www.launchpad.net/tomdroid
1273 *
1274- * Copyright 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
1275+ * Copyright 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
1276 *
1277 * This file is part of Tomdroid.
1278 *
1279@@ -28,51 +28,49 @@
1280 import java.io.FilenameFilter;
1281 import java.io.IOException;
1282 import java.io.InputStreamReader;
1283+import java.io.Reader;
1284 import java.util.concurrent.ExecutorService;
1285 import java.util.concurrent.Executors;
1286+import java.util.regex.Matcher;
1287+import java.util.regex.Pattern;
1288
1289 import javax.xml.parsers.ParserConfigurationException;
1290 import javax.xml.parsers.SAXParser;
1291 import javax.xml.parsers.SAXParserFactory;
1292
1293 import org.tomdroid.Note;
1294-import org.tomdroid.NoteCollection;
1295+import org.tomdroid.NoteManager;
1296 import org.tomdroid.ui.Tomdroid;
1297 import org.tomdroid.xml.NoteHandler;
1298 import org.xml.sax.InputSource;
1299 import org.xml.sax.SAXException;
1300 import org.xml.sax.XMLReader;
1301
1302-import android.os.Bundle;
1303-import android.os.Handler;
1304-import android.os.Message;
1305+import android.app.Activity;
1306 import android.util.Log;
1307
1308 public class AsyncNoteLoaderAndParser {
1309 private final ExecutorService pool;
1310 private final static int poolSize = 1;
1311+ private Activity activity;
1312 private File path;
1313- private NoteCollection noteCollection;
1314- private Handler parentHandler;
1315+
1316+ // regexp for <note-content..>...</note-content>
1317+ private static Pattern note_content = Pattern.compile(".*(<note-content.*>.*<\\/note-content>).*", Pattern.CASE_INSENSITIVE+Pattern.DOTALL);
1318
1319 // logging related
1320 private final static String TAG = "AsyncNoteLoaderAndParser";
1321
1322- public AsyncNoteLoaderAndParser(File path, NoteCollection nc, Handler hndl) {
1323+ public AsyncNoteLoaderAndParser(Activity a, File path) {
1324+ this.activity = a;
1325 this.path = path;
1326+
1327 pool = Executors.newFixedThreadPool(poolSize);
1328- noteCollection = nc;
1329- parentHandler = hndl;
1330 }
1331
1332 public void readAndParseNotes() {
1333 File[] fileList = path.listFiles(new NotesFilter());
1334
1335- // If there are no notes, warn the UI through an empty message
1336- if (fileList.length == 0) {
1337- parentHandler.sendEmptyMessage(Note.NO_NOTES);
1338- }
1339-
1340 for (File file : fileList) {
1341
1342 // give a filename to a thread and ask to parse it when nothing's left to do its over
1343@@ -91,13 +89,14 @@
1344 }
1345
1346 /**
1347- * The worker spawns a new note, parse the file its being given by the executor and add it to the NoteCollection
1348+ * The worker spawns a new note, parse the file its being given by the executor.
1349 */
1350 class Worker implements Runnable {
1351
1352 // the note to be loaded and parsed
1353 private Note note = new Note();
1354 private File file;
1355+ final char[] buffer = new char[0x10000];
1356
1357 public Worker(File f) {
1358 file = f;
1359@@ -106,7 +105,9 @@
1360 public void run() {
1361
1362 note.setFileName(file.getAbsolutePath());
1363-
1364+ // the note guid is not stored in the xml but in the filename
1365+ note.setGuid(file.getName().replace(".note", ""));
1366+
1367 try {
1368 // Parsing
1369 // XML
1370@@ -121,13 +122,12 @@
1371 NoteHandler xmlHandler = new NoteHandler(note);
1372 xr.setContentHandler(xmlHandler);
1373
1374-
1375- // Create the proper input source based on if its a local note or a web note
1376- FileInputStream fin = new FileInputStream(file);
1377- BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192);
1378- InputSource is = new InputSource(in);
1379-
1380- if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note");
1381+ // Create the proper input source
1382+ FileInputStream fin = new FileInputStream(file);
1383+ BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192);
1384+ InputSource is = new InputSource(in);
1385+
1386+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "parsing note");
1387 xr.parse(is);
1388
1389 // TODO wrap and throw a new exception here
1390@@ -138,26 +138,38 @@
1391 } catch (IOException e) {
1392 e.printStackTrace();
1393 }
1394+
1395+ // extract the <note-content..>...</note-content>
1396+ if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "retrieving what is inside of note-content");
1397
1398- synchronized (noteCollection) {
1399- noteCollection.addNote(note);
1400+ // FIXME here we are re-reading the whole note just to grab note-content out, there is probably a best way to do this
1401+ StringBuilder out = new StringBuilder();
1402+ try {
1403+ int read;
1404+ Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
1405+
1406+ do {
1407+ read = reader.read(buffer, 0, buffer.length);
1408+ if (read>0) {
1409+ out.append(buffer, 0, read);
1410+ }
1411+ }
1412+ while (read>=0);
1413+
1414+ Matcher m = note_content.matcher(out.toString());
1415+ if (m.find()) {
1416+ note.setXmlContent(m.group());
1417+ } else {
1418+ if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to grab the note-content out of a note");
1419+ }
1420+
1421+ } catch (IOException e) {
1422+ // TODO handle properly
1423+ e.printStackTrace();
1424+ if (Tomdroid.LOGGING_ENABLED) Log.w(TAG, "Something went wrong trying to read the note");
1425 }
1426
1427- // notify UI that we are done here and send result
1428- warnHandler();
1429+ NoteManager.putNote(AsyncNoteLoaderAndParser.this.activity, note);
1430 }
1431-
1432- private void warnHandler() {
1433-
1434- // notify the main UI that we are done here (sending an ok along with the note's title)
1435- Message msg = Message.obtain();
1436- Bundle bundle = new Bundle();
1437- bundle.putString(Note.TITLE, note.getTitle());
1438- msg.setData(bundle);
1439- msg.what = Note.NOTE_RECEIVED_AND_VALID;
1440-
1441- parentHandler.sendMessage(msg);
1442- }
1443-
1444 }
1445 }
1446
1447=== renamed file 'src/org/tomdroid/util/NoteBuilder.java' => 'src/org/tomdroid/util/NoteContentBuilder.java'
1448--- src/org/tomdroid/util/NoteBuilder.java 2009-06-21 20:25:16 +0000
1449+++ src/org/tomdroid/util/NoteContentBuilder.java 2010-01-17 18:10:19 +0000
1450@@ -3,7 +3,7 @@
1451 * Tomboy on Android
1452 * http://www.launchpad.net/tomdroid
1453 *
1454- * Copyright 2008, 2009 Olivier Bilodeau <olivier@bottomlesspit.org>
1455+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
1456 *
1457 * This file is part of Tomdroid.
1458 *
1459@@ -22,43 +22,30 @@
1460 */
1461 package org.tomdroid.util;
1462
1463-import java.io.BufferedInputStream;
1464-import java.io.BufferedReader;
1465-import java.io.File;
1466-import java.io.FileInputStream;
1467-import java.io.InputStream;
1468-import java.io.InputStreamReader;
1469-import java.net.URL;
1470+import java.io.StringReader;
1471
1472 import javax.xml.parsers.SAXParser;
1473 import javax.xml.parsers.SAXParserFactory;
1474
1475-import org.tomdroid.Note;
1476 import org.tomdroid.ui.Tomdroid;
1477-import org.tomdroid.xml.NoteHandler;
1478+import org.tomdroid.xml.NoteContentHandler;
1479 import org.xml.sax.InputSource;
1480-import org.xml.sax.XMLReader;
1481
1482-import android.os.Bundle;
1483 import android.os.Handler;
1484 import android.os.Message;
1485+import android.text.SpannableStringBuilder;
1486 import android.util.Log;
1487
1488-/*
1489- * For now, you should only use this class for a Note fetched through the network
1490- * TODO remove duplication between here and AsyncNoteLoaderAndParser
1491- *
1492- */
1493-public class NoteBuilder implements Runnable {
1494+public class NoteContentBuilder implements Runnable {
1495+
1496+ public static final int PARSE_OK = 0;
1497+ public static final int PARSE_ERROR = 1;
1498
1499 // Metadata for the Note that will be built
1500- private File file;
1501- private URL url;
1502- // true means local note and false means Web note
1503- private Boolean noteTypeLocal; // using the Object only to be able to test against null
1504+ private InputSource noteContentIs;
1505
1506 // the object being built
1507- private Note note = new Note();
1508+ private SpannableStringBuilder noteContent = new SpannableStringBuilder();
1509
1510 private final String TAG = "NoteBuilder";
1511
1512@@ -66,94 +53,65 @@
1513 private Thread runner;
1514 private Handler parentHandler;
1515
1516- public NoteBuilder () {}
1517+ public NoteContentBuilder () {}
1518
1519- public NoteBuilder setCaller(Handler parent) {
1520+ public NoteContentBuilder setCaller(Handler parent) {
1521
1522 parentHandler = parent;
1523 return this;
1524 }
1525
1526- public NoteBuilder setInputSource(File f) {
1527-
1528- file = f;
1529- note.setFileName(file.getAbsolutePath());
1530- noteTypeLocal = new Boolean(true);
1531- return this;
1532- }
1533-
1534- public NoteBuilder setInputSource(URL u) {
1535-
1536- url = u;
1537- noteTypeLocal = new Boolean(false);
1538- return this;
1539- }
1540-
1541- public Note build() {
1542+ public NoteContentBuilder setInputSource(String nc) {
1543+
1544+ noteContentIs = new InputSource(new StringReader(nc));
1545+ return this;
1546+ }
1547+
1548+ public SpannableStringBuilder build() {
1549
1550 runner = new Thread(this);
1551 runner.start();
1552- return note;
1553+ return noteContent;
1554 }
1555
1556 public void run() {
1557
1558+ boolean successful = true;
1559+
1560 try {
1561 // Parsing
1562 // XML
1563 // Get a SAXParser from the SAXPArserFactory
1564 SAXParserFactory spf = SAXParserFactory.newInstance();
1565+
1566+ // trashing the namespaces but keep prefixes (since we don't have the xml header)
1567+ spf.setFeature("http://xml.org/sax/features/namespaces", false);
1568+ spf.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
1569 SAXParser sp = spf.newSAXParser();
1570-
1571- // Get the XMLReader of the SAXParser we created
1572- XMLReader xr = sp.getXMLReader();
1573-
1574- // Create a new ContentHandler, send it this note to fill and apply it to the XML-Reader
1575- NoteHandler xmlHandler = new NoteHandler(note);
1576- xr.setContentHandler(xmlHandler);
1577-
1578- if (noteTypeLocal == null) {
1579- // TODO find the proper exception to throw here.
1580- throw new IllegalArgumentException("You are not respecting NoteBuilder's contract.");
1581- }
1582-
1583- // Create the proper input source based on if its a local note or a web note
1584- InputSource is;
1585- if (noteTypeLocal) {
1586
1587- FileInputStream fin = new FileInputStream(file);
1588- BufferedReader in = new BufferedReader(new InputStreamReader(fin));
1589- is = new InputSource(in);
1590- } else {
1591- is = new InputSource(new BufferedInputStream((InputStream) url.getContent()));
1592- }
1593-
1594 if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note");
1595- xr.parse(is);
1596+ sp.parse(noteContentIs, new NoteContentHandler(noteContent));
1597 } catch (Exception e) {
1598+ e.printStackTrace();
1599 // TODO handle error in a more granular way
1600- warnHandler(false);
1601+ Log.e(TAG, "There was an error parsing the note.");
1602+ successful = false;
1603 }
1604
1605- // notify UI that we are done here and send result
1606- warnHandler(true);
1607+ warnHandler(successful);
1608 }
1609
1610- private void warnHandler(boolean successfull) {
1611+ private void warnHandler(boolean successful) {
1612
1613 // notify the main UI that we are done here (sending an ok along with the note's title)
1614 Message msg = Message.obtain();
1615- if (successfull) {
1616- Bundle bundle = new Bundle();
1617- bundle.putString(Note.TITLE, note.getTitle());
1618- msg.setData(bundle);
1619- msg.what = Note.NOTE_RECEIVED_AND_VALID;
1620+ if (successful) {
1621+ msg.what = PARSE_OK;
1622 } else {
1623
1624- msg.what = Note.NOTE_BADURL_OR_PARSING_ERROR;
1625+ msg.what = PARSE_ERROR;
1626 }
1627
1628 parentHandler.sendMessage(msg);
1629 }
1630-
1631 }
1632
1633=== added file 'src/org/tomdroid/xml/NoteContentHandler.java'
1634--- src/org/tomdroid/xml/NoteContentHandler.java 1970-01-01 00:00:00 +0000
1635+++ src/org/tomdroid/xml/NoteContentHandler.java 2010-01-17 18:10:19 +0000
1636@@ -0,0 +1,200 @@
1637+/*
1638+ * Tomdroid
1639+ * Tomboy on Android
1640+ * http://www.launchpad.net/tomdroid
1641+ *
1642+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
1643+ *
1644+ * This file is part of Tomdroid.
1645+ *
1646+ * Tomdroid is free software: you can redistribute it and/or modify
1647+ * it under the terms of the GNU General Public License as published by
1648+ * the Free Software Foundation, either version 3 of the License, or
1649+ * (at your option) any later version.
1650+ *
1651+ * Tomdroid is distributed in the hope that it will be useful,
1652+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1653+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1654+ * GNU General Public License for more details.
1655+ *
1656+ * You should have received a copy of the GNU General Public License
1657+ * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>.
1658+ */
1659+package org.tomdroid.xml;
1660+
1661+import org.tomdroid.Note;
1662+import org.xml.sax.Attributes;
1663+import org.xml.sax.SAXException;
1664+import org.xml.sax.helpers.DefaultHandler;
1665+
1666+import android.text.Spannable;
1667+import android.text.SpannableStringBuilder;
1668+import android.text.style.BackgroundColorSpan;
1669+import android.text.style.BulletSpan;
1670+import android.text.style.LeadingMarginSpan;
1671+import android.text.style.RelativeSizeSpan;
1672+import android.text.style.StrikethroughSpan;
1673+import android.text.style.StyleSpan;
1674+import android.text.style.TypefaceSpan;
1675+
1676+/*
1677+ * This class is responsible for parsing the xml note content
1678+ * and formatting the contents in a SpannableStringBuilder
1679+ */
1680+public class NoteContentHandler extends DefaultHandler {
1681+
1682+ // position keepers
1683+ private boolean inNoteContentTag = false;
1684+ private boolean inBoldTag = false;
1685+ private boolean inItalicTag = false;
1686+ private boolean inStrikeTag = false;
1687+ private boolean inHighlighTag = false;
1688+ private boolean inMonospaceTag = false;
1689+ private boolean inSizeSmallTag = false;
1690+ private boolean inSizeLargeTag = false;
1691+ private boolean inSizeHugeTag = false;
1692+ private int inListLevel = 0;
1693+ private boolean inListItem = false;
1694+
1695+ // -- Tomboy's notes XML tags names --
1696+ // Style related
1697+ private final static String NOTE_CONTENT = "note-content";
1698+ private final static String BOLD = "bold";
1699+ private final static String ITALIC = "italic";
1700+ private final static String STRIKETHROUGH = "strikethrough";
1701+ private final static String HIGHLIGHT = "highlight";
1702+ private final static String MONOSPACE = "monospace";
1703+ private final static String SMALL = "size:small";
1704+ private final static String LARGE = "size:large";
1705+ private final static String HUGE = "size:huge";
1706+ // Bullet list-related
1707+ private final static String LIST = "list";
1708+ private final static String LIST_ITEM = "list-item";
1709+
1710+ // accumulate notecontent is this var since it spans multiple xml tags
1711+ private SpannableStringBuilder ssb;
1712+
1713+ public NoteContentHandler(SpannableStringBuilder noteContent) {
1714+
1715+ this.ssb = noteContent;
1716+ }
1717+
1718+ @Override
1719+ public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
1720+
1721+ if (name.equals(NOTE_CONTENT)) {
1722+
1723+ // we are under the note-content tag
1724+ // we will append all its nested tags so I create a string builder to do that
1725+ inNoteContentTag = true;
1726+ }
1727+
1728+ // if we are in note-content, keep and convert formatting tags
1729+ // TODO is XML CaSe SeNsItIve? if not change equals to equalsIgnoreCase and apply to endElement()
1730+ if (inNoteContentTag) {
1731+ if (name.equals(BOLD)) {
1732+ inBoldTag = true;
1733+ } else if (name.equals(ITALIC)) {
1734+ inItalicTag = true;
1735+ } else if (name.equals(STRIKETHROUGH)) {
1736+ inStrikeTag = true;
1737+ } else if (name.equals(HIGHLIGHT)) {
1738+ inHighlighTag = true;
1739+ } else if (name.equals(MONOSPACE)) {
1740+ inMonospaceTag = true;
1741+ } else if (name.equals(SMALL)) {
1742+ inSizeSmallTag = true;
1743+ } else if (name.equals(LARGE)) {
1744+ inSizeLargeTag = true;
1745+ } else if (name.equals(HUGE)) {
1746+ inSizeHugeTag = true;
1747+ } else if (name.equals(LIST)) {
1748+ inListLevel++;
1749+ } else if (name.equals(LIST_ITEM)) {
1750+ inListItem = true;
1751+ }
1752+ }
1753+
1754+ }
1755+
1756+ @Override
1757+ public void endElement(String uri, String localName, String name)
1758+ throws SAXException {
1759+
1760+ if (name.equals(NOTE_CONTENT)) {
1761+ inNoteContentTag = false;
1762+ }
1763+
1764+ // if we are in note-content, keep and convert formatting tags
1765+ if (inNoteContentTag) {
1766+ if (name.equals(BOLD)) {
1767+ inBoldTag = false;
1768+ } else if (name.equals(ITALIC)) {
1769+ inItalicTag = false;
1770+ } else if (name.equals(STRIKETHROUGH)) {
1771+ inStrikeTag = false;
1772+ } else if (name.equals(HIGHLIGHT)) {
1773+ inHighlighTag = false;
1774+ } else if (name.equals(MONOSPACE)) {
1775+ inMonospaceTag = false;
1776+ } else if (name.equals(SMALL)) {
1777+ inSizeSmallTag = false;
1778+ } else if (name.equals(LARGE)) {
1779+ inSizeLargeTag = false;
1780+ } else if (name.equals(HUGE)) {
1781+ inSizeHugeTag = false;
1782+ } else if (name.equals(LIST)) {
1783+ inListLevel--;
1784+ } else if (name.equals(LIST_ITEM)) {
1785+ inListItem = false;
1786+ }
1787+ }
1788+ }
1789+
1790+ @Override
1791+ public void characters(char[] ch, int start, int length)
1792+ throws SAXException {
1793+
1794+ String currentString = new String(ch, start, length);
1795+
1796+ if (inNoteContentTag) {
1797+ // while we are in note-content, append
1798+ ssb.append(currentString, start, length);
1799+ int strLenStart = ssb.length()-length;
1800+ int strLenEnd = ssb.length();
1801+
1802+ // apply style if required
1803+ // TODO I haven't tested nested tags yet
1804+ if (inBoldTag) {
1805+ ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1806+ }
1807+ if (inItalicTag) {
1808+ ssb.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1809+ }
1810+ if (inStrikeTag) {
1811+ ssb.setSpan(new StrikethroughSpan(), strLenStart, ssb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1812+ }
1813+ if (inHighlighTag) {
1814+ ssb.setSpan(new BackgroundColorSpan(Note.NOTE_HIGHLIGHT_COLOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1815+ }
1816+ if (inMonospaceTag) {
1817+ ssb.setSpan(new TypefaceSpan(Note.NOTE_MONOSPACE_TYPEFACE), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1818+ }
1819+ if (inSizeSmallTag) {
1820+ ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_SMALL_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1821+ }
1822+ if (inSizeLargeTag) {
1823+ ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_LARGE_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1824+ }
1825+ if (inSizeHugeTag) {
1826+ ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_HUGE_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1827+ }
1828+ if (inListItem) {
1829+ // TODO new sexier bullets?
1830+ // Show a leading margin that is as wide as the nested level we are in
1831+ ssb.setSpan(new LeadingMarginSpan.Standard(10*inListLevel), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1832+ ssb.setSpan(new BulletSpan(), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1833+ }
1834+ }
1835+ }
1836+}
1837
1838=== modified file 'src/org/tomdroid/xml/NoteHandler.java'
1839--- src/org/tomdroid/xml/NoteHandler.java 2009-06-21 21:59:41 +0000
1840+++ src/org/tomdroid/xml/NoteHandler.java 2010-01-17 18:10:19 +0000
1841@@ -3,7 +3,7 @@
1842 * Tomboy on Android
1843 * http://www.launchpad.net/tomdroid
1844 *
1845- * Copyright 2008 Olivier Bilodeau <olivier@bottomlesspit.org>
1846+ * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
1847 *
1848 * This file is part of Tomdroid.
1849 *
1850@@ -22,169 +22,51 @@
1851 */
1852 package org.tomdroid.xml;
1853
1854-import org.joda.time.format.DateTimeFormatter;
1855-import org.joda.time.format.ISODateTimeFormat;
1856 import org.tomdroid.Note;
1857 import org.xml.sax.Attributes;
1858 import org.xml.sax.SAXException;
1859 import org.xml.sax.helpers.DefaultHandler;
1860
1861-import android.text.Spannable;
1862-import android.text.SpannableStringBuilder;
1863-import android.text.style.BackgroundColorSpan;
1864-import android.text.style.BulletSpan;
1865-import android.text.style.LeadingMarginSpan;
1866-import android.text.style.RelativeSizeSpan;
1867-import android.text.style.StrikethroughSpan;
1868-import android.text.style.StyleSpan;
1869-import android.text.style.TypefaceSpan;
1870-
1871-/*
1872- * I don't know if I'm doing the right thing but I think that giving this class
1873- * the responsibility of filling the note is something quite cohesive and hope
1874- * the coupling involved won't do much damage. I guess time will tell.
1875- */
1876 public class NoteHandler extends DefaultHandler {
1877
1878 // position keepers
1879 private boolean inTitleTag = false;
1880 private boolean inLastChangeDateTag = false;
1881- private boolean inNoteTag = false;
1882- private boolean inTextTag = false;
1883- private boolean inNoteContentTag = false;
1884- private boolean inNoteContentTagMustGrabTitle = true; // hack to grab title and make it big
1885- private boolean inBoldTag = false;
1886- private boolean inItalicTag = false;
1887- private boolean inStrikeTag = false;
1888- private boolean inHighlighTag = false;
1889- private boolean inMonospaceTag = false;
1890- private boolean inSizeSmallTag = false;
1891- private boolean inSizeLargeTag = false;
1892- private boolean inSizeHugeTag = false;
1893- private int inListLevel = 0;
1894- private boolean inListItem = false;
1895
1896 // -- Tomboy's notes XML tags names --
1897 // Metadata related
1898 private final static String TITLE = "title";
1899 private final static String LAST_CHANGE_DATE = "last-change-date";
1900- // Style related
1901- private final static String NOTE_CONTENT = "note-content";
1902- private final static String BOLD = "bold";
1903- private final static String ITALIC = "italic";
1904- private final static String STRIKETHROUGH = "strikethrough";
1905- private final static String HIGHLIGHT = "highlight";
1906- private final static String MONOSPACE = "monospace";
1907- // Sizes are using a namespace identifier size. Ex: <size:small></size:small>
1908- private final static String NS_SIZE = "http://beatniksoftware.com/tomboy/size";
1909- private final static String SMALL = "small";
1910- private final static String LARGE = "large";
1911- private final static String HUGE = "huge";
1912- // Bullet list-related
1913- private final static String LIST = "list";
1914- private final static String LIST_ITEM = "list-item";
1915-
1916- // accumulate notecontent is this var since it spans multiple xml tags
1917- private SpannableStringBuilder ssb;
1918
1919 // link to model
1920 private Note note;
1921
1922 public NoteHandler(Note note) {
1923 this.note = note;
1924-
1925- // we will use the SpannableStringBuilder from the note
1926- this.ssb = note.getNoteContent();
1927 }
1928
1929 @Override
1930 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
1931
1932- // TODO validate top-level tag for tomboy notes and throw exception if its the wrong version number (maybe offer to parse also?)
1933-
1934- if (localName.equals(NOTE_CONTENT)) {
1935+ // TODO validate top-level tag for tomboy notes and throw exception if its the wrong version number (maybe offer to parse also?)
1936
1937- // we are under the note-content tag
1938- // we will append all its nested tags so I create a string builder to do that
1939- inNoteContentTag = true;
1940- } else if (localName.equals(TITLE)) {
1941+ if (localName.equals(TITLE)) {
1942 inTitleTag = true;
1943 } else if (localName.equals(LAST_CHANGE_DATE)) {
1944 inLastChangeDateTag = true;
1945 }
1946
1947- // if we are in note-content, keep and convert formatting tags
1948- // TODO is XML CaSe SeNsItIve? if not change equals to equalsIgnoreCase and apply to endElement()
1949- if (inNoteContentTag) {
1950- if (localName.equals(BOLD)) {
1951- inBoldTag = true;
1952- } else if (localName.equals(ITALIC)) {
1953- inItalicTag = true;
1954- } else if (localName.equals(STRIKETHROUGH)) {
1955- inStrikeTag = true;
1956- } else if (localName.equals(HIGHLIGHT)) {
1957- inHighlighTag = true;
1958- } else if (localName.equals(MONOSPACE)) {
1959- inMonospaceTag = true;
1960- } else if (uri.equals(NS_SIZE)) {
1961- // now check for the different possible sizes
1962- if (localName.equals(SMALL)) {
1963- inSizeSmallTag = true;
1964- } else if (localName.equals(LARGE)) {
1965- inSizeLargeTag = true;
1966- } else if (localName.equals(HUGE)) {
1967- inSizeHugeTag = true;
1968- }
1969- } else if (localName.equals(LIST)) {
1970- inListLevel++;
1971- } else if (localName.equals(LIST_ITEM)) {
1972- inListItem = true;
1973- }
1974- }
1975-
1976 }
1977
1978 @Override
1979 public void endElement(String uri, String localName, String name)
1980 throws SAXException {
1981
1982- if (localName.equals(NOTE_CONTENT)) {
1983-
1984- // note-content is over, we can set the builded note to Note's noteContent
1985- inNoteContentTag = false;
1986- } else if (localName.equals(TITLE)) {
1987+ if (localName.equals(TITLE)) {
1988 inTitleTag = false;
1989 } else if (localName.equals(LAST_CHANGE_DATE)) {
1990 inLastChangeDateTag = false;
1991 }
1992-
1993- // if we are in note-content, keep and convert formatting tags
1994- if (inNoteContentTag) {
1995- if (localName.equals(BOLD)) {
1996- inBoldTag = false;
1997- } else if (localName.equals(ITALIC)) {
1998- inItalicTag = false;
1999- } else if (localName.equals(STRIKETHROUGH)) {
2000- inStrikeTag = false;
2001- } else if (localName.equals(HIGHLIGHT)) {
2002- inHighlighTag = false;
2003- } else if (localName.equals(MONOSPACE)) {
2004- inMonospaceTag = false;
2005- } else if (uri.equals(NS_SIZE)) {
2006- // now check for the different possible sizes
2007- if (localName.equals(SMALL)) {
2008- inSizeSmallTag = false;
2009- } else if (localName.equals(LARGE)) {
2010- inSizeLargeTag = false;
2011- } else if (localName.equals(HUGE)) {
2012- inSizeHugeTag = false;
2013- }
2014- } else if (localName.equals(LIST)) {
2015- inListLevel--;
2016- } else if (localName.equals(LIST_ITEM)) {
2017- inListItem = false;
2018- }
2019- }
2020 }
2021
2022 // 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
2023@@ -202,52 +84,5 @@
2024 // DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
2025 // note.setLastChangeDate(fmt.parseDateTime(currentString));
2026 }
2027-
2028- if (inNoteContentTag) {
2029- // while we are in note-content, append
2030- ssb.append(currentString, start, length);
2031- int strLenStart = ssb.length()-length;
2032- int strLenEnd = ssb.length();
2033-
2034- // the first line of the note-content tag is the note's title. It must be big like in tomboy.
2035- // TODO tomboy's fileformat suggestion: title should not be repeated in the note-content. This is ugly IMHO
2036- if (inNoteContentTagMustGrabTitle) {
2037- ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_HUGE_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2038- inNoteContentTagMustGrabTitle = false;
2039- }
2040-
2041- // apply style if required
2042- // TODO I haven't tested nested tags yet
2043- if (inBoldTag) {
2044- ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2045- }
2046- if (inItalicTag) {
2047- ssb.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2048- }
2049- if (inStrikeTag) {
2050- ssb.setSpan(new StrikethroughSpan(), strLenStart, ssb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2051- }
2052- if (inHighlighTag) {
2053- ssb.setSpan(new BackgroundColorSpan(Note.NOTE_HIGHLIGHT_COLOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2054- }
2055- if (inMonospaceTag) {
2056- ssb.setSpan(new TypefaceSpan(Note.NOTE_MONOSPACE_TYPEFACE), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2057- }
2058- if (inSizeSmallTag) {
2059- ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_SMALL_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2060- }
2061- if (inSizeLargeTag) {
2062- ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_LARGE_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2063- }
2064- if (inSizeHugeTag) {
2065- ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_HUGE_FACTOR), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2066- }
2067- if (inListItem) {
2068- // TODO new sexier bullets?
2069- // Show a leading margin that is as wide as the nested level we are in
2070- ssb.setSpan(new LeadingMarginSpan.Standard(10*inListLevel), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2071- ssb.setSpan(new BulletSpan(), strLenStart, strLenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2072- }
2073- }
2074 }
2075 }

Subscribers

People subscribed via source and target branches