view seobeo/snapshot_creator.c @ 71:75de5903355c

Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
author June Park <parkjune1995@gmail.com>
date Sun, 28 Dec 2025 20:34:22 -0800
parents 6626ec933933
children e7899c93da77
line wrap: on
line source

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

#define MAX_RESPONSE_SIZE (1024 * 1024)

// Helper: Convert URL path to filename
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: Generate HTTP GET request
static int generate_http_get(char *buffer, size_t size, const char *path, const char *host)
{
  return snprintf(
    buffer, size,
    "GET %s HTTP/1.1\r\n"
    "Host: %s\r\n"
    "Connection: close\r\n"
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
    "User-Agent: SeobeoSnapshotCreator/1.0\r\n"
    "\r\n",
    path, host
  );
}

// Helper: Read full HTTP response
static char* read_full_response(Seobeo_Handle *client, size_t *len_out)
{
  char *response = malloc(MAX_RESPONSE_SIZE);
  if (!response)
  {
    return NULL;
  }

  size_t total = 0;
  int attempts = 0;
  const int max_attempts = 100;

  while (attempts++ < max_attempts && total < MAX_RESPONSE_SIZE - 1)
  {
    int n = Seobeo_Handle_Read(client);

    if (n > 0)
    {
      size_t to_copy = client->read_buffer_len;
      if (total + to_copy > MAX_RESPONSE_SIZE - 1)
      {
        to_copy = MAX_RESPONSE_SIZE - 1 - total;
      }

      memcpy(response + total, client->read_buffer, to_copy);
      total += to_copy;
      Seobeo_Handle_Consume(client, (uint32)to_copy);
    }
    else if (n == -2)
    {
      break;
    }
    else if (n == 0)
    {
      usleep(10000);
      continue;
    }
    else
    {
      free(response);
      return NULL;
    }
  }

  response[total] = '\0';
  *len_out = total;
  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);

  // Connect to server
  Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(config->host, config->port, FALSE);
  if (!client || client->socket < 0)
  {
    fprintf(stderr, "  ✗ Failed to connect to %s:%s\n", config->host, config->port);
    if (client)
    {
      Seobeo_Handle_Destroy(client);
    }
    return -1;
  }

  // Send GET request
  char request[4096];
  int req_len = generate_http_get(request, sizeof(request), config->path, config->host);
  Seobeo_Handle_Queue(client, (uint8*)request, (uint32)req_len);

  if (Seobeo_Handle_Flush(client) < 0)
  {
    fprintf(stderr, "  ✗ Failed to send request\n");
    Seobeo_Handle_Destroy(client);
    return -1;
  }

  // Read response
  size_t response_len = 0;
  char *response = read_full_response(client, &response_len);
  Seobeo_Handle_Destroy(client);

  if (!response || response_len == 0)
  {
    fprintf(stderr, "  ✗ Failed to read response\n");
    if (response)
    {
      free(response);
    }
    return -1;
  }

  // Parse status code
  int status = -1;
  const char *status_line = strstr(response, "HTTP/1.1 ");
  if (!status_line)
  {
    status_line = strstr(response, "HTTP/1.0 ");
  }
  if (status_line)
  {
    sscanf(status_line + 9, "%d", &status);
  }

  if (status != config->expected_status)
  {
    fprintf(stderr, "  ✗ Status mismatch: expected %d, got %d\n",
            config->expected_status, status);
    free(response);
    return -1;
  }

  printf("  ✓ Status: %d\n", status);

  // 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, response, response_len) == 0)
  {
    printf("  ✓ Snapshot saved: %s (%zu bytes)\n", filepath, response_len);
    free(response);
    return 0;
  }
  else
  {
    fprintf(stderr, "  ✗ Failed to write snapshot: %s\n", filepath);
    free(response);
    return -1;
  }
}

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;
}