Merge lp:~plaxx/tomdroid/storage-redesign into lp:~benoit.garret/tomdroid/storage-redesign
- storage-redesign
- Merge into storage-redesign
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 |
Related bugs: |
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.
Commit message
Description of the change
Olivier Bilodeau (plaxx) wrote : Posted in a previous version of this proposal | # |
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.
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?
Benoit Garret (benoit.garret) wrote : Posted in a previous version of this proposal | # |
I merged the revisions 158, 159 and 160.
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.
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.
> 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?
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?
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:/
are the owner of lp...
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.
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.
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/.
- 169. By Olivier Bilodeau
-
Misc cleanup
- NoteManager's constructor no longer public (to prevent bypassing getInstance())
- Added comments
- Moved stuff around (static first)
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://
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?
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!
- 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.
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://
> 28android.
> g[],%20java.
>
> 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...
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.
Preview Diff
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; |
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("&", "&") |
593 | - .replace("<", "<") |
594 | - .replace(">", ">") |
595 | - .replace("\"", """) |
596 | - .replace("\'", "'"); |
597 | - } |
598 | - |
599 | - /** |
600 | - * Useful to replace the escaped characters their unescaped counterparts |
601 | - * Ex: &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("&", "&") |
609 | - .replace("<", "<") |
610 | - .replace(">", ">") |
611 | - .replace(""", "\"") |
612 | - .replace("'", "\'"); |
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 | } |
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.