Login
1 branch 0 tags
Ben (Desktop/Arch) Smoother UI bb6b2c1 1 month ago 72 Commits
moon / src / bookmark.c
#include "bookmark.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "storage.h"

#define BOOKMARK_BUF_SIZE 2048

// Format milliseconds as HH:MM:SS
static void format_timestamp(uint32_t ms, char* buf, size_t buf_size) {
	uint32_t total_sec = ms / 1000;
	uint32_t hours = total_sec / 3600;
	uint32_t mins = (total_sec % 3600) / 60;
	uint32_t secs = total_sec % 60;
	snprintf(buf, buf_size, "%02u:%02u:%02u", (unsigned)hours, (unsigned)mins,
	         (unsigned)secs);
}

// Parse HH:MM:SS timestamp to milliseconds
static uint32_t parse_timestamp(const char* ts) {
	unsigned h = 0, m = 0, s = 0;
	if (sscanf(ts, "%u:%u:%u", &h, &m, &s) == 3) {
		return (h * 3600 + m * 60 + s) * 1000;
	}
	return 0;
}

// Read entire bookmark file into buffer. Returns bytes read.
static size_t read_bookmark_file(char* buf, size_t buf_size) {
	storage_file_t f = storage_open(BOOKMARK_FILE, "r");
	if (!f) {
		buf[0] = '\0';
		return 0;
	}
	size_t n = storage_read(f, buf, buf_size - 1);
	storage_close(f);
	buf[n] = '\0';
	return n;
}

// Write buffer contents to bookmark file
static bool write_bookmark_file(const char* buf, size_t len) {
	storage_mkdir(DATA_DIR);
	storage_file_t f = storage_open(BOOKMARK_FILE, "w");
	if (!f) {
		return false;
	}
	size_t written = storage_write(f, buf, len);
	storage_close(f);
	return written == len;
}

uint32_t bookmark_load(const char* path) {
	if (!path || !path[0]) {
		return 0;
	}

	char buf[BOOKMARK_BUF_SIZE];
	if (read_bookmark_file(buf, sizeof(buf)) == 0) {
		return 0;
	}

	// Search for the key line: "path" = "HH:MM:SS"
	char key[STORAGE_MAX_PATH + 4];
	snprintf(key, sizeof(key), "\"%s\"", path);

	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');

		char* found = strstr(line, key);
		if (found && found == line) {
			// Found the key at start of line, find the value
			char* eq = strchr(found, '=');
			if (eq) {
				char* quote1 = strchr(eq, '"');
				if (quote1) {
					quote1++;
					char* quote2 = strchr(quote1, '"');
					if (quote2) {
						char ts[16];
						size_t ts_len = (size_t)(quote2 - quote1);
						if (ts_len < sizeof(ts)) {
							memcpy(ts, quote1, ts_len);
							ts[ts_len] = '\0';
							return parse_timestamp(ts);
						}
					}
				}
			}
		}

		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	return 0;
}

bool bookmark_save(const char* path, uint32_t position_ms) {
	if (!path || !path[0]) {
		return false;
	}

	char buf[BOOKMARK_BUF_SIZE];
	size_t len = read_bookmark_file(buf, sizeof(buf));

	char ts[16];
	format_timestamp(position_ms, ts, sizeof(ts));

	// Build the new line
	char new_line[STORAGE_MAX_PATH + 32];
	int new_line_len =
	    snprintf(new_line, sizeof(new_line), "\"%s\" = \"%s\"\n", path, ts);
	if (new_line_len <= 0 || (size_t)new_line_len >= sizeof(new_line)) {
		return false;
	}

	// Build key to search for
	char key[STORAGE_MAX_PATH + 4];
	snprintf(key, sizeof(key), "\"%s\"", path);

	// Output buffer
	char out[BOOKMARK_BUF_SIZE];
	size_t out_len = 0;
	bool replaced = false;
	bool in_bookmarks = false;

	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');
		size_t line_len = eol ? (size_t)(eol - line + 1) : strlen(line);

		// Check for [bookmarks] section header
		if (strncmp(line, "[bookmarks]", 11) == 0) {
			in_bookmarks = true;
		} else if (line[0] == '[') {
			in_bookmarks = false;
		}

		// Replace existing entry
		if (in_bookmarks && strstr(line, key) == line) {
			if (out_len + (size_t)new_line_len < sizeof(out)) {
				memcpy(out + out_len, new_line, (size_t)new_line_len);
				out_len += (size_t)new_line_len;
				replaced = true;
			}
		} else {
			if (out_len + line_len < sizeof(out)) {
				memcpy(out + out_len, line, line_len);
				out_len += line_len;
			}
		}

		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	// If no existing entry, append
	if (!replaced) {
		if (len == 0) {
			// New file - add section header
			const char* header = "[bookmarks]\n";
			size_t hlen = strlen(header);
			if (out_len + hlen < sizeof(out)) {
				memcpy(out + out_len, header, hlen);
				out_len += hlen;
			}
		}
		if (out_len + (size_t)new_line_len < sizeof(out)) {
			memcpy(out + out_len, new_line, (size_t)new_line_len);
			out_len += (size_t)new_line_len;
		}
	}

	out[out_len] = '\0';
	return write_bookmark_file(out, out_len);
}

bool bookmark_load_string(const char* path,
                          char* out_buf,
                          size_t out_buf_size) {
	if (!path || !path[0] || !out_buf || out_buf_size == 0) {
		return false;
	}

	char buf[BOOKMARK_BUF_SIZE];
	if (read_bookmark_file(buf, sizeof(buf)) == 0) {
		return false;
	}

	char key[STORAGE_MAX_PATH + 4];
	snprintf(key, sizeof(key), "\"%s\"", path);

	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');

		char* found = strstr(line, key);
		if (found && found == line) {
			char* eq = strchr(found, '=');
			if (eq) {
				char* quote1 = strchr(eq, '"');
				if (quote1) {
					quote1++;
					char* quote2 = strchr(quote1, '"');
					if (quote2) {
						size_t val_len = (size_t)(quote2 - quote1);
						if (val_len < out_buf_size) {
							memcpy(out_buf, quote1, val_len);
							out_buf[val_len] = '\0';
							return true;
						}
					}
				}
			}
		}

		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	return false;
}

bool bookmark_save_string(const char* path, const char* value) {
	if (!path || !path[0] || !value) {
		return false;
	}

	char buf[BOOKMARK_BUF_SIZE];
	size_t len = read_bookmark_file(buf, sizeof(buf));

	char new_line[STORAGE_MAX_PATH + STORAGE_MAX_NAME + 16];
	int new_line_len =
	    snprintf(new_line, sizeof(new_line), "\"%s\" = \"%s\"\n", path, value);
	if (new_line_len <= 0 || (size_t)new_line_len >= sizeof(new_line)) {
		return false;
	}

	char key[STORAGE_MAX_PATH + 4];
	snprintf(key, sizeof(key), "\"%s\"", path);

	char out[BOOKMARK_BUF_SIZE];
	size_t out_len = 0;
	bool replaced = false;
	bool in_bookmarks = false;

	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');
		size_t line_len = eol ? (size_t)(eol - line + 1) : strlen(line);

		if (strncmp(line, "[bookmarks]", 11) == 0) {
			in_bookmarks = true;
		} else if (line[0] == '[') {
			in_bookmarks = false;
		}

		if (in_bookmarks && strstr(line, key) == line) {
			if (out_len + (size_t)new_line_len < sizeof(out)) {
				memcpy(out + out_len, new_line, (size_t)new_line_len);
				out_len += (size_t)new_line_len;
				replaced = true;
			}
		} else {
			if (out_len + line_len < sizeof(out)) {
				memcpy(out + out_len, line, line_len);
				out_len += line_len;
			}
		}

		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	if (!replaced) {
		if (len == 0) {
			const char* header = "[bookmarks]\n";
			size_t hlen = strlen(header);
			if (out_len + hlen < sizeof(out)) {
				memcpy(out + out_len, header, hlen);
				out_len += hlen;
			}
		}
		if (out_len + (size_t)new_line_len < sizeof(out)) {
			memcpy(out + out_len, new_line, (size_t)new_line_len);
			out_len += (size_t)new_line_len;
		}
	}

	out[out_len] = '\0';
	return write_bookmark_file(out, out_len);
}

bool bookmark_clear_all(void) {
	return storage_remove(BOOKMARK_FILE);
}

bool bookmark_remove(const char* path) {
	if (!path || !path[0]) {
		return false;
	}

	char buf[BOOKMARK_BUF_SIZE];
	if (read_bookmark_file(buf, sizeof(buf)) == 0) {
		return true;
	}

	char key[STORAGE_MAX_PATH + 4];
	snprintf(key, sizeof(key), "\"%s\"", path);

	char out[BOOKMARK_BUF_SIZE];
	size_t out_len = 0;
	bool in_bookmarks = false;

	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');
		size_t line_len = eol ? (size_t)(eol - line + 1) : strlen(line);

		if (strncmp(line, "[bookmarks]", 11) == 0) {
			in_bookmarks = true;
		} else if (line[0] == '[') {
			in_bookmarks = false;
		}

		// Skip matching entry
		if (in_bookmarks && strstr(line, key) == line) {
			// Skip this line
		} else {
			if (out_len + line_len < sizeof(out)) {
				memcpy(out + out_len, line, line_len);
				out_len += line_len;
			}
		}

		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	out[out_len] = '\0';
	return write_bookmark_file(out, out_len);
}