db.c (6034B)
1 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <sqlite3.h> 6 7 #include "db.h" 8 #include "utils.h" 9 #include "config.h" 10 11 static sqlite3* db = NULL; 12 13 void 14 db_create(void) 15 { 16 const char* dbpath = expand_tilde(DB_PATH); 17 18 const char* create_table = "CREATE TABLE IF NOT EXISTS posts (" 19 " id INTEGER PRIMARY KEY AUTOINCREMENT," 20 " title TEXT NOT NULL," 21 " link TEXT NOT NULL," 22 " feed_url TEXT NOT NULL," 23 " comments TEXT NULL," 24 " pub_date TEXT," 25 " summary TEXT," 26 " seen INTEGER DEFAULT 0," 27 " UNIQUE(feed_url, link)" 28 ");"; 29 30 if (sqlite3_open(dbpath, &db) != SQLITE_OK) { 31 fprintf(stderr, "could not open/create db file at path %s\n", dbpath); 32 exit(1); 33 } 34 35 if (sqlite3_exec(db, create_table, NULL, NULL, NULL) != SQLITE_OK) { 36 fprintf(stderr, "could not create table in db at path %s\n", DB_PATH); 37 exit(1); 38 } 39 } 40 41 void 42 db_insert_post(db_post_t dbp) 43 { 44 if (db == NULL) { 45 fprintf(stderr, "fetch_posts: bad args / missing db\n"); 46 exit(1); 47 } 48 if (dbp.link == NULL) { 49 fprintf(stderr, "trying to insert a dbpost without a link!\n"); 50 exit(1); 51 } else if (dbp.title == NULL) { 52 fprintf(stderr, "trying to insert a dbpost without a title.\n"); 53 exit(1); 54 } else if (dbp.feed_url == NULL) { 55 fprintf(stderr, "trying to insert a dbpost without a feed.\n"); 56 exit(1); 57 } 58 59 sqlite3_stmt* ins = NULL; 60 const char* ins_sql = "INSERT INTO posts(title, link, feed_url, comments, pub_date, summary, seen)" 61 " VALUES(?, ?, ?, ?, ?, ?, ?) ON CONFLICT(feed_url, link) DO NOTHING;"; 62 63 int rc = sqlite3_prepare_v2(db, ins_sql, -1, &ins, NULL); 64 if (rc != SQLITE_OK) { 65 fprintf(stderr, "prepare failed: %s\n", sqlite3_errmsg(db)); 66 exit(1); 67 } 68 69 sqlite3_reset(ins); 70 71 sqlite3_clear_bindings(ins); 72 73 if (sqlite3_bind_text(ins, 1, dbp.title, -1, SQLITE_TRANSIENT) != SQLITE_OK) 74 perror("failed db insertion of: title"); 75 76 if (sqlite3_bind_text(ins, 2, dbp.link, -1, SQLITE_TRANSIENT) != SQLITE_OK) 77 perror("failed db insertion of: link"); 78 79 if (sqlite3_bind_text(ins, 3, dbp.feed_url, -1, SQLITE_TRANSIENT) != SQLITE_OK) 80 perror("failed db insertion of: feed_url"); 81 82 if (sqlite3_bind_text(ins, 4, dbp.comments ? dbp.comments : NULL, -1, SQLITE_TRANSIENT) != SQLITE_OK) 83 perror("failed db insertion of: comments"); 84 85 if (sqlite3_bind_text(ins, 5, dbp.pub_date ? dbp.pub_date : NULL, -1, SQLITE_TRANSIENT) != SQLITE_OK) 86 perror("failed db insertion of: pub_date"); 87 88 if (sqlite3_bind_text(ins, 6, dbp.summary ? dbp.summary : NULL, -1, SQLITE_TRANSIENT) != SQLITE_OK) 89 perror("failed db insertion of: summary"); 90 91 if (sqlite3_bind_int(ins, 7, dbp.seen ? dbp.seen : 0) != SQLITE_OK) 92 perror("failed db insertion of: seen"); 93 94 rc = sqlite3_step(ins); 95 if (rc != SQLITE_DONE) { 96 printf("%s\n", dbp.link); 97 fprintf(stderr, "step failed: %s\n", sqlite3_errmsg(db)); 98 } else { 99 int changes = sqlite3_changes(db); 100 if (changes == 0) { 101 // fprintf(stderr, "no change\n"); 102 } else { 103 // sqlite3_int64 rowid = sqlite3_last_insert_rowid(db); 104 // fprintf(stderr, "inserted rowid = %lld\n", (long long)rowid); 105 } 106 } 107 108 sqlite3_finalize(ins); 109 } 110 111 static char* 112 dup_col(sqlite3_stmt* st, int i) 113 { 114 const unsigned char* t = sqlite3_column_text(st, i); 115 if (!t) return NULL; 116 size_t n = strlen((const char*)t); 117 char* s = (char*)ecalloc(n + 1, sizeof(char)); 118 if (!s) return NULL; 119 memcpy(s, t, n + 1); 120 return s; 121 } 122 123 db_fetch_post_t 124 db_fetch_posts(const char* feed_url) 125 { 126 if (!db || !feed_url) { 127 fprintf(stderr, "fetch_posts: bad args / missing db\n"); 128 return (db_fetch_post_t) { .success = 0 }; 129 } 130 131 const char* sql = "SELECT id, title, link, feed_url, comments, pub_date, summary, seen " 132 "FROM posts WHERE feed_url = ? " 133 "ORDER BY id DESC LIMIT ?;"; 134 135 sqlite3_stmt* st = NULL; 136 if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) { 137 fprintf(stderr, "prepare failed: %s\n", sqlite3_errmsg(db)); 138 return (db_fetch_post_t) { .success = 0 }; 139 } 140 sqlite3_bind_text(st, 1, feed_url, -1, SQLITE_TRANSIENT); 141 sqlite3_bind_int(st, 2, MAX_POST_PER_FEED); 142 143 int len = 0, cap = 0; 144 db_post_t** rows = NULL; 145 146 while (sqlite3_step(st) == SQLITE_ROW) { 147 db_post_t* p = ecalloc(1, sizeof(db_post_t)); 148 p->id = sqlite3_column_int(st, 0); 149 p->title = dup_col(st, 1); 150 p->link = dup_col(st, 2); 151 p->feed_url = dup_col(st, 3); 152 p->comments = dup_col(st, 4); 153 p->pub_date = dup_col(st, 5); 154 p->summary = dup_col(st, 6); 155 p->seen = sqlite3_column_int(st, 7); 156 if (len == cap) { 157 cap = cap ? cap * 2 : MAX_POST_PER_FEED; 158 rows = (db_post_t**)realloc(rows, sizeof(db_post_t*) * (size_t)cap); 159 if (rows == NULL) { 160 fprintf(stderr, "could not reallocate for list of posts fetch from db\n"); 161 break; 162 } 163 } 164 rows[len++] = p; 165 } 166 167 int rc = sqlite3_finalize(st); 168 169 if (rc != SQLITE_OK) { 170 fprintf(stderr, "finalize: %s\n", sqlite3_errmsg(db)); 171 for (int i = 0; i < len; i++) { 172 free(rows[i]->title); 173 free(rows[i]->link); 174 free(rows[i]->feed_url); 175 free(rows[i]->comments); 176 free(rows[i]->pub_date); 177 free(rows[i]->summary); 178 } 179 free(rows); 180 return (db_fetch_post_t) { .success = 0 }; 181 } 182 183 return (db_fetch_post_t) { 184 .posts = rows, 185 .count = len, 186 .success = 1, 187 }; 188 } 189 190 int 191 db_mark_as_seen(int id) 192 { 193 if (!db) { 194 fprintf(stderr, "db_mark_as_seen: missing db\n"); 195 return 1; 196 } 197 198 const char* sql = "UPDATE posts SET seen=1 WHERE id=?;"; 199 sqlite3_stmt* st = NULL; 200 201 if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) { 202 fprintf(stderr, "db_mark_as_seen: prepare: %s\n", sqlite3_errmsg(db)); 203 return 1; 204 } 205 if (sqlite3_bind_int(st, 1, id) != SQLITE_OK) { 206 fprintf(stderr, "db_mark_as_seen: bind id: %s\n", sqlite3_errmsg(db)); 207 sqlite3_finalize(st); 208 return 1; 209 } 210 211 int rc = sqlite3_step(st); 212 if (rc != SQLITE_DONE) { 213 fprintf(stderr, "db_mark_as_seen: step: %s\n", sqlite3_errmsg(db)); 214 sqlite3_finalize(st); 215 return 1; 216 } 217 218 sqlite3_finalize(st); 219 return 0; 220 } 221 222 void 223 db_close(void) 224 { 225 if (sqlite3_close(db) != SQLITE_OK) { 226 fprintf(stderr, "prepare insert failed\n"); 227 exit(1); 228 } 229 }