readr

Minimal Terminal RSS Reader
Log | Files | Refs | README | LICENSE

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 }