Login
1 branch 0 tags
Ben (Desktop/Arch) Fixed audiobooks c5958d5 1 month ago 34 Commits
moon / firmware / src / bookmark.c
#include "bookmark.h"
#include "storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BOOKMARK_DIR ".onigiri"
#define BOOKMARK_FILE ".onigiri/state.toml"
#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(BOOKMARK_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_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);
}