diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 34 | ||||
-rw-r--r-- | src/build.c | 116 | ||||
-rw-r--r-- | src/build.h | 6 | ||||
-rw-r--r-- | src/cgci.c | 53 | ||||
-rw-r--r-- | src/cgci.h | 7 | ||||
-rw-r--r-- | src/config.c | 158 | ||||
-rw-r--r-- | src/config.h | 52 | ||||
-rw-r--r-- | src/context.c | 67 | ||||
-rw-r--r-- | src/context.h | 32 | ||||
-rw-r--r-- | src/env.c | 12 | ||||
-rw-r--r-- | src/env.h | 6 | ||||
-rw-r--r-- | src/fs.c | 141 | ||||
-rw-r--r-- | src/fs.h | 9 | ||||
-rw-r--r-- | src/parser.c | 312 | ||||
-rw-r--r-- | src/parser.h | 8 | ||||
-rw-r--r-- | src/ui.c | 390 | ||||
-rw-r--r-- | src/ui.h | 26 |
17 files changed, 1429 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..9f7e945 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,34 @@ + + +list(APPEND + SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/build.c + ${CMAKE_CURRENT_SOURCE_DIR}/build.h + ${CMAKE_CURRENT_SOURCE_DIR}/cgci.c + ${CMAKE_CURRENT_SOURCE_DIR}/cgci.h + ${CMAKE_CURRENT_SOURCE_DIR}/config.c + ${CMAKE_CURRENT_SOURCE_DIR}/config.h + ${CMAKE_CURRENT_SOURCE_DIR}/context.c + ${CMAKE_CURRENT_SOURCE_DIR}/context.h + ${CMAKE_CURRENT_SOURCE_DIR}/env.c + ${CMAKE_CURRENT_SOURCE_DIR}/env.h + ${CMAKE_CURRENT_SOURCE_DIR}/fs.c + ${CMAKE_CURRENT_SOURCE_DIR}/fs.h + ${CMAKE_CURRENT_SOURCE_DIR}/parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/parser.h + ${CMAKE_CURRENT_SOURCE_DIR}/ui.c + ${CMAKE_CURRENT_SOURCE_DIR}/ui.h +) + +set(CFLAGS + -Wall -Wextra -pedantic + -Wconversion -Wshadow -Wstrict-aliasing + -Winit-self -Wcast-align -Wpointer-arith + -Wmissing-declarations -Wmissing-include-dirs + -Wno-unused-parameter -Wuninitialized +) + +set(CMAKE_EXECUTABLE_SUFFIX ".cgi") +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) + +target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC ${CFLAGS}) diff --git a/src/build.c b/src/build.c new file mode 100644 index 0000000..4c7667d --- /dev/null +++ b/src/build.c @@ -0,0 +1,116 @@ +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include "config.h" +#include "fs.h" +#include "build.h" + +static void write_build(char* project, char* build_id, struct build_t* build) +{ + char* build_path = build_dir(project, build_id); + if (!build_path) + return; + + if (!isDir(build_path)) + makeDir(build_path); + + size_t build_size = strlen(build_path); + build_path = realloc(build_path, (build_size + 11 + 1) * sizeof(char)); + + FILE* fd; + + strcat(build_path, "/timestamp"); + fd = fopen(build_path, "wb"); + if (fd) + { + fwrite(&build->timestamp, sizeof(time_t), 1, fd); + fclose(fd); + } + build_path[build_size] = '\0'; + + strcat(build_path, "/completion"); + fd = fopen(build_path, "wb"); + if (fd) + { + fwrite(&build->completion, sizeof(build->completion), 1, fd); + fclose(fd); + } + build_path[build_size] = '\0'; + + strcat(build_path, "/status"); + fd = fopen(build_path, "wb"); + if (fd) + { + fwrite(&build->status, sizeof(build->status), 1, fd); + fclose(fd); + } + build_path[build_size] = '\0'; + + free(build_path); + +} + +void create_build() +{ + if (!current_project) + return; + + int build_id = 0; + + if (current_project->build_count > 0) + build_id = atoi(current_project->builds[0].name)+1; + + if (build_id <= 0) + build_id = 1; + + struct build_t build; + build.name = NULL; + build.timestamp = time(NULL); + build.completion = 0; + build.status = STATUS_INPROGRESS; + + size_t name_len = 1; + int temp = build_id; + + while (temp > 10) + { + temp /= 10; + name_len++; + } + + build.name = malloc((name_len + 1) * sizeof(char)); + sprintf(build.name, "%d", build_id); + + write_build(current_project->name, build.name, &build); + + if (!fork()) + { + char* build_path = build_dir(current_project->name, build.name); + if (build_path) + { + makeDir(build_path); + build_path = realloc(build_path, (strlen(build_path) + 4 + 1) * sizeof(char)); + strcat(build_path, "/log"); + + freopen(build_path, "w", stdout); + freopen(build_path, "w", stderr); + + free(build_path); + } + + int status = system(current_project->script_path); + if (status) + build.status = STATUS_FAILURE; + else + build.status = STATUS_SUCCESS; + + build.completion = time(NULL); + + write_build(current_project->name, build.name, &build); + exit(status); + } +}
\ No newline at end of file diff --git a/src/build.h b/src/build.h new file mode 100644 index 0000000..01205ec --- /dev/null +++ b/src/build.h @@ -0,0 +1,6 @@ +#ifndef BUILD_H +#define BUILD_H + +void create_build(); + +#endif
\ No newline at end of file diff --git a/src/cgci.c b/src/cgci.c new file mode 100644 index 0000000..cf2f70e --- /dev/null +++ b/src/cgci.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> + +#include "config.h" +#include "env.h" +#include "fs.h" +#include "parser.h" +#include "context.h" +#include "ui.h" +#include "cgci.h" + +void init() +{ + unsetenv("HOME"); + unsetenv("USER"); + + init_context(); + init_config(); +} + +void deinit() +{ + deinit_context(); + deinit_config(); +} + +int main(int argc, char **argv, char** envp) +{ + if (argc > 1) + { + argv_to_path(argc, argv); + } + + init(); + + if (context.project && !strcmp(context.project, "assets")) + { + print_asset(context.action); + } + else + { + print_html(); + } + + deinit(); + + return 0; +} diff --git a/src/cgci.h b/src/cgci.h new file mode 100644 index 0000000..c7958dd --- /dev/null +++ b/src/cgci.h @@ -0,0 +1,7 @@ +#ifndef CGCI_H +#define CGCI_H + +void init(); +void deinit(); + +#endif
\ No newline at end of file diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..02ad9e3 --- /dev/null +++ b/src/config.c @@ -0,0 +1,158 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "parser.h" +#include "context.h" +#include "fs.h" +#include "config.h" + +const char* build_string[] = { + "Unknown", + "In Progress", + "Success", + "Failure" +}; + +const char* build_class[] = { + "unknown", + "in-progress", + "success", + "failure" +}; + +struct config_t config; +struct project_t* current_project = NULL; +struct build_t* current_build = NULL; + + +void init_config() +{ + config.cache_dir = NULL; + config.token = NULL; + config.projects = malloc(1); + config.project_count = 0; + + parse_config(); + + if (context.project) + { + for (size_t i = 0; i < config.project_count; ++i) + { + if (!strcmp(context.project, config.projects[i].name)) + { + current_project = &config.projects[i]; + break; + } + } + } + + if (context.index) + { + for (size_t i = 0; i < config.project_count; ++i) + { + for (size_t j = 0; j < config.projects[i].build_count; ++j) + { + if (!strcmp(context.index, config.projects[i].builds[j].name)) + { + current_build = &config.projects[i].builds[j]; + break; + } + } + } + } +} + +void deinit_config() +{ + if (config.cache_dir) + free(config.cache_dir); + + if (config.token) + free(config.token); + + if (config.projects) + { + for (size_t i = 0; i < config.project_count; ++i) + { + if (config.projects[i].name) + free(config.projects[i].name); + + if (config.projects[i].script_path) + free(config.projects[i].script_path); + + if (config.projects[i].description) + free(config.projects[i].description); + + if (config.projects[i].builds) + { + for (size_t j = 0; j < config.projects[i].build_count; ++j) + { + if (config.projects[i].builds[j].name) + free(config.projects[i].builds[j].name); + + if (config.projects[i].builds[j].log) + free(config.projects[i].builds[j].log); + } + + free(config.projects[i].builds); + } + } + free(config.projects); + } +} + +char* cache_dir() +{ + char* dir = "cache/"NAME; + if (isDir(dir)) + return strdup(dir); + + dir = CACHE_DIR; + + if (!isDir(CACHE_DIR) && makeDir(CACHE_DIR)) + { + return NULL; + } + + return strdup(dir); +} + +char* project_dir(char* project) +{ + if (!project) + return NULL; + + char* cache = cache_dir(); + + if (!cache) + return cache; + + size_t size = strlen(cache) + 1 + strlen(project) ; + cache = realloc(cache, size+1); + strncat(cache, "/", size); + strncat(cache, project, size); + cache[size] = '\0'; + + return cache; +} + +char* build_dir(char* project, char* build) +{ + if (!project || !build) + return NULL; + + char* project_path = project_dir(project); + + if (!project_path) + return project_path; + + size_t size = strlen(project_path) + 1 + strlen(build) ; + project_path = realloc(project_path, size+1); + strncat(project_path, "/", size); + strncat(project_path, build, size); + project_path[size] = '\0'; + + return project_path; +}
\ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..49daf77 --- /dev/null +++ b/src/config.h @@ -0,0 +1,52 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include <stddef.h> +#include <time.h> + +enum build_status { + STATUS_UNKNOWN, + STATUS_INPROGRESS, + STATUS_SUCCESS, + STATUS_FAILURE +}; +extern const char* build_string[]; +extern const char* build_class[]; + +struct build_t { + char* name; + time_t timestamp; + time_t completion; + enum build_status status; + char* log; +}; + +struct project_t { + char* name; + char* script_path; + char* description; + + struct build_t* builds; + size_t build_count; +}; + +struct config_t { + char* cache_dir; + char* token; + struct project_t* projects; + size_t project_count; +}; + +extern struct config_t config; +extern struct project_t* current_project; +extern struct build_t* current_build; + +void init_config(); +void deinit_config(); +void parse_config(); + +char* cache_dir(); +char* project_dir(char* project); +char* build_dir(char* project, char* build); + +#endif
\ No newline at end of file diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..4b99bea --- /dev/null +++ b/src/context.c @@ -0,0 +1,67 @@ +#include "env.h" +#include "parser.h" +#include "context.h" + +struct context_t context; + +void init_context() +{ + context.document_root = getenv_default("DOCUMENT_ROOT", "/"); + context.raw_path = getenv_default("PATH_INFO", "/"); + context.path = malloc(1); + context.path_length = 0; + + context.project = NULL; + context.action = NULL; + context.index = NULL; + context.extra = NULL; + + context.token = NULL; + + context.debug = 0; + + parse_path(context.raw_path); + parse_query(getenv_default("QUERY_STRING", "")); + + for (size_t i = 0; i < context.path_length; ++i) + { + switch(i) + { + case 0: + context.project = context.path[i]; + break; + + case 1: + context.action = context.path[i]; + break; + + case 2: + context.index = context.path[i]; + break; + + case 3: + context.extra = context.path[i]; + break; + + default: + break; + } + } +} + +void deinit_context() +{ + if (context.path) + { + for (size_t i = 0; i < context.path_length; ++i) + { + if (context.path[i]) + free(context.path[i]); + } + + free(context.path); + + if (context.token) + free(context.token); + } +} diff --git a/src/context.h b/src/context.h new file mode 100644 index 0000000..80c7e72 --- /dev/null +++ b/src/context.h @@ -0,0 +1,32 @@ +#ifndef CONTEXT_H +#define CONTEXT_H + +#include <stdint.h> +#include <stdlib.h> + +struct context_t { + char* document_root; + + char* raw_path; + + char** path; /* NULLABLE */ + size_t path_length; + + // simplistic values + // never allocated to + char* project; + char* action; + char* index; + char* extra; + + char* token; + + uint8_t debug; +}; + +extern struct context_t context; + +void init_context(); +void deinit_context(); + +#endif
\ No newline at end of file diff --git a/src/env.c b/src/env.c new file mode 100644 index 0000000..43bf8d7 --- /dev/null +++ b/src/env.c @@ -0,0 +1,12 @@ +#include <stdlib.h> +#include "env.h" + +char* getenv_default(const char* name, char* default_val) +{ + char* val = getenv(name); + if (!val) + val = default_val; + + return val; + +} diff --git a/src/env.h b/src/env.h new file mode 100644 index 0000000..45f99bb --- /dev/null +++ b/src/env.h @@ -0,0 +1,6 @@ +#ifndef ENV_H +#define ENV_H + +char* getenv_default(const char* name, char* default_val); + +#endif
\ No newline at end of file diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..a28c217 --- /dev/null +++ b/src/fs.c @@ -0,0 +1,141 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> + +#ifdef _WIN32 +#include <windows.h> +#include <shlwapi.h> +#endif + +#include "fs.h" + +#ifdef _WIN32 +#define mkdir(path, perm) mkdir(path) +#endif + +static struct stat getStat(const char* path) +{ + // fill with 0s by default in the case stat fails + struct stat sb = {0}; + + // the return value signifies if stat failes (e.g. file not found) + // unimportant for us if it fails it won't touch sb + stat(path, &sb); + + return sb; +} + +int isFile(const char* path) +{ + struct stat sb = getStat(path); + +#ifndef _WIN32 + if (S_ISLNK(sb.st_mode)) + { + char buf[PATH_MAX]; + readlink(path, buf, sizeof(buf)); + + return isFile(buf); + } +#endif + return S_ISREG(sb.st_mode); +} + +int isDir(const char* path) +{ + struct stat sb = getStat(path); + +#ifndef _WIN32 + if (S_ISLNK(sb.st_mode)) + { + char buf[PATH_MAX]; + readlink(path, buf, sizeof(buf)); + + return isDir(buf); + } +#endif + return S_ISDIR(sb.st_mode); +} + +int makeDir(const char* path) +{ + char pathcpy[PATH_MAX]; + char *index; + + strncpy(pathcpy, path, PATH_MAX-1); // make a mutable copy of the path + + for(index = pathcpy+1; *index; ++index) + { + + if (*index == '/') + { + *index = '\0'; + + if (mkdir(pathcpy, 0755) != 0) + { + if (errno != EEXIST) + return -1; + } + + *index = '/'; + } + } + + return mkdir(path, 0755); +} + +int removeDir(const char* path) +{ + DIR *d = opendir(path); + size_t path_len = strlen(path); + int r = -1; + + if (d) { + struct dirent *p; + + r = 0; + while (!r && (p = readdir(d))) { + char *buf; + size_t len; + + // Skip the names "." and ".." as we don't want to recurse on them. + if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) + continue; + + len = path_len + strlen(p->d_name) + 2; + buf = malloc(len); + + if (buf) { + struct stat statbuf = {0}; + + snprintf(buf, len, "%s/%s", path, p->d_name); + if (!stat(buf, &statbuf)) { + if (S_ISDIR(statbuf.st_mode)) + r = removeDir(buf); +#ifndef _WIN32 + else if (S_ISLNK(statbuf.st_mode)) + r = unlink(buf); +#endif + else + r = remove(buf); + } + else // it is very likely that we found a dangling symlink which is not detected by stat + { + r = unlink(buf); + } + free(buf); + } + } + closedir(d); + } + + if (!r) + r = rmdir(path); + + return r; +}
\ No newline at end of file diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..8f4de4b --- /dev/null +++ b/src/fs.h @@ -0,0 +1,9 @@ +#ifndef FS_H +#define FS_H + +int isFile(const char*); +int isDir(const char*); +int makeDir(const char*); +int removeDir(const char*); + +#endif
\ No newline at end of file diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..052acb4 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,312 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <dirent.h> + +#include "config.h" +#include "context.h" +#include "fs.h" +#include "parser.h" + +void argv_to_path(int argc, char** argv) +{ + char* arg_path = malloc(1); + size_t arg_path_length = 1; + *arg_path = '\0'; + + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] != '\0') + { + arg_path_length += strlen(argv[i]) + 1; + + arg_path = realloc(arg_path, sizeof(char) * arg_path_length); + + strncat(arg_path, "/", arg_path_length - strlen(arg_path)); + strncat(arg_path, argv[i], arg_path_length - strlen(arg_path)); + } + } + + if (arg_path_length) + { + setenv("PATH_INFO", arg_path, 1); + } + free(arg_path); +} + +void parse_config() +{ + FILE* fd = fopen("/etc/cgcirc", "r"); + if (!fd) + { + fd = fopen("cgcirc", "r"); + if (!fd) + return; + } + + fseek(fd, 0L, SEEK_END); + size_t file_size = (size_t)ftell(fd); + rewind(fd); + + char* buf = malloc(file_size+1); + assert(buf); + + fread(buf, sizeof(char*), file_size, fd); + buf[file_size] = '\0'; + + fclose(fd); + + char* head = buf; + char* tail = head; + + char* key = NULL; + char* value = NULL; + + while(*tail) + { + assert((size_t)(tail - buf) <= file_size); + + if (*head == '#') + { + while (*tail != '\0' && *tail != '\n') + { + assert((size_t)(tail - buf) <= file_size); + ++tail; + } + head = tail+1;; + continue; + } + else if (*tail == '=') + { + key = head; + *tail = '\0'; + head = tail+1;; + } + else if (*tail == '\0' || *tail == '\n') + { + if (key) + { + value = head; + } + *tail = '\0'; + head = tail+1;; + } + + ++tail; + + if (key && value) + { + if (!strcmp(key, "cache-dir")) + { + assert(!config.cache_dir); + config.cache_dir = strdup(value); + } + if (!strcmp(key, "token")) + { + assert(!config.token); + config.token = strdup(value); + } + else if (!strcmp(key, "project.name")) + { + ++config.project_count; + + config.projects = realloc(config.projects, config.project_count * sizeof(struct project_t)); + assert(config.projects); + + config.projects[config.project_count-1].name = strdup(value); + config.projects[config.project_count-1].script_path = NULL; + config.projects[config.project_count-1].description = NULL; + } + else if (!strcmp(key, "project.script")) + { + config.projects[config.project_count-1].script_path = strdup(value); + } + else if (!strcmp(key, "project.description")) + { + config.projects[config.project_count-1].description = strdup(value); + } + + key = NULL; + value = NULL; + } + } + + char* project = NULL; + char* build = NULL; + for (size_t i = 0; i < config.project_count; ++i) + { + config.projects[i].build_count = 0; + config.projects[i].builds = NULL; + + project = project_dir(config.projects[i].name); + + if (!isDir(project)) + { + free(project); + continue; + } + + DIR *dir; + struct dirent *ent; + + if ((dir = opendir(project)) != NULL) + { + while ((ent = readdir(dir)) != NULL) + { + if (ent->d_name[0] == '.') continue; + + build = build_dir(config.projects[i].name, ent->d_name); + size_t build_size = strlen(build); + build = realloc(build, (build_size + 11 + 1) * sizeof(char)); + + config.projects[i].build_count++; + config.projects[i].builds = realloc(config.projects[i].builds, config.projects[i].build_count * sizeof(struct build_t)); + + config.projects[i].builds[config.projects[i].build_count-1].name = strdup(ent->d_name); + config.projects[i].builds[config.projects[i].build_count-1].timestamp = 0; + config.projects[i].builds[config.projects[i].build_count-1].completion = 0; + config.projects[i].builds[config.projects[i].build_count-1].status = STATUS_UNKNOWN; + config.projects[i].builds[config.projects[i].build_count-1].log = NULL; + + strcat(build, "/timestamp"); + fd = fopen(build, "rb"); + if (fd) + { + fread(&config.projects[i].builds[config.projects[i].build_count-1].timestamp, sizeof(time_t), 1, fd); + fclose(fd); + } + build[build_size] = '\0'; + + strcat(build, "/completion"); + fd = fopen(build, "rb"); + if (fd) + { + fread(&config.projects[i].builds[config.projects[i].build_count-1].completion, sizeof(time_t), 1, fd); + fclose(fd); + } + build[build_size] = '\0'; + + strcat(build, "/status"); + fd = fopen(build, "rb"); + if (fd) + { + fread(&config.projects[i].builds[config.projects[i].build_count-1].status, sizeof(enum build_status), 1, fd); + fclose(fd); + } + build[build_size] = '\0'; + + strcat(build, "/log"); + fd = fopen(build, "rb"); + if (fd) + { + fseek(fd, 0, SEEK_END); + size_t size = (size_t)ftell(fd); + rewind(fd); + + config.projects[i].builds[config.projects[i].build_count-1].log = malloc(size + 1); + fread(config.projects[i].builds[config.projects[i].build_count-1].log, sizeof(char), size, fd); + fclose(fd); + config.projects[i].builds[config.projects[i].build_count-1].log[size] = '\0'; + } + build[build_size] = '\0'; + + free(build); + } + closedir(dir); + } + + // sort the builds + struct build_t t; + for (size_t j = 0; j < config.projects[i].build_count; ++j) + { + for (size_t k = 0; k < config.projects[i].build_count; ++k) + { + struct build_t* a = &config.projects[i].builds[j]; + struct build_t* b = &config.projects[i].builds[k]; + + if (atoi(a->name) > atoi(b->name)) + { + t = *a; + *a = *b; + *b = t; + } + } + } + + free(project); + } + + free(buf); +} + +void parse_path(const char* path) +{ + size_t path_len = strlen(path) + 1; + const char* const orig_path = path; + + if (path[0] != '/') + return; + + const char* head = ++path; + + while(*path) + { + assert(orig_path+path_len >= path); + ++path; + if (*path == '/' || *path == '\0') + { + assert(path > head); + size_t len = (size_t)(path - head) + 1; + + if (!len) + continue; + + ++context.path_length; + + context.path = realloc(context.path, sizeof(char*) * context.path_length); + assert(context.path); + + context.path[context.path_length-1] = malloc(len * sizeof(char)); + strncpy(context.path[context.path_length-1], head, len-1); + context.path[context.path_length-1][len-1] = '\0'; + + head = path+1; + } + } +} + +void parse_query(const char* query_arg) +{ + char* query = strdup(query_arg); + char* head = query; + char* end = head + strlen(query); + + char* key = head; + char* value = NULL; + + while (head <= end) + { + ++head; + if (!value && *head == '=') + { + *head = '\0'; + value = head+1; + } + + if (*head == '&' || *head == '\0') + { + *head = '\0'; + + if (!strcmp(key, "debug")) + { + context.debug = 1; + } + else if (!strcmp(key, "token")) + { + context.token = strdup(value); + } + } + } + + free(query); +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..e5d5465 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,8 @@ +#ifndef PARSER_H +#define PARSER_H + +void argv_to_path(int, char**); +void parse_path(const char*); +void parse_query(const char*); + +#endif
\ No newline at end of file diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..25c810e --- /dev/null +++ b/src/ui.c @@ -0,0 +1,390 @@ +#include <stdio.h> +#include <string.h> +#include <dirent.h> +#include <stdlib.h> + +#include "config.h" +#include "context.h" +#include "build.h" +#include "ui.h" + +#define CONTENT_TYPE_FORMAT "Content-Type: %s;\n\n" +#define TEXT_HTML "text/html" +#define TEXT_CSS "text/css" +#define TEXT_PLAIN "text/plain" + +void print_html() +{ + if (current_project && current_project->name) + { + if (context.action) + { + if (!strcmp(context.action, "builds") && context.index) + { + if (context.extra) + { + printf(CONTENT_TYPE_FORMAT, TEXT_PLAIN); + printf("%s", current_build->log); + } + else + { + printf(CONTENT_TYPE_FORMAT, TEXT_HTML); + printf(HTML_START); + print_head(); + print_title(); + print_build_nav(); + print_build_info(); + printf(HTML_END); + } + } + else if (!strcmp(context.action, "trigger")) + { + if (context.token && config.token && !strcmp(context.token, config.token)) + { + create_build(); + + printf(CONTENT_TYPE_FORMAT, TEXT_HTML); + printf(HTML_START); + print_head(); + printf(HTML_END); + } + else + { + printf(CONTENT_TYPE_FORMAT, TEXT_HTML); + printf(HTML_START); + print_head(); + print_title(); + print_build_nav(); + print_build_trigger(); + printf(HTML_END); + } + } + } + else + { + printf(CONTENT_TYPE_FORMAT, TEXT_HTML); + printf(HTML_START); + print_head(); + print_title(); + print_build_nav(); + print_build_list(); + printf(HTML_END); + } + } + else + { + printf(CONTENT_TYPE_FORMAT, TEXT_HTML); + printf(HTML_START); + print_head(); + print_title(); + print_project_nav(); + print_project_list(); + printf(HTML_END); + } +} + +void print_head() +{ + printf("<head>"); + + printf("<meta charset=\"utf-8\">"); + + printf("<title>CGCI"); + if (current_project && current_project->name) + { + printf(" : %s", current_project->name); + } + printf("</title>"); + + if (current_project && current_project->name + && context.action && !strcmp(context.action, "trigger") + && context.token && config.token && !strcmp(context.token, config.token)) + printf("<meta http-equiv=\"refresh\" content=\"0; url=/%s\"/>", current_project->name); + + printf("<link rel=\"stylesheet\" type=\"text/css\" href=\"/assets/base.css\"/>"); + printf("</head>"); +} + +void print_title() +{ + printf("<h1 class=\"title\">"); + + printf("<a %s>CGCI</a>", context.project != NULL ? "href=\"/\"" : ""); + + if (context.project) + { + printf(" : <a href=\"/%s\">%s</a>", context.project, context.project); + if (context.action) + { + printf(" : %s", context.action); + } + } + printf("</h1>"); +} + +void print_build_nav() +{ + printf( + "<table class=\"tabs\">" + "<tbody>" + "<tr>" + "<td>" + "<a href=\"/%s\" class=\"active\">Builds</a>" + "</td>", + current_project->name + ); + + if (config.token && current_project->script_path && strlen(current_project->script_path)) + { + printf( + "<td class=\"align-right\">" + "<a href=\"/%s/trigger\">Trigger Build</a>" + "</td>", + current_project->name + ); + } + + printf( + "</tr>" + "</tbody>" + "</table>" + ); +} + +void print_build_info() +{ + // YYYY-MM-DD HH:MM + char time[18]; + strftime(time, sizeof(time), "%Y-%m-%d %H:%M", localtime(¤t_build->timestamp)); + char buildtime[18]; + strftime(buildtime, sizeof(buildtime), "%Y-%m-%d %H:%M", localtime(¤t_build->completion)); + + char* log_lines = current_build->log + strlen(current_build->log); + int line_count = 20; + + while (line_count && log_lines > current_build->log) + { + --log_lines; + + if (*log_lines == '\n') + --line_count; + } + + printf( + "<table class=\"build-info\">" + "<tbody>" + "<tr>" + "<td>Build ID</td>" + "<td>%s</td>" + "</tr>" + "<tr>" + "<td>Build Date</td>" + "<td>%s</td>" + "</tr>" + "<tr>" + "<td>Completion Time</td>" + "<td>%s</td>" + "</tr>" + "<tr>" + "<td>Status</td>" + "<td class=\"%s\">%s</td>" + "</tr>" + "<tr>" + "<td>Log</td>" + "<td>" + "<a href=\"/%s/builds/%s/log\">Raw</a>" + "</td>" + "</tr>" + "<!--<tr>" + "<td>Artifact</td>" + "<td>" + "<a href=\"/polecat/builds/1/artifact\">Download</a>" + "</td>" + "</tr>-->" + "</tbody>" + "</table>" + "<pre>%s</pre>", + current_build->name, time, current_build->completion ? buildtime : "", + build_class[current_build->status], build_string[current_build->status], + current_project->name, current_build->name, + log_lines + ); +} + +void print_build_trigger() +{ + printf( + "<form>" + "<label>Trigger Build</label><br>" + "%s" + "<input name=\"token\" type=\"password\" placeholder=\"token\" value=\"%s\" required><br>" + "<input type=\"submit\" value=\"submit\">" + "</form>", + context.token ? "Invalid token" : "", + context.token ? context.token : "" + ); +} + +void print_build_list() +{ + printf( + "<table class=\"content\">" + "<thead>" + "<tr>" + "<th>Build ID</th>" + "<th>Build Date</th>" + "<th>Completion Time</th>" + "<th>Status</th>" + "</tr>" + "</thead>" + "<tbody>" + ); + + for (size_t i = 0; i < current_project->build_count; ++i) + { + struct build_t* build = ¤t_project->builds[i]; + + // YYYY-MM-DD HH:MM + char time[18]; + strftime(time, sizeof(time), "%Y-%m-%d %H:%M", localtime(&build->timestamp)); + char buildtime[255] = ""; + strdifftime(build->completion, build->timestamp, buildtime, sizeof(buildtime)); + + printf( + "<tr>" + "<td>" + "<a href=\"/%s/builds/%s\">%s</a>" + "</td>" + "<td>%s</td>" + "<td>%s</td>" + "<td class=\"%s\">%s</td>" + "</tr>", + context.project, build->name, build->name, + time, buildtime, + build_class[build->status], build_string[build->status] + ); + } + + printf( + "</tbody>" + "</table>" + ); +} + +void print_project_nav() +{ + printf( + "<table class=\"tabs\">" + "<tbody>" + "<tr>" + "<td>" + "<a href=\"/\" class=\"active\">Projects</a>" + "</td>" + "</tr>" + "</tbody>" + "</table>" + ); +} + +void print_project_list() +{ + printf( + "<table class=\"content\">" + "<thead>" + "<tr>" + "<th>Name</th>" + "<th>Description</th>" + "<th>Last Build Date</th>" + "<th>Last Completion Time</th>" + "<th>Last Build Status</th>" + "</tr>" + "</thead>" + "<tbody>" + ); + + for (size_t i = 0; i < config.project_count; ++i) + { + struct project_t* project = &config.projects[i]; + + const char* class = "unknown"; + const char* status = "Unknown"; + char time[18] = "never"; + char buildtime[255] = ""; + if (project->build_count > 0) + { + class = build_class[project->builds[0].status]; + status = build_string[project->builds[0].status]; + strftime(time, sizeof(time), "%Y-%m-%d %H:%M", localtime(&project->builds[0].timestamp)); + strdifftime(project->builds[0].completion, project->builds[0].timestamp, buildtime, sizeof(buildtime)); + } + + printf( + "<tr>" + "<td>" + "<a href=\"/%s\">%s</a>" + "</td>" + "<td>%s</td>" + "<td>%s</td>" + "<td>%s</td>" + "<td class=\"%s\">%s</td>" + "</tr>", + project->name, project->name, + project->description, + time, buildtime, + class, status + ); + } + printf( + "</tbody>" + "</table>" + ); +} + +void print_asset(const char* file) +{ + if (!file) + return; + + printf(CONTENT_TYPE_FORMAT, TEXT_CSS); + + FILE* fd = fopen(file, "r"); + + if (!fd) + return; + + char buf[255+1]; + + while (!feof(fd)) + { + fread(buf, sizeof(buf)-1, sizeof(buf[0]), fd); + buf[sizeof(buf)-1] = '\0'; + + printf("%s", buf); + } +} + +void strdifftime(time_t time1, time_t time0, char* str, size_t size) +{ + if (!size) + return; + + double diff = difftime(time1, time0); + + if (diff > 0) + { + *str = '\0'; + + int seconds = (int)diff % 60; + int minutes = (int)(diff / 60); + int hours = minutes / 60; + + if (hours) + snprintf(str+strlen(str), size, "%i hours ", hours); + + if (minutes) + snprintf(str+strlen(str), size, "%i minutes ", minutes); + + if (seconds) + snprintf(str+strlen(str), size, "%i seconds ", seconds); + } +}
\ No newline at end of file diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..06b559b --- /dev/null +++ b/src/ui.h @@ -0,0 +1,26 @@ +#ifndef UI_H +#define UI_H + +#include <time.h> +#include <stdint.h> + +#define HTML_START "<!DOCTYPE html><html>" +#define HTML_END "</html>" + +void print_html(); +void print_head(); + +void print_body(); +void print_title(); +void print_build_nav(); +void print_build_info(); +void print_build_trigger(); +void print_build_list(); +void print_project_nav(); +void print_project_list(); + +void print_asset(const char*); + +void strdifftime(time_t, time_t, char*, size_t); + +#endif |