summaryrefslogtreecommitdiff
path: root/src/bookmarks.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bookmarks.c')
-rw-r--r--src/bookmarks.c228
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
35void init_Bookmark(iBookmark *d) { 36void 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
43void deinit_Bookmark(iBookmark *d) { 45void 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
80static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) { 82int 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
86iBool filterInsideFolder_Bookmark(void *context, const iBookmark *bm) {
87 return hasParent_Bookmark(bm, id_Bookmark(context));
88}
89
84/*----------------------------------------------------------------------------------------------*/ 90/*----------------------------------------------------------------------------------------------*/
85 91
86static const char *fileName_Bookmarks_ = "bookmarks.txt"; 92static const char *oldFileName_Bookmarks_ = "bookmarks.txt";
93static const char *fileName_Bookmarks_ = "bookmarks.ini"; /* since v1.7 (TOML subset) */
87 94
88struct Impl_Bookmarks { 95struct 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
133static void insertId_Bookmarks_(iBookmarks *d, iBookmark *bookmark, int id) {
134 bookmark->node.key = id;
135 insert_Hash(&d->bookmarks, &bookmark->node);
136}
137
126static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { 138static 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
133void load_Bookmarks(iBookmarks *d, const char *dirPath) { 144static 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
185iDeclareType(BookmarkLoader)
186
187struct Impl_BookmarkLoader {
188 iTomlParser *toml;
189 iBookmarks * bookmarks;
190 iBookmark * bm;
191};
192
193static 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
207static 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
237static 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
244static void deinit_BookmarkLoader(iBookmarkLoader *d) {
245 delete_TomlParser(d->toml);
246}
247
248static 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
254iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b)
255
256/*----------------------------------------------------------------------------------------------*/
257
258static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) {
259 return bm->parentId == *(const uint32_t *) context;
260}
261
262void 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
271void 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
173void save_Bookmarks(const iBookmarks *d, const char *dirPath) { 288void 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
414void 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
290iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { 428iBool 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
462size_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
324const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkListType listType) { 472const 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);