diff options
Diffstat (limited to 'src/bookmarks.c')
-rw-r--r-- | src/bookmarks.c | 228 |
1 files changed, 196 insertions, 32 deletions
diff --git a/src/bookmarks.c b/src/bookmarks.c index c27efbfe..fe2ca47a 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c | |||
@@ -31,13 +31,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include <the_Foundation/path.h> | 31 | #include <the_Foundation/path.h> |
32 | #include <the_Foundation/regexp.h> | 32 | #include <the_Foundation/regexp.h> |
33 | #include <the_Foundation/stringset.h> | 33 | #include <the_Foundation/stringset.h> |
34 | #include <the_Foundation/toml.h> | ||
34 | 35 | ||
35 | void init_Bookmark(iBookmark *d) { | 36 | void init_Bookmark(iBookmark *d) { |
36 | init_String(&d->url); | 37 | init_String(&d->url); |
37 | init_String(&d->title); | 38 | init_String(&d->title); |
38 | init_String(&d->tags); | 39 | init_String(&d->tags); |
39 | iZap(d->when); | 40 | iZap(d->when); |
40 | d->sourceId = 0; | 41 | d->parentId = 0; |
42 | d->order = 0; | ||
41 | } | 43 | } |
42 | 44 | ||
43 | void deinit_Bookmark(iBookmark *d) { | 45 | void deinit_Bookmark(iBookmark *d) { |
@@ -77,13 +79,18 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b) | |||
77 | return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); | 79 | return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); |
78 | } | 80 | } |
79 | 81 | ||
80 | static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) { | 82 | int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) { |
81 | return cmpStringCase_String(&(*a)->title, &(*b)->title); | 83 | return cmpStringCase_String(&(*a)->title, &(*b)->title); |
82 | } | 84 | } |
83 | 85 | ||
86 | iBool filterInsideFolder_Bookmark(void *context, const iBookmark *bm) { | ||
87 | return hasParent_Bookmark(bm, id_Bookmark(context)); | ||
88 | } | ||
89 | |||
84 | /*----------------------------------------------------------------------------------------------*/ | 90 | /*----------------------------------------------------------------------------------------------*/ |
85 | 91 | ||
86 | static const char *fileName_Bookmarks_ = "bookmarks.txt"; | 92 | static const char *oldFileName_Bookmarks_ = "bookmarks.txt"; |
93 | static const char *fileName_Bookmarks_ = "bookmarks.ini"; /* since v1.7 (TOML subset) */ | ||
87 | 94 | ||
88 | struct Impl_Bookmarks { | 95 | struct Impl_Bookmarks { |
89 | iMutex * mtx; | 96 | iMutex * mtx; |
@@ -123,16 +130,19 @@ void clear_Bookmarks(iBookmarks *d) { | |||
123 | unlock_Mutex(d->mtx); | 130 | unlock_Mutex(d->mtx); |
124 | } | 131 | } |
125 | 132 | ||
133 | static void insertId_Bookmarks_(iBookmarks *d, iBookmark *bookmark, int id) { | ||
134 | bookmark->node.key = id; | ||
135 | insert_Hash(&d->bookmarks, &bookmark->node); | ||
136 | } | ||
137 | |||
126 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { | 138 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { |
127 | lock_Mutex(d->mtx); | 139 | lock_Mutex(d->mtx); |
128 | bookmark->node.key = ++d->idEnum; | 140 | insertId_Bookmarks_(d, bookmark, ++d->idEnum); |
129 | insert_Hash(&d->bookmarks, &bookmark->node); | ||
130 | unlock_Mutex(d->mtx); | 141 | unlock_Mutex(d->mtx); |
131 | } | 142 | } |
132 | 143 | ||
133 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { | 144 | static void loadOldFormat_Bookmarks(iBookmarks *d, const char *dirPath) { |
134 | clear_Bookmarks(d); | 145 | iFile *f = newCStr_File(concatPath_CStr(dirPath, oldFileName_Bookmarks_)); |
135 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); | ||
136 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | 146 | if (open_File(f, readOnly_FileMode | text_FileMode)) { |
137 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); | 147 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); |
138 | iRangecc line = iNullRange; | 148 | iRangecc line = iNullRange; |
@@ -170,6 +180,111 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) { | |||
170 | iRelease(f); | 180 | iRelease(f); |
171 | } | 181 | } |
172 | 182 | ||
183 | /*----------------------------------------------------------------------------------------------*/ | ||
184 | |||
185 | iDeclareType(BookmarkLoader) | ||
186 | |||
187 | struct Impl_BookmarkLoader { | ||
188 | iTomlParser *toml; | ||
189 | iBookmarks * bookmarks; | ||
190 | iBookmark * bm; | ||
191 | }; | ||
192 | |||
193 | static void handleTable_BookmarkLoader_(void *context, const iString *table, iBool isStart) { | ||
194 | iBookmarkLoader *d = context; | ||
195 | if (isStart) { | ||
196 | iAssert(!d->bm); | ||
197 | d->bm = new_Bookmark(); | ||
198 | const int id = toInt_String(table); | ||
199 | d->bookmarks->idEnum = iMax(d->bookmarks->idEnum, id); | ||
200 | insertId_Bookmarks_(d->bookmarks, d->bm, id); | ||
201 | } | ||
202 | else { | ||
203 | d->bm = NULL; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | static void handleKeyValue_BookmarkLoader_(void *context, const iString *table, const iString *key, | ||
208 | const iTomlValue *tv) { | ||
209 | iBookmarkLoader *d = context; | ||
210 | iBookmark *bm = d->bm; | ||
211 | if (bm) { | ||
212 | iUnused(table); /* it's the current one */ | ||
213 | if (!cmp_String(key, "url") && tv->type == string_TomlType) { | ||
214 | set_String(&bm->url, tv->value.string); | ||
215 | } | ||
216 | else if (!cmp_String(key, "title") && tv->type == string_TomlType) { | ||
217 | set_String(&bm->title, tv->value.string); | ||
218 | } | ||
219 | else if (!cmp_String(key, "tags") && tv->type == string_TomlType) { | ||
220 | set_String(&bm->tags, tv->value.string); | ||
221 | } | ||
222 | else if (!cmp_String(key, "icon") && tv->type == int64_TomlType) { | ||
223 | bm->icon = (iChar) tv->value.int64; | ||
224 | } | ||
225 | else if (!cmp_String(key, "created") && tv->type == int64_TomlType) { | ||
226 | initSeconds_Time(&bm->when, tv->value.int64); | ||
227 | } | ||
228 | else if (!cmp_String(key, "parent") && tv->type == int64_TomlType) { | ||
229 | bm->parentId = tv->value.int64; | ||
230 | } | ||
231 | else if (!cmp_String(key, "order") && tv->type == int64_TomlType) { | ||
232 | bm->order = tv->value.int64; | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | static void init_BookmarkLoader(iBookmarkLoader *d, iBookmarks *bookmarks) { | ||
238 | d->toml = new_TomlParser(); | ||
239 | setHandlers_TomlParser(d->toml, handleTable_BookmarkLoader_, handleKeyValue_BookmarkLoader_, d); | ||
240 | d->bookmarks = bookmarks; | ||
241 | d->bm = NULL; | ||
242 | } | ||
243 | |||
244 | static void deinit_BookmarkLoader(iBookmarkLoader *d) { | ||
245 | delete_TomlParser(d->toml); | ||
246 | } | ||
247 | |||
248 | static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) { | ||
249 | if (!parse_TomlParser(d->toml, collect_String(readString_File(file)))) { | ||
250 | fprintf(stderr, "[Bookmarks] syntax error(s) in %s\n", cstr_String(path_File(file))); | ||
251 | } | ||
252 | } | ||
253 | |||
254 | iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b) | ||
255 | |||
256 | /*----------------------------------------------------------------------------------------------*/ | ||
257 | |||
258 | static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) { | ||
259 | return bm->parentId == *(const uint32_t *) context; | ||
260 | } | ||
261 | |||
262 | void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp) { | ||
263 | lock_Mutex(d->mtx); | ||
264 | iConstForEach(PtrArray, i, list_Bookmarks(d, cmp, isMatchingParent_Bookmark_, &parentId)) { | ||
265 | iBookmark *bm = i.ptr; | ||
266 | bm->order = index_PtrArrayConstIterator(&i) + 1; | ||
267 | } | ||
268 | unlock_Mutex(d->mtx); | ||
269 | } | ||
270 | |||
271 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { | ||
272 | clear_Bookmarks(d); | ||
273 | /* Load new .ini bookmarks, if present. */ | ||
274 | iFile *f = iClob(newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_))); | ||
275 | if (!open_File(f, readOnly_FileMode | text_FileMode)) { | ||
276 | /* As a fallback, try loading the v1.6 bookmarks file. */ | ||
277 | loadOldFormat_Bookmarks(d, dirPath); | ||
278 | /* Old format has an implicit alphabetic sort order. */ | ||
279 | sort_Bookmarks(d, 0, cmpTitleAscending_Bookmark); | ||
280 | return; | ||
281 | } | ||
282 | iBookmarkLoader loader; | ||
283 | init_BookmarkLoader(&loader, d); | ||
284 | load_BookmarkLoader(&loader, f); | ||
285 | deinit_BookmarkLoader(&loader); | ||
286 | } | ||
287 | |||
173 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | 288 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { |
174 | lock_Mutex(d->mtx); | 289 | lock_Mutex(d->mtx); |
175 | iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption)); | 290 | iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption)); |
@@ -185,12 +300,26 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | |||
185 | continue; | 300 | continue; |
186 | } | 301 | } |
187 | format_String(str, | 302 | format_String(str, |
188 | "%08x %.0lf %s\n%s\n%s\n", | 303 | "[%d]\n" |
304 | "url = \"%s\"\n" | ||
305 | "title = \"%s\"\n" | ||
306 | "tags = \"%s\"\n" | ||
307 | "icon = 0x%x\n" | ||
308 | "created = %.0f # %s\n", | ||
309 | id_Bookmark(bm), | ||
310 | cstrCollect_String(quote_String(&bm->url, iFalse)), | ||
311 | cstrCollect_String(quote_String(&bm->title, iFalse)), | ||
312 | cstrCollect_String(quote_String(&bm->tags, iFalse)), | ||
189 | bm->icon, | 313 | bm->icon, |
190 | seconds_Time(&bm->when), | 314 | seconds_Time(&bm->when), |
191 | cstr_String(&bm->url), | 315 | cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d"))); |
192 | cstr_String(&bm->title), | 316 | if (bm->parentId) { |
193 | cstr_String(&bm->tags)); | 317 | appendFormat_String(str, "parent = %d\n", bm->parentId); |
318 | } | ||
319 | if (bm->order) { | ||
320 | appendFormat_String(str, "order = %d\n", bm->order); | ||
321 | } | ||
322 | appendCStr_String(str, "\n"); | ||
194 | writeData_File(f, cstr_String(str), size_String(str)); | 323 | writeData_File(f, cstr_String(str), size_String(str)); |
195 | } | 324 | } |
196 | } | 325 | } |
@@ -202,7 +331,9 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, | |||
202 | iChar icon) { | 331 | iChar icon) { |
203 | lock_Mutex(d->mtx); | 332 | lock_Mutex(d->mtx); |
204 | iBookmark *bm = new_Bookmark(); | 333 | iBookmark *bm = new_Bookmark(); |
205 | set_String(&bm->url, canonicalUrl_String(url)); | 334 | if (url) { |
335 | set_String(&bm->url, canonicalUrl_String(url)); | ||
336 | } | ||
206 | set_String(&bm->title, title); | 337 | set_String(&bm->title, title); |
207 | if (tags) { | 338 | if (tags) { |
208 | set_String(&bm->tags, tags); | 339 | set_String(&bm->tags, tags); |
@@ -218,16 +349,9 @@ iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { | |||
218 | lock_Mutex(d->mtx); | 349 | lock_Mutex(d->mtx); |
219 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); | 350 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); |
220 | if (bm) { | 351 | if (bm) { |
221 | /* If this is a remote source, make sure all the remote bookmarks are | 352 | /* Remove all the contained bookmarks as well. */ |
222 | removed as well. */ | 353 | iConstForEach(PtrArray, i, list_Bookmarks(d, NULL, filterInsideFolder_Bookmark, bm)) { |
223 | if (hasTag_Bookmark(bm, remoteSource_BookmarkTag)) { | 354 | delete_Bookmark((iBookmark *) remove_Hash(&d->bookmarks, id_Bookmark(i.ptr))); |
224 | iForEach(Hash, i, &d->bookmarks) { | ||
225 | iBookmark *j = (iBookmark *) i.value; | ||
226 | if (j->sourceId == id_Bookmark(bm)) { | ||
227 | remove_HashIterator(&i); | ||
228 | delete_Bookmark(j); | ||
229 | } | ||
230 | } | ||
231 | } | 355 | } |
232 | delete_Bookmark(bm); | 356 | delete_Bookmark(bm); |
233 | } | 357 | } |
@@ -287,6 +411,20 @@ iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { | |||
287 | return (iBookmark *) value_Hash(&d->bookmarks, id); | 411 | return (iBookmark *) value_Hash(&d->bookmarks, id); |
288 | } | 412 | } |
289 | 413 | ||
414 | void reorder_Bookmarks(iBookmarks *d, uint32_t id, int newOrder) { | ||
415 | lock_Mutex(d->mtx); | ||
416 | iForEach(Hash, i, &d->bookmarks) { | ||
417 | iBookmark *bm = (iBookmark *) i.value; | ||
418 | if (id_Bookmark(bm) == id) { | ||
419 | bm->order = newOrder; | ||
420 | } | ||
421 | else if (bm->order >= newOrder) { | ||
422 | bm->order++; | ||
423 | } | ||
424 | } | ||
425 | unlock_Mutex(d->mtx); | ||
426 | } | ||
427 | |||
290 | iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { | 428 | iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { |
291 | iRegExpMatch m; | 429 | iRegExpMatch m; |
292 | init_RegExpMatch(&m); | 430 | init_RegExpMatch(&m); |
@@ -321,6 +459,16 @@ const iPtrArray *list_Bookmarks(const iBookmarks *d, iBookmarksCompareFunc cmp, | |||
321 | return list; | 459 | return list; |
322 | } | 460 | } |
323 | 461 | ||
462 | size_t count_Bookmarks(const iBookmarks *d) { | ||
463 | size_t n = 0; | ||
464 | iConstForEach(Hash, i, &d->bookmarks) { | ||
465 | if (!isFolder_Bookmark((const iBookmark *) i.value)) { | ||
466 | n++; | ||
467 | } | ||
468 | } | ||
469 | return n; | ||
470 | } | ||
471 | |||
324 | const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkListType listType) { | 472 | const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkListType listType) { |
325 | iString *str = collectNew_String(); | 473 | iString *str = collectNew_String(); |
326 | lock_Mutex(d->mtx); | 474 | lock_Mutex(d->mtx); |
@@ -333,21 +481,37 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis | |||
333 | appendFormat_String(str, | 481 | appendFormat_String(str, |
334 | "%s\n\n" | 482 | "%s\n\n" |
335 | "${bookmark.export.saving}\n\n", | 483 | "${bookmark.export.saving}\n\n", |
336 | formatCStrs_Lang("bookmark.export.count.n", size_Hash(&d->bookmarks))); | 484 | formatCStrs_Lang("bookmark.export.count.n", count_Bookmarks(d))); |
337 | } | 485 | } |
338 | else if (listType == listByTag_BookmarkListType) { | 486 | else if (listType == listByTag_BookmarkListType) { |
339 | appendFormat_String(str, "${bookmark.export.taginfo}\n\n"); | 487 | appendFormat_String(str, "${bookmark.export.taginfo}\n\n"); |
340 | } | 488 | } |
341 | iStringSet *tags = new_StringSet(); | 489 | iStringSet *tags = new_StringSet(); |
342 | const iPtrArray *bmList = list_Bookmarks(d, | 490 | const iPtrArray *bmList = |
343 | listType == listByCreationTime_BookmarkListType | 491 | list_Bookmarks(d, |
344 | ? cmpTimeDescending_Bookmark_ | 492 | listType == listByCreationTime_BookmarkListType ? cmpTimeDescending_Bookmark_ |
345 | : cmpTitleAscending_Bookmark_, | 493 | : listType == listByTag_BookmarkListType ? cmpTitleAscending_Bookmark |
346 | NULL, | 494 | : cmpTree_Bookmark, |
347 | NULL); | 495 | NULL, NULL); |
496 | if (listType == listByFolder_BookmarkListType) { | ||
497 | iConstForEach(PtrArray, i, bmList) { | ||
498 | const iBookmark *bm = i.ptr; | ||
499 | if (!isFolder_Bookmark(bm) && !bm->parentId) { | ||
500 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); | ||
501 | } | ||
502 | } | ||
503 | } | ||
348 | iConstForEach(PtrArray, i, bmList) { | 504 | iConstForEach(PtrArray, i, bmList) { |
349 | const iBookmark *bm = i.ptr; | 505 | const iBookmark *bm = i.ptr; |
350 | if (listType == listByFolder_BookmarkListType) { | 506 | if (isFolder_Bookmark(bm)) { |
507 | if (listType == listByFolder_BookmarkListType) { | ||
508 | const int depth = depth_Bookmark(bm); | ||
509 | appendFormat_String(str, "\n%s %s\n", | ||
510 | depth == 0 ? "##" : "###", cstr_String(&bm->title)); | ||
511 | } | ||
512 | continue; | ||
513 | } | ||
514 | if (listType == listByFolder_BookmarkListType && bm->parentId) { | ||
351 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); | 515 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); |
352 | } | 516 | } |
353 | else if (listType == listByCreationTime_BookmarkListType) { | 517 | else if (listType == listByCreationTime_BookmarkListType) { |
@@ -452,7 +616,7 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) { | |||
452 | } | 616 | } |
453 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913); | 617 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913); |
454 | iBookmark *bm = get_Bookmarks(d, bmId); | 618 | iBookmark *bm = get_Bookmarks(d, bmId); |
455 | bm->sourceId = *(uint32_t *) userData_Object(req); | 619 | bm->parentId = *(uint32_t *) userData_Object(req); |
456 | delete_String(titleStr); | 620 | delete_String(titleStr); |
457 | } | 621 | } |
458 | delete_String(urlStr); | 622 | delete_String(urlStr); |