view seobeo/snapshot_creator.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 7eb79fd91c7e
children
line wrap: on
line source

#include "seobeo/snapshot_creator.h"
#include "seobeo/seobeo_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static void path_to_filename(const char *path, char *filename, size_t max_len)
{
  if (strcmp(path, "/") == 0)
  {
    snprintf(filename, max_len, "root.snapshot");
    return;
  }

  const char *p = path;
  if (*p == '/')
    p++;

  char *out = filename;
  size_t remaining = max_len - 1;

  while (*p && remaining > 0)
  {
    if (*p == '/')
    {
      *out++ = '_';
      remaining--;
    }
    else
    {
      *out++ = *p;
      remaining--;
    }
    p++;
  }

  snprintf(out, remaining, ".snapshot");
}

// Helper: Create directory recursively
static int create_directory(const char *path)
{
  char tmp[1024];
  char *p = NULL;
  size_t len;

  snprintf(tmp, sizeof(tmp), "%s", path);
  len = strlen(tmp);
  if (tmp[len - 1] == '/')
  {
    tmp[len - 1] = 0;
  }

  for (p = tmp + 1; *p; p++)
  {
    if (*p == '/')
    {
      *p = 0;
      mkdir(tmp, 0755);
      *p = '/';
    }
  }
  return mkdir(tmp, 0755);
}

// Helper: Build full HTTP response from Seobeo_Client_Response
static char* build_full_response(Seobeo_Client_Response *p_resp, size_t *len_out)
{
  if (!p_resp)
    return NULL;

  // Calculate total response size
  size_t status_line_len = 100; // "HTTP/1.1 200 OK\r\n"
  size_t headers_len = 0;
  size_t body_len = p_resp->body_length;

  // Estimate header size
  if (p_resp->headers)
  {
    size_t header_count = Dowa_Array_Length(p_resp->headers);
    for (size_t i = 0; i < header_count; i++)
    {
      headers_len += strlen(p_resp->headers[i].key) + strlen(p_resp->headers[i].value) + 4; // ": " + "\r\n"
    }
  }

  size_t total_size = status_line_len + headers_len + 2 + body_len + 1; // +2 for "\r\n", +1 for null terminator
  char *response = malloc(total_size);
  if (!response)
    return NULL;

  // Build status line
  int offset = snprintf(response, total_size, "HTTP/1.1 %d %s\r\n",
                        p_resp->status_code,
                        p_resp->status_text ? p_resp->status_text : "OK");

  // Add headers
  if (p_resp->headers)
  {
    size_t header_count = Dowa_Array_Length(p_resp->headers);
    for (size_t i = 0; i < header_count; i++)
    {
      offset += snprintf(response + offset, total_size - offset, "%s: %s\r\n",
                        p_resp->headers[i].key,
                        p_resp->headers[i].value);
    }
  }

  // End of headers
  offset += snprintf(response + offset, total_size - offset, "\r\n");

  // Add body
  if (p_resp->body && body_len > 0)
  {
    memcpy(response + offset, p_resp->body, body_len);
    offset += body_len;
  }

  response[offset] = '\0';
  *len_out = offset;
  return response;
}

// Helper: Write snapshot to file
static int write_snapshot(const char *filepath, const char *data, size_t size)
{
  // Create directory if needed
  char dir_copy[1024];
  snprintf(dir_copy, sizeof(dir_copy), "%s", filepath);

  char *last_slash = strrchr(dir_copy, '/');
  if (last_slash)
  {
    *last_slash = '\0';
    create_directory(dir_copy);
  }

  FILE *f = fopen(filepath, "wb");
  if (!f)
  {
    perror("fopen");
    return -1;
  }

  size_t written = fwrite(data, 1, size, f);
  fclose(f);

  return (written == size) ? 0 : -1;
}

int Seobeo_Snapshot_Create(const SnapshotConfig *config)
{
  if (!config || !config->path || !config->snapshot_dir || !config->host || !config->port)
  {
    fprintf(stderr, "Invalid snapshot config\n");
    return -1;
  }

  printf("Creating snapshot: %s (expecting %d)\n", config->path, config->expected_status);

  // Build URL
  char url[2048];
  snprintf(url, sizeof(url), "http://%s:%s%s", config->host, config->port, config->path);

  // Create HTTP request
  Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url);
  if (!p_req)
  {
    fprintf(stderr, "  ✗ Failed to create request for %s\n", url);
    return -1;
  }

  // Execute request
  Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req);
  if (!p_resp)
  {
    fprintf(stderr, "  ✗ Failed to get response\n");
    Seobeo_Client_Request_Destroy(p_req);
    return -1;
  }

  // Check status code
  if (p_resp->status_code != config->expected_status)
  {
    fprintf(stderr, "  ✗ Status mismatch: expected %d, got %d\n",
            config->expected_status, p_resp->status_code);
    // fprintf(stderr, "%s", p_resp->body);
    Seobeo_Client_Response_Destroy(p_resp);
    Seobeo_Client_Request_Destroy(p_req);
    return -1;
  }

  printf("  ✓ Status: %d\n", p_resp->status_code);

  // Generate snapshot filename
  char filename[256];
  path_to_filename(config->path, filename, sizeof(filename));

  char filepath[1024];
  snprintf(filepath, sizeof(filepath), "%s/%s", config->snapshot_dir, filename);

  // Write snapshot
  if (write_snapshot(filepath, p_resp->body, p_resp->body_length) == 0)
  {
    printf("  ✓ Snapshot saved: %s (%zu bytes)\n", filepath, p_resp->body_length);
    return 0;
  }
  else
  {
    fprintf(stderr, "  ✗ Failed to write snapshot: %s\n", filepath);
    return -1;
  }

  Seobeo_Client_Response_Destroy(p_resp);
  Seobeo_Client_Request_Destroy(p_req);
}

int Seobeo_Snapshots_Create_Batch(const SnapshotConfig configs[], int count)
{
  int failed = 0;
  int passed = 0;

  for (int i = 0; i < count; i++)
  {
    if (Seobeo_Snapshot_Create(&configs[i]) == 0)
      passed++;
    else
      failed++;
    printf("\n");
  }

  printf("=== Summary ===\n");
  printf("Created: %d\n", passed);
  printf("Failed: %d\n", failed);

  return (failed == 0) ? 0 : -1;
}