text: /**
    Onion HTTP server library
    Copyright (C) 2010-2018 David Moreno Montero and others
  
    This library is free software; you can redistribute it and/or
    modify it under the terms of, at your choice:
  
    a. the Apache License Version 2.0.
  
    b. the GNU General Public License as published by the
    Free Software Foundation; either version 2.0 of the License,
    or (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of both licenses, if not see
    <http://www.gnu.org/licenses/> and
    <http://www.apache.org/licenses/LICENSE-2.0>.
  */
  
  #include <stdlib.h>
  #include <onion/log.h>
  #include <assert.h>
  #include <string.h>
  #include <stdio.h>
  #include <ctype.h>
  #include <libgen.h>
  #include <stdarg.h>
  
  #include "updateassets.h"
  
  #define INSERT_HERE_MARK "// New data will be inserted over this line. DO NOT MODIFY (SPECIALLY THIS LINE)."
  
  struct onion_assets_file_t {
    char *filename;
    char **head;
    char **tail;
    char **lines;
    int lines_count;
    int lines_capacity;
  };
  
  // Forces add.
  void onion_assets_file_add_line(onion_assets_file * f, const char *line);
  void onion_assets_file_add_linef(onion_assets_file * f, const char *line, ...);
  
  void onion_assets_file_add_linef(onion_assets_file * f, const char *line, ...) {
    char tmp[1024];
    va_list ap;
    va_start(ap, line);
    vsnprintf(tmp, sizeof(tmp), line, ap);
    va_end(ap);
    onion_assets_file_add_line(f, tmp);
  }
  
  // Converts the string to a valid define like string.
  static char *to_define_able(const char *str) {
    char *FILENAME = strdup(str);
    int i = 0;
    while (FILENAME[i]) {
      if (isalpha(FILENAME[i]))
        FILENAME[i] = toupper(FILENAME[i]);
      else if (isdigit(FILENAME[i]))
        FILENAME[i] = FILENAME[i];
      else
        FILENAME[i] = '_';
  
      i++;
    }
    return FILENAME;
  }
  
  static void onion_assets_file_set_head(onion_assets_file * f) {
    ONION_DEBUG("Set header mark");
    onion_assets_file_add_line(f, NULL);
    f->head = f->lines;
    f->lines_capacity = 16;
    f->lines_count = 0;
    f->lines = malloc(sizeof(const char *) * f->lines_capacity);
  }
  static void onion_assets_file_set_tail(onion_assets_file * f) {
    ONION_DEBUG("Found tail mark");
    onion_assets_file_add_line(f, NULL);
    f->tail = f->lines;
    f->lines_capacity = 16;
    f->lines_count = 0;
    f->lines = malloc(sizeof(const char *) * f->lines_capacity);
  }
  
  void onion_assets_file_add_line(onion_assets_file * f, const char *line) {
    if (line && !f->head && strcmp(line, INSERT_HERE_MARK) == 0) {
      onion_assets_file_set_head(f);
    }
  
    if (line) {                   // Check line not yet in.
      if (f->head) {
        char **ll = f->head;
        while (*ll) {
          if (strcmp(*ll, line) == 0)
            return;
          ++ll;
        }
      }
  
      int i;
      for (i = 0; i < f->lines_count; i++) {
        if (strcmp(f->lines[i], line) == 0)       // Already in
          return;
      }
    }
  
    if (f->lines_count == f->lines_capacity) {
      if (f->lines_capacity < 2048)
        f->lines_capacity *= 2;
      else
        f->lines_capacity += 2048;
      f->lines = realloc(f->lines, sizeof(const char *) * f->lines_capacity);
    }
    if (line)
      f->lines[f->lines_count++] = strdup(line);
    else
      f->lines[f->lines_count++] = NULL;
    ONION_DEBUG("Add line: %s", line);
  }
  
  onion_assets_file *onion_assets_file_new(const char *filename) {
    onion_assets_file *ret = malloc(sizeof(onion_assets_file));
    FILE *file = fopen(filename, "rt");
    ret->filename = strdup(filename);
    ret->lines_count = 0;
    ret->lines_capacity = 16;
    ret->lines = malloc(sizeof(const char *) * ret->lines_capacity);
    ret->head = NULL;
    ret->tail = NULL;
  
    if (file) {
      char buffer[4096];
      int r = 0;
      int o = 0;
      int total = 0;
      do {
        r = fread(&buffer[o], 1, sizeof(buffer) - o, file);
        int i;
        o = 0;
        for (i = 0; i < r; i++) {
          if (buffer[i] == '\n') {
            buffer[i] = 0;
            ONION_DEBUG("Read original line <%s>", &buffer[o]);
            onion_assets_file_add_line(ret, &buffer[o]);
            total += strlen(&buffer[o]);
            o = i + 1;
          }
        }
      } while (r > 0);
      onion_assets_file_set_tail(ret);
      fclose(file);
      if (total > 0) {
        return ret;
      }
    }
  
    char *FILENAME = to_define_able(filename);
  
    onion_assets_file_add_line(ret, "/* Autogenerated by onion assets */");
    onion_assets_file_add_linef(ret, "#ifndef __ASSETS_H__%s", FILENAME);
    onion_assets_file_add_linef(ret, "#define __ASSETS_H__%s", FILENAME);
    onion_assets_file_add_line(ret, "# ifdef __cplusplus");
    onion_assets_file_add_line(ret, "extern \"C\"{");
    onion_assets_file_add_line(ret, "# endif");
    onion_assets_file_add_line(ret, "#include <onion/types.h>");
    onion_assets_file_add_line(ret, INSERT_HERE_MARK);
    onion_assets_file_add_line(ret, "# ifdef __cplusplus ");      // Space trick important to allow to dup line.
    onion_assets_file_add_line(ret, "} // extern \"C\"");
    onion_assets_file_add_line(ret, "# endif ");
    onion_assets_file_add_line(ret, "#endif");
  
    onion_assets_file_set_tail(ret);
  
    free(FILENAME);
    return ret;
  }
  
  int onion_assets_file_update(onion_assets_file * file, const char *line) {
    int i;
    for (i = 0; i < file->lines_count; i++) {
      if (strcmp(file->lines[i], line) == 0)
        return 0;
    }
    onion_assets_file_add_line(file, line);
    return 1;
  }
  
  static void onion_assets_write_and_free_until_null(char **lines, FILE * file) {
    char **ll = lines;
    while (*ll) {
      char *l = *ll;
      ONION_DEBUG("Write: %s", l);
      ssize_t length = strlen(l);
      ssize_t wlength = fwrite(l, 1, length, file);
      if (wlength != length) {
        ONION_ERROR("Could not write all data. Aborting");
        abort();
      }
      wlength = fwrite("\n", 1, 1, file);
      if (wlength != 1) {
        ONION_ERROR("Could not write all data. Aborting");
        abort();
      }
      free(l);
      ++ll;
    }
    free(lines);
  }
  
  int onion_assets_file_free(onion_assets_file * f) {
    onion_assets_file_add_line(f, NULL);  // Seal this lines list.
  
    FILE *file = fopen(f->filename, "wt");
    if (!file) {
      ONION_WARNING("Could not open %s asset file for updating.", f->filename);
      return -1;
    }
  
    if (f->head)
      onion_assets_write_and_free_until_null(f->head, file);
    onion_assets_write_and_free_until_null(f->lines, file);
    if (f->tail)
      onion_assets_write_and_free_until_null(f->tail, file);
  
    assert(fclose(file) == 0);
    free(f->filename);
    free(f);
    return 0;
  }
  