0 /*
1 * ISC License
2 * Copyright (c) 2023 RMF <rawmonk@firemail.cc>
3 */
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <string.h>
8 #include <wchar.h>
9 #include <pthread.h>
10 #include "macro.h"
11 #include "strlcpy.h"
12 #include "error.h"
13 #include "config.h"
14 #include "utf8.h"
15 #include "url.h"
16 #include "storage.h"
17 #define HISTORY_INTERNAL
18 #include "history.h"
19
20 #define HISTORY "history.txt"
21 pthread_mutex_t history_mutex = PTHREAD_MUTEX_INITIALIZER;
22
23 struct history_entry *history = NULL;
24
25 int history_load(const char *path) {
26
27 struct history_entry entry = {0};
28 struct history_entry *last = NULL;
29 FILE *f;
30 unsigned int i, part, count;
31
32 if (!config.maximumHistorySize) return 0;
33
34 if (!(f = storage_fopen(path, "r"))) return ERROR_STORAGE_ACCESS;
35
36 count = i = part = 0;
37 for (;;) {
38 uint32_t ch;
39 char *ptr;
40 if (utf8_fgetc(f, &ch)) break;
41 if (ch == '\n') {
42 struct history_entry *new;
43 i = 0;
44 part = 0;
45 new = malloc(sizeof(*new));
46 if (!new) {
47 fclose(f);
48 return ERROR_MEMORY_FAILURE;
49 }
50 *new = entry;
51 if (last) {
52 last->next = new;
53 } else {
54 new->next = NULL;
55 history = new;
56 }
57 last = new;
58 memset(&entry, 0, sizeof(entry));
59 count++;
60 if (count >= (unsigned)config.maximumHistorySize)
61 break;
62 continue;
63 }
64 if (!part && ch <= ' ') {
65 part = 1;
66 i = 0;
67 continue;
68 }
69 if (part == 1) {
70 if (ch <= ' ') continue;
71 part = 2;
72 }
73 if (i >= (part ? sizeof(entry.title) : sizeof(entry.url))) {
74 continue;
75 }
76 ptr = part ? entry.title : entry.url;
77 i += utf8_unicode_to_char(&ptr[i], ch);
78 }
79 fclose(f);
80 return 0;
81 }
82
83 void history_init() {
84 if (!config.enableHistory) return;
85 history_load(HISTORY);
86 }
87
88 int history_write(const char *path) {
89
90 FILE *f;
91 struct history_entry *entry;
92
93 f = storage_fopen(path, "w");
94 if (!f) return -1;
95 for (entry = history; entry; entry = entry->next) {
96 fprintf(f, "%s %s\n", entry->url, entry->title);
97 }
98 fclose(f);
99
100 return 0;
101 }
102
103 int history_clear() {
104 history_free();
105 history = NULL;
106 return history_save();
107 }
108
109 int history_save() {
110 return history_write(HISTORY);
111 }
112
113 int history_add(const char *url, const char *title) {
114
115 struct history_entry *entry;
116
117 if (!config.enableHistory) return 0;
118 entry = malloc(sizeof(*entry));
119 if (!entry) return ERROR_MEMORY_FAILURE;
120 url_hide_query(title, V(entry->title));
121 if (strstr(url, "gemini://") == url) {
122 url_hide_query(url, V(entry->url));
123 } else {
124 UTF8CPY(entry->title, title);
125 }
126 pthread_mutex_lock(&history_mutex);
127 entry->next = history;
128 history = entry;
129 pthread_mutex_unlock(&history_mutex);
130 return 0;
131 }
132
133 void history_free() {
134 struct history_entry *next;
135 while (history) {
136 next = history->next;
137 free(history);
138 history = next;
139 }
140 }
141