.
This commit is contained in:
parent
b2c9f9bd63
commit
e3cc2fcf5e
29 changed files with 876 additions and 117 deletions
322
colla/coropool.c
Normal file
322
colla/coropool.c
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
#include "coropool.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <tracelog.h>
|
||||||
|
|
||||||
|
#include "jobpool.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NUM_JOBS = 50
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _pool_internal_t;
|
||||||
|
|
||||||
|
typedef union job_t {
|
||||||
|
struct {
|
||||||
|
mco_coro *co;
|
||||||
|
struct _pool_internal_t *pool;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
union job_t *next_free;
|
||||||
|
void *__padding;
|
||||||
|
};
|
||||||
|
} job_t;
|
||||||
|
|
||||||
|
static inline bool _isJobValid(job_t *job) {
|
||||||
|
return job->pool != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct jobchunk_t {
|
||||||
|
job_t jobs[NUM_JOBS];
|
||||||
|
job_t *first_free;
|
||||||
|
struct jobchunk_t *next;
|
||||||
|
} jobchunk_t;
|
||||||
|
|
||||||
|
void _chunkInit(jobchunk_t *chunk) {
|
||||||
|
if (!chunk) return;
|
||||||
|
chunk->first_free = chunk->jobs;
|
||||||
|
for (int i = 0; i < (NUM_JOBS - 1); ++i) {
|
||||||
|
chunk->jobs[i].next_free = chunk->jobs + i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jobchunk_t *_chunkNew(jobchunk_t *prev) {
|
||||||
|
jobchunk_t *chunk = calloc(1, sizeof(jobchunk_t));
|
||||||
|
_chunkInit(chunk);
|
||||||
|
prev->next = chunk;
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_t *_chunkGetFirstFree(jobchunk_t *chunk) {
|
||||||
|
job_t *first_free = chunk->first_free;
|
||||||
|
if (first_free) {
|
||||||
|
chunk->first_free = first_free->next_free;
|
||||||
|
}
|
||||||
|
return first_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _chunkSetFree(jobchunk_t *chunk, job_t *job) {
|
||||||
|
job->pool = NULL;
|
||||||
|
job->next_free = chunk->first_free;
|
||||||
|
chunk->first_free = job;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _pool_internal_t {
|
||||||
|
jobpool_t pool;
|
||||||
|
jobchunk_t head_chunk;
|
||||||
|
cmutex_t chunk_mtx;
|
||||||
|
} _pool_internal_t;
|
||||||
|
|
||||||
|
static int _worker(void *arg);
|
||||||
|
|
||||||
|
coropool_t copoInit(uint32 num) {
|
||||||
|
_pool_internal_t *pool = calloc(1, sizeof(_pool_internal_t));
|
||||||
|
pool->pool = poolInit(num);
|
||||||
|
_chunkInit(&pool->head_chunk);
|
||||||
|
pool->chunk_mtx = mtxInit();
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copoFree(coropool_t copo) {
|
||||||
|
_pool_internal_t *pool = copo;
|
||||||
|
|
||||||
|
poolWait(pool->pool);
|
||||||
|
poolFree(pool->pool);
|
||||||
|
|
||||||
|
jobchunk_t *chunk = &pool->head_chunk;
|
||||||
|
while (chunk) {
|
||||||
|
jobchunk_t *next = chunk->next;
|
||||||
|
free(chunk);
|
||||||
|
chunk = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtxFree(pool->chunk_mtx);
|
||||||
|
free(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAdd(coropool_t copo, copo_func_f fn) {
|
||||||
|
mco_desc desc = mco_desc_init(fn, 0);
|
||||||
|
return copoAddDesc(copo, &desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAddDesc(coropool_t copo, mco_desc *desc) {
|
||||||
|
mco_coro *co;
|
||||||
|
mco_create(&co, desc);
|
||||||
|
return copoAddCo(copo, co);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _copoAddJob(job_t *job) {
|
||||||
|
//return poolAdd(job->pool->pool, _worker, job);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _copoRemoveJob(job_t *job) {
|
||||||
|
_pool_internal_t *pool = job->pool;
|
||||||
|
// find chunk
|
||||||
|
jobchunk_t *chunk = &pool->head_chunk;
|
||||||
|
while (chunk) {
|
||||||
|
if (chunk->jobs < job && (chunk->jobs + NUM_JOBS) > job) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunk = chunk->next;
|
||||||
|
}
|
||||||
|
if (!chunk) return false;
|
||||||
|
mtxLock(pool->chunk_mtx);
|
||||||
|
_chunkSetFree(chunk, job);
|
||||||
|
mtxUnlock(pool->chunk_mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAddCo(coropool_t copo, mco_coro *co) {
|
||||||
|
_pool_internal_t *pool = copo;
|
||||||
|
|
||||||
|
job_t *new_job = NULL;
|
||||||
|
jobchunk_t *chunk = &pool->head_chunk;
|
||||||
|
|
||||||
|
mtxLock(pool->chunk_mtx);
|
||||||
|
while (!new_job) {
|
||||||
|
new_job = _chunkGetFirstFree(chunk);
|
||||||
|
if (!new_job) {
|
||||||
|
if (!chunk->next) {
|
||||||
|
info("adding new chunk");
|
||||||
|
chunk->next = _chunkNew(chunk);
|
||||||
|
}
|
||||||
|
chunk = chunk->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtxUnlock(pool->chunk_mtx);
|
||||||
|
|
||||||
|
new_job->co = co;
|
||||||
|
new_job->pool = pool;
|
||||||
|
|
||||||
|
//return poolAdd(pool->pool, _worker, new_job);
|
||||||
|
return _copoAddJob(new_job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copoWait(coropool_t copo) {
|
||||||
|
_pool_internal_t *pool = copo;
|
||||||
|
poolWait(pool->pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _worker(void *arg) {
|
||||||
|
job_t *job = arg;
|
||||||
|
mco_result res = mco_resume(job->co);
|
||||||
|
if (res != MCO_DEAD) {
|
||||||
|
_copoAddJob(job);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_copoRemoveJob(job);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <vec.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
vec(mco_coro *) jobs;
|
||||||
|
uint32 head;
|
||||||
|
cmutex_t work_mutex;
|
||||||
|
condvar_t work_cond;
|
||||||
|
condvar_t working_cond;
|
||||||
|
int32 working_count;
|
||||||
|
int32 thread_count;
|
||||||
|
bool stop;
|
||||||
|
} _copo_internal_t;
|
||||||
|
|
||||||
|
static mco_coro *_getJob(_copo_internal_t *copo);
|
||||||
|
static int _copoWorker(void *arg);
|
||||||
|
|
||||||
|
coropool_t copoInit(uint32 num) {
|
||||||
|
if (!num) num = 2;
|
||||||
|
|
||||||
|
_copo_internal_t *copo = malloc(sizeof(_copo_internal_t));
|
||||||
|
*copo = (_copo_internal_t){
|
||||||
|
.work_mutex = mtxInit(),
|
||||||
|
.work_cond = condInit(),
|
||||||
|
.working_cond = condInit(),
|
||||||
|
.thread_count = (int32)num
|
||||||
|
};
|
||||||
|
|
||||||
|
for (usize i = 0; i < num; ++i) {
|
||||||
|
thrDetach(thrCreate(_copoWorker, copo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return copo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copoFree(coropool_t copo_in) {
|
||||||
|
_copo_internal_t *copo = copo_in;
|
||||||
|
if (!copo) return;
|
||||||
|
|
||||||
|
mtxLock(copo->work_mutex);
|
||||||
|
copo->stop = true;
|
||||||
|
condWakeAll(copo->work_cond);
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
|
||||||
|
copoWait(copo);
|
||||||
|
|
||||||
|
vecFree(copo->jobs);
|
||||||
|
mtxFree(copo->work_mutex);
|
||||||
|
condFree(copo->work_cond);
|
||||||
|
condFree(copo->working_cond);
|
||||||
|
|
||||||
|
free(copo);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAdd(coropool_t copo, copo_func_f fn) {
|
||||||
|
mco_desc desc = mco_desc_init(fn, 0);
|
||||||
|
return copoAddDesc(copo, &desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAddDesc(coropool_t copo, mco_desc *desc) {
|
||||||
|
mco_coro *co;
|
||||||
|
mco_create(&co, desc);
|
||||||
|
return copoAddCo(copo, co);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copoAddCo(coropool_t copo_in, mco_coro *co) {
|
||||||
|
_copo_internal_t *copo = copo_in;
|
||||||
|
if (!copo) return false;
|
||||||
|
|
||||||
|
mtxLock(copo->work_mutex);
|
||||||
|
|
||||||
|
if (copo->head > vecLen(copo->jobs)) {
|
||||||
|
vecClear(copo->jobs);
|
||||||
|
copo->head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vecAppend(copo->jobs, co);
|
||||||
|
|
||||||
|
condWake(copo->work_cond);
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copoWait(coropool_t copo_in) {
|
||||||
|
_copo_internal_t *copo = copo_in;
|
||||||
|
if (!copo) return;
|
||||||
|
|
||||||
|
mtxLock(copo->work_mutex);
|
||||||
|
// while its either
|
||||||
|
// - working and there's still some threads doing some work
|
||||||
|
// - not working and there's still some threads exiting
|
||||||
|
while ((!copo->stop && copo->working_count > 0) ||
|
||||||
|
(copo->stop && copo->thread_count > 0)
|
||||||
|
) {
|
||||||
|
condWait(copo->working_cond, copo->work_mutex);
|
||||||
|
}
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == PRIVATE FUNCTIONS ===================================
|
||||||
|
|
||||||
|
static mco_coro *_getJob(_copo_internal_t *copo) {
|
||||||
|
if (copo->head >= vecLen(copo->jobs)) {
|
||||||
|
copo->head = 0;
|
||||||
|
}
|
||||||
|
return copo->jobs[copo->head++];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _copoWorker(void *arg) {
|
||||||
|
_copo_internal_t *copo = arg;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
mtxLock(copo->work_mutex);
|
||||||
|
// wait for a new job
|
||||||
|
while (copo->head >= vecLen(copo->jobs) && !copo->stop) {
|
||||||
|
condWait(copo->work_cond, copo->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copo->stop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mco_coro *job = _getJob(copo);
|
||||||
|
copo->working_count++;
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
|
||||||
|
if (job && mco_status(job) != MCO_DEAD) {
|
||||||
|
mco_resume(job);
|
||||||
|
if (mco_status(job) != MCO_DEAD) {
|
||||||
|
copoAddCo(copo, job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mtxLock(copo->work_mutex);
|
||||||
|
copo->working_count--;
|
||||||
|
if (!copo->stop &&
|
||||||
|
copo->working_count == 0 &&
|
||||||
|
copo->head == vecLen(copo->jobs)
|
||||||
|
) {
|
||||||
|
condWake(copo->working_cond);
|
||||||
|
}
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
copo->thread_count--;
|
||||||
|
condWake(copo->working_cond);
|
||||||
|
mtxUnlock(copo->work_mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
17
colla/coropool.h
Normal file
17
colla/coropool.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <collatypes.h>
|
||||||
|
#include <cthreads.h>
|
||||||
|
|
||||||
|
#include "minicoro.h"
|
||||||
|
|
||||||
|
typedef void *coropool_t;
|
||||||
|
typedef void (*copo_func_f)(mco_coro *co);
|
||||||
|
|
||||||
|
coropool_t copoInit(uint32 num);
|
||||||
|
void copoFree(coropool_t copo);
|
||||||
|
|
||||||
|
bool copoAdd(coropool_t copo, copo_func_f fn);
|
||||||
|
bool copoAddDesc(coropool_t copo, mco_desc *desc);
|
||||||
|
bool copoAddCo(coropool_t copo, mco_coro *co);
|
||||||
|
void copoWait(coropool_t copo);
|
||||||
|
|
@ -75,8 +75,9 @@ cmutex_t mtxInit(void) {
|
||||||
return (cmutex_t)crit_sec;
|
return (cmutex_t)crit_sec;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mtxDestroy(cmutex_t ctx) {
|
void mtxFree(cmutex_t ctx) {
|
||||||
DeleteCriticalSection((CRITICAL_SECTION *)ctx);
|
DeleteCriticalSection((CRITICAL_SECTION *)ctx);
|
||||||
|
free((CRITICAL_SECTION *)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mtxValid(cmutex_t ctx) {
|
bool mtxValid(cmutex_t ctx) {
|
||||||
|
|
@ -97,6 +98,35 @@ bool mtxUnlock(cmutex_t ctx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
|
#include <tracelog.h>
|
||||||
|
|
||||||
|
condvar_t condInit(void) {
|
||||||
|
CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE));
|
||||||
|
InitializeConditionVariable(cond);
|
||||||
|
return (condvar_t)cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
void condFree(condvar_t cond) {
|
||||||
|
free((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWake(condvar_t cond) {
|
||||||
|
WakeConditionVariable((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWakeAll(condvar_t cond) {
|
||||||
|
WakeAllConditionVariable((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||||
|
BOOL res = SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
|
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
@ -168,14 +198,16 @@ cmutex_t mtxInit(void) {
|
||||||
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
|
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
|
|
||||||
if(mutex) {
|
if(mutex) {
|
||||||
int res = pthread_mutex_init(mutex, NULL);
|
if(pthread_mutex_init(mutex, NULL)) {
|
||||||
if(res != 0) mutex = NULL;
|
free(mutex);
|
||||||
|
mutex = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (cmutex_t)mutex;
|
return (cmutex_t)mutex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mtxDestroy(cmutex_t ctx) {
|
void mtxFree(cmutex_t ctx) {
|
||||||
pthread_mutex_destroy((pthread_mutex_t *)ctx);
|
pthread_mutex_destroy((pthread_mutex_t *)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,4 +227,44 @@ bool mtxUnlock(cmutex_t ctx) {
|
||||||
return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
|
return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
|
condvar_t condInit(void) {
|
||||||
|
pthread_cond_t *cond = malloc(sizeof(pthread_cond_t));
|
||||||
|
|
||||||
|
if(cond) {
|
||||||
|
if(pthread_cond_init(cond, NULL)) {
|
||||||
|
free(cond);
|
||||||
|
cond = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (condvar_t)cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
void condFree(condvar_t cond) {
|
||||||
|
if (!cond) return;
|
||||||
|
pthread_cond_destroy((pthread_cond_t *)cond);
|
||||||
|
free((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWake(condvar_t cond) {
|
||||||
|
pthread_cond_signal((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWakeAll(condvar_t cond) {
|
||||||
|
pthread_cond_broadcast((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||||
|
pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
|
struct timespec timeout;
|
||||||
|
time(&timeout.tv_sec);
|
||||||
|
timeout.tv_nsec += milliseconds * 1000000;
|
||||||
|
pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -29,7 +29,7 @@ bool thrJoin(cthread_t ctx, int *code);
|
||||||
typedef uintptr_t cmutex_t;
|
typedef uintptr_t cmutex_t;
|
||||||
|
|
||||||
cmutex_t mtxInit(void);
|
cmutex_t mtxInit(void);
|
||||||
void mtxDestroy(cmutex_t ctx);
|
void mtxFree(cmutex_t ctx);
|
||||||
|
|
||||||
bool mtxValid(cmutex_t ctx);
|
bool mtxValid(cmutex_t ctx);
|
||||||
|
|
||||||
|
|
@ -62,6 +62,21 @@ struct lock_t {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
|
typedef uintptr_t condvar_t;
|
||||||
|
|
||||||
|
#define COND_WAIT_INFINITE 0xFFFFFFFF
|
||||||
|
|
||||||
|
condvar_t condInit(void);
|
||||||
|
void condFree(condvar_t cond);
|
||||||
|
|
||||||
|
void condWake(condvar_t cond);
|
||||||
|
void condWakeAll(condvar_t cond);
|
||||||
|
|
||||||
|
void condWait(condvar_t cond, cmutex_t mtx);
|
||||||
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -24,10 +24,7 @@ static DWORD _toWin32Creation(filemode_t mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileExists(const char *fname) {
|
bool fileExists(const char *fname) {
|
||||||
DWORD dwAttrib = GetFileAttributesA(fname);
|
return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES;
|
||||||
|
|
||||||
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
|
|
||||||
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file_t fileOpen(const char *fname, filemode_t mode) {
|
file_t fileOpen(const char *fname, filemode_t mode) {
|
||||||
|
|
@ -95,6 +92,12 @@ uint64 fileTell(file_t ctx) {
|
||||||
return result == TRUE ? (uint64)tell.QuadPart : 0;
|
return result == TRUE ? (uint64)tell.QuadPart : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 fileGetTime(file_t ctx) {
|
||||||
|
uint64 fp_time = 0;
|
||||||
|
GetFileTime((HANDLE)ctx, NULL, NULL, (FILETIME *)&fp_time);
|
||||||
|
return fp_time;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -164,6 +167,10 @@ void fileRewind(file_t ctx) {
|
||||||
uint64 fileTell(file_t ctx) {
|
uint64 fileTell(file_t ctx) {
|
||||||
return (uint64)ftell((FILE*)ctx);
|
return (uint64)ftell((FILE*)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 fileGetTime(file_t ctx) {
|
||||||
|
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static str_t _readWholeInternalStr(file_t ctx) {
|
static str_t _readWholeInternalStr(file_t ctx) {
|
||||||
|
|
@ -292,3 +299,13 @@ bool fileWriteWholeText(const char *fname, strview_t string) {
|
||||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string) {
|
bool fileWriteWholeTextFP(file_t ctx, strview_t string) {
|
||||||
return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len });
|
return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 fileGetTimePath(const char *path) {
|
||||||
|
file_t fp = fileOpen(path, FILE_READ);
|
||||||
|
if (!fileIsValid(fp)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint64 fp_time = fileGetTime(fp);
|
||||||
|
fileClose(fp);
|
||||||
|
return fp_time;
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,9 @@ bool fileWriteWholeFP(file_t ctx, filebuf_t data);
|
||||||
bool fileWriteWholeText(const char *fname, strview_t string);
|
bool fileWriteWholeText(const char *fname, strview_t string);
|
||||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string);
|
bool fileWriteWholeTextFP(file_t ctx, strview_t string);
|
||||||
|
|
||||||
|
uint64 fileGetTime(file_t ctx);
|
||||||
|
uint64 fileGetTimePath(const char *path);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "os.h"
|
// #include "os.h"
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#include "vec.h"
|
#include "vec.h"
|
||||||
|
|
@ -48,6 +48,62 @@ static void _setField(vec(http_field_t) *fields_vec, const char *key, const char
|
||||||
vecAppend(*fields_vec, field);
|
vecAppend(*fields_vec, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _parseFields(vec(http_field_t) *fields, str_istream_t *in) {
|
||||||
|
strview_t line;
|
||||||
|
|
||||||
|
do {
|
||||||
|
line = istrGetview(in, '\r');
|
||||||
|
|
||||||
|
usize pos = strvFind(line, ':', 0);
|
||||||
|
if(pos != STRV_NOT_FOUND) {
|
||||||
|
strview_t key = strvSub(line, 0, pos);
|
||||||
|
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
|
||||||
|
|
||||||
|
char *key_str = NULL;
|
||||||
|
char *value_str = NULL;
|
||||||
|
|
||||||
|
key_str = strvCopy(key).buf;
|
||||||
|
value_str = strvCopy(value).buf;
|
||||||
|
|
||||||
|
_setField(fields, key_str, value_str);
|
||||||
|
|
||||||
|
free(key_str);
|
||||||
|
free(value_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
istrSkip(in, 2); // skip \r\n
|
||||||
|
} while(line.len > 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == HTTP STATUS =============================================================
|
||||||
|
|
||||||
|
const char *httpGetStatusString(resstatus_t status) {
|
||||||
|
switch (status) {
|
||||||
|
case STATUS_OK: return "OK";
|
||||||
|
case STATUS_CREATED: return "CREATED";
|
||||||
|
case STATUS_ACCEPTED: return "ACCEPTED";
|
||||||
|
case STATUS_NO_CONTENT: return "NO CONTENT";
|
||||||
|
case STATUS_RESET_CONTENT: return "RESET CONTENT";
|
||||||
|
case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT";
|
||||||
|
case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES";
|
||||||
|
case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY";
|
||||||
|
case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY";
|
||||||
|
case STATUS_NOT_MODIFIED: return "NOT MODIFIED";
|
||||||
|
case STATUS_BAD_REQUEST: return "BAD REQUEST";
|
||||||
|
case STATUS_UNAUTHORIZED: return "UNAUTHORIZED";
|
||||||
|
case STATUS_FORBIDDEN: return "FORBIDDEN";
|
||||||
|
case STATUS_NOT_FOUND: return "NOT FOUND";
|
||||||
|
case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE";
|
||||||
|
case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR";
|
||||||
|
case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED";
|
||||||
|
case STATUS_BAD_GATEWAY: return "BAD GATEWAY";
|
||||||
|
case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE";
|
||||||
|
case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT";
|
||||||
|
case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
// == HTTP VERSION ============================================================
|
// == HTTP VERSION ============================================================
|
||||||
|
|
||||||
int httpVerNumber(http_version_t ver) {
|
int httpVerNumber(http_version_t ver) {
|
||||||
|
|
@ -57,22 +113,65 @@ int httpVerNumber(http_version_t ver) {
|
||||||
// == HTTP REQUEST ============================================================
|
// == HTTP REQUEST ============================================================
|
||||||
|
|
||||||
http_request_t reqInit() {
|
http_request_t reqInit() {
|
||||||
http_request_t req;
|
http_request_t req = {0};
|
||||||
memset(&req, 0, sizeof(req));
|
reqSetUri(&req, strvInit(""));
|
||||||
reqSetUri(&req, strvInit("/"));
|
|
||||||
req.version = (http_version_t){1, 1};
|
req.version = (http_version_t){1, 1};
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reqFree(http_request_t *ctx) {
|
http_request_t reqParse(const char *request) {
|
||||||
for (uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
http_request_t req = {0};
|
||||||
free(ctx->fields[i].key);
|
str_istream_t in = istrInit(request);
|
||||||
free(ctx->fields[i].value);
|
|
||||||
|
// get data
|
||||||
|
|
||||||
|
strview_t method = strvTrim(istrGetview(&in, '/'));
|
||||||
|
istrSkip(&in, 1); // skip /
|
||||||
|
strview_t page = strvTrim(istrGetview(&in, ' '));
|
||||||
|
strview_t http = strvTrim(istrGetview(&in, '\n'));
|
||||||
|
|
||||||
|
istrSkip(&in, 1); // skip \n
|
||||||
|
|
||||||
|
_parseFields(&req.fields, &in);
|
||||||
|
|
||||||
|
strview_t body = strvTrim(istrGetviewLen(&in, 0, SIZE_MAX));
|
||||||
|
|
||||||
|
// parse data
|
||||||
|
|
||||||
|
// -- method
|
||||||
|
const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" };
|
||||||
|
const int methods_count = sizeof(methods) / sizeof(*methods);
|
||||||
|
|
||||||
|
for (int i = 0; i < methods_count; ++i) {
|
||||||
|
if (strvCompare(method, strvInit(methods[i])) == 0) {
|
||||||
|
req.method = (reqtype_t)i;
|
||||||
}
|
}
|
||||||
vecFree(ctx->fields);
|
}
|
||||||
free(ctx->uri);
|
|
||||||
free(ctx->body);
|
// -- page
|
||||||
memset(ctx, 0, sizeof(http_request_t));
|
req.uri = strvCopy(page).buf;
|
||||||
|
|
||||||
|
// -- http
|
||||||
|
in = istrInitLen(http.buf, http.len);
|
||||||
|
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
|
||||||
|
istrGetu8(&in, &req.version.major);
|
||||||
|
istrSkip(&in, 1); // skip .
|
||||||
|
istrGetu8(&in, &req.version.minor);
|
||||||
|
|
||||||
|
// -- body
|
||||||
|
req.body = strvCopy(body).buf;
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reqFree(http_request_t ctx) {
|
||||||
|
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
||||||
|
free(it->key);
|
||||||
|
free(it->value);
|
||||||
|
}
|
||||||
|
vecFree(ctx.fields);
|
||||||
|
free(ctx.uri);
|
||||||
|
free(ctx.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool reqHasField(http_request_t *ctx, const char *key) {
|
bool reqHasField(http_request_t *ctx, const char *key) {
|
||||||
|
|
@ -91,15 +190,10 @@ void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
||||||
void reqSetUri(http_request_t *ctx, strview_t uri) {
|
void reqSetUri(http_request_t *ctx, strview_t uri) {
|
||||||
if (strvIsEmpty(uri)) return;
|
if (strvIsEmpty(uri)) return;
|
||||||
free(ctx->uri);
|
free(ctx->uri);
|
||||||
if (uri.buf[0] != '/') {
|
if (uri.buf[0] == '/') {
|
||||||
ctx->uri = (char *)realloc(ctx->uri, uri.len + 1);
|
strvRemovePrefix(uri, 1);
|
||||||
ctx->uri[0] = '/';
|
|
||||||
memcpy(ctx->uri + 1, uri.buf, uri.len);
|
|
||||||
ctx->uri[uri.len] = '\0';
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
ctx->uri = strvCopy(uri).buf;
|
ctx->uri = strvCopy(uri).buf;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
str_ostream_t reqPrepare(http_request_t *ctx) {
|
str_ostream_t reqPrepare(http_request_t *ctx) {
|
||||||
|
|
@ -115,7 +209,7 @@ str_ostream_t reqPrepare(http_request_t *ctx) {
|
||||||
default: err("unrecognized method: %d", method); goto error;
|
default: err("unrecognized method: %d", method); goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n",
|
ostrPrintf(&out, "%s /%s HTTP/%hhu.%hhu\r\n",
|
||||||
method, ctx->uri, ctx->version.major, ctx->version.minor
|
method, ctx->uri, ctx->version.major, ctx->version.minor
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -139,18 +233,49 @@ str_t reqString(http_request_t *ctx) {
|
||||||
|
|
||||||
// == HTTP RESPONSE ===========================================================
|
// == HTTP RESPONSE ===========================================================
|
||||||
|
|
||||||
http_response_t resInit() {
|
http_response_t resParse(const char *data) {
|
||||||
return (http_response_t) {0};
|
http_response_t ctx = {0};
|
||||||
|
str_istream_t in = istrInit(data);
|
||||||
|
|
||||||
|
char hp[5];
|
||||||
|
istrGetstringBuf(&in, hp, 5);
|
||||||
|
if(stricmp(hp, "http") != 0) {
|
||||||
|
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
istrSkip(&in, 1); // skip /
|
||||||
|
istrGetu8(&in, &ctx.version.major);
|
||||||
|
istrSkip(&in, 1); // skip .
|
||||||
|
istrGetu8(&in, &ctx.version.minor);
|
||||||
|
istrGeti32(&in, (int32*)&ctx.status_code);
|
||||||
|
|
||||||
|
istrIgnore(&in, '\n');
|
||||||
|
istrSkip(&in, 1); // skip \n
|
||||||
|
|
||||||
|
resParseFields(&ctx, &in);
|
||||||
|
|
||||||
|
const char *tran_encoding = resGetField(&ctx, "transfer-encoding");
|
||||||
|
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
||||||
|
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
||||||
|
vecClear(ctx.body);
|
||||||
|
vecReserve(ctx.body, body.len);
|
||||||
|
memcpy(ctx.body, body.buf, body.len);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// fatal("chunked encoding not implemented yet");
|
||||||
|
err("chunked encoding not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resFree(http_response_t *ctx) {
|
void resFree(http_response_t ctx) {
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
||||||
free(ctx->fields[i].key);
|
free(it->key);
|
||||||
free(ctx->fields[i].value);
|
free(it->value);
|
||||||
}
|
}
|
||||||
vecFree(ctx->fields);
|
vecFree(ctx.fields);
|
||||||
vecFree(ctx->body);
|
vecFree(ctx.body);
|
||||||
memset(ctx, 0, sizeof(http_response_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool resHasField(http_response_t *ctx, const char *key) {
|
bool resHasField(http_response_t *ctx, const char *key) {
|
||||||
|
|
@ -162,6 +287,10 @@ bool resHasField(http_response_t *ctx, const char *key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resSetField(http_response_t *ctx, const char *key, const char *value) {
|
||||||
|
_setField(&ctx->fields, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
const char *resGetField(http_response_t *ctx, const char *field) {
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||||
if(stricmp(ctx->fields[i].key, field) == 0) {
|
if(stricmp(ctx->fields[i].key, field) == 0) {
|
||||||
|
|
@ -171,63 +300,30 @@ const char *resGetField(http_response_t *ctx, const char *field) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resParse(http_response_t *ctx, const char *data) {
|
void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
||||||
str_istream_t in = istrInit(data);
|
_parseFields(&ctx->fields, in);
|
||||||
|
|
||||||
char hp[5];
|
|
||||||
istrGetstringBuf(&in, hp, 5);
|
|
||||||
if(stricmp(hp, "http") != 0) {
|
|
||||||
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
istrSkip(&in, 1); // skip /
|
|
||||||
istrGetu8(&in, &ctx->version.major);
|
|
||||||
istrSkip(&in, 1); // skip .
|
|
||||||
istrGetu8(&in, &ctx->version.minor);
|
|
||||||
istrGeti32(&in, (int32*)&ctx->status_code);
|
|
||||||
|
|
||||||
istrIgnore(&in, '\n');
|
|
||||||
istrSkip(&in, 1); // skip \n
|
|
||||||
|
|
||||||
resParseFields(ctx, &in);
|
|
||||||
|
|
||||||
const char *tran_encoding = resGetField(ctx, "transfer-encoding");
|
|
||||||
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
|
||||||
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
|
||||||
vecClear(ctx->body);
|
|
||||||
vecReserve(ctx->body, body.len);
|
|
||||||
memcpy(ctx->body, body.buf, body.len);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fatal("chunked encoding not implemented yet");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
str_ostream_t resPrepare(http_response_t *ctx) {
|
||||||
strview_t line;
|
str_ostream_t out = ostrInitLen(1024);
|
||||||
|
|
||||||
do {
|
ostrPrintf(
|
||||||
line = istrGetview(in, '\r');
|
&out, "HTTP/%hhu.%hhu %d %s\r\n",
|
||||||
|
ctx->version.major, ctx->version.minor,
|
||||||
usize pos = strvFind(line, ':', 0);
|
ctx->status_code, httpGetStatusString(ctx->status_code)
|
||||||
if(pos != STRV_NOT_FOUND) {
|
);
|
||||||
strview_t key = strvSub(line, 0, pos);
|
for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) {
|
||||||
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
|
ostrPrintf(&out, "%s: %s\r\n", field->key, field->value);
|
||||||
|
|
||||||
char *key_str = NULL;
|
|
||||||
char *value_str = NULL;
|
|
||||||
|
|
||||||
key_str = strvCopy(key).buf;
|
|
||||||
value_str = strvCopy(value).buf;
|
|
||||||
|
|
||||||
_setField(&ctx->fields, key_str, value_str);
|
|
||||||
|
|
||||||
free(key_str);
|
|
||||||
free(value_str);
|
|
||||||
}
|
}
|
||||||
|
ostrPuts(&out, "\r\n");
|
||||||
|
ostrAppendview(&out, strvInitLen(ctx->body, vecLen(ctx->body)));
|
||||||
|
|
||||||
istrSkip(in, 2); // skip \r\n
|
return out;
|
||||||
} while(line.len > 2);
|
}
|
||||||
|
|
||||||
|
str_t resString(http_response_t *ctx) {
|
||||||
|
str_ostream_t out = resPrepare(ctx);
|
||||||
|
return ostrAsStr(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == HTTP CLIENT =============================================================
|
// == HTTP CLIENT =============================================================
|
||||||
|
|
@ -238,9 +334,8 @@ http_client_t hcliInit() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void hcliFree(http_client_t *ctx) {
|
void hcliFree(http_client_t ctx) {
|
||||||
strFree(ctx->host_name);
|
strFree(ctx.host_name);
|
||||||
memset(ctx, 0, sizeof(http_client_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
|
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
|
||||||
|
|
@ -283,7 +378,7 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
||||||
reqSetField(req, "Connection", "close");
|
reqSetField(req, "Connection", "close");
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_t res = resInit();
|
http_response_t res = {0};
|
||||||
str_t req_str = strInit();
|
str_t req_str = strInit();
|
||||||
str_ostream_t received = ostrInitLen(1024);
|
str_ostream_t received = ostrInitLen(1024);
|
||||||
|
|
||||||
|
|
@ -327,7 +422,7 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
||||||
received.len--;
|
received.len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
resParse(&res, received.buf);
|
res = resParse(received.buf);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
|
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
|
||||||
|
|
@ -357,8 +452,8 @@ http_response_t httpGet(strview_t hostname, strview_t uri) {
|
||||||
|
|
||||||
http_response_t res = hcliSendRequest(&client, &request);
|
http_response_t res = hcliSendRequest(&client, &request);
|
||||||
|
|
||||||
reqFree(&request);
|
reqFree(request);
|
||||||
hcliFree(&client);
|
hcliFree(client);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +51,8 @@ typedef enum {
|
||||||
STATUS_VERSION_NOT_SUPPORTED = 505,
|
STATUS_VERSION_NOT_SUPPORTED = 505,
|
||||||
} resstatus_t;
|
} resstatus_t;
|
||||||
|
|
||||||
|
const char *httpGetStatusString(resstatus_t status);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8 major;
|
uint8 major;
|
||||||
uint8 minor;
|
uint8 minor;
|
||||||
|
|
@ -77,7 +79,8 @@ typedef struct {
|
||||||
} http_request_t;
|
} http_request_t;
|
||||||
|
|
||||||
http_request_t reqInit(void);
|
http_request_t reqInit(void);
|
||||||
void reqFree(http_request_t *ctx);
|
http_request_t reqParse(const char *request);
|
||||||
|
void reqFree(http_request_t ctx);
|
||||||
|
|
||||||
bool reqHasField(http_request_t *ctx, const char *key);
|
bool reqHasField(http_request_t *ctx, const char *key);
|
||||||
|
|
||||||
|
|
@ -96,14 +99,17 @@ typedef struct {
|
||||||
vec(uint8) body;
|
vec(uint8) body;
|
||||||
} http_response_t;
|
} http_response_t;
|
||||||
|
|
||||||
http_response_t resInit(void);
|
http_response_t resParse(const char *data);
|
||||||
void resFree(http_response_t *ctx);
|
void resFree(http_response_t ctx);
|
||||||
|
|
||||||
bool resHasField(http_response_t *ctx, const char *key);
|
bool resHasField(http_response_t *ctx, const char *key);
|
||||||
|
void resSetField(http_response_t *ctx, const char *key, const char *value);
|
||||||
const char *resGetField(http_response_t *ctx, const char *field);
|
const char *resGetField(http_response_t *ctx, const char *field);
|
||||||
|
|
||||||
void resParse(http_response_t *ctx, const char *data);
|
// void resParse(http_response_t *ctx, const char *data);
|
||||||
void resParseFields(http_response_t *ctx, str_istream_t *in);
|
void resParseFields(http_response_t *ctx, str_istream_t *in);
|
||||||
|
str_ostream_t resPrepare(http_response_t *ctx);
|
||||||
|
str_t resString(http_response_t *ctx);
|
||||||
|
|
||||||
// == HTTP CLIENT =============================================================
|
// == HTTP CLIENT =============================================================
|
||||||
|
|
||||||
|
|
@ -114,7 +120,7 @@ typedef struct {
|
||||||
} http_client_t;
|
} http_client_t;
|
||||||
|
|
||||||
http_client_t hcliInit(void);
|
http_client_t hcliInit(void);
|
||||||
void hcliFree(http_client_t *ctx);
|
void hcliFree(http_client_t ctx);
|
||||||
|
|
||||||
void hcliSetHost(http_client_t *ctx, strview_t hostname);
|
void hcliSetHost(http_client_t *ctx, strview_t hostname);
|
||||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
|
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
|
||||||
|
|
@ -6,8 +6,11 @@
|
||||||
|
|
||||||
// == INI READER ========================================================================
|
// == INI READER ========================================================================
|
||||||
|
|
||||||
static const iniopts_t default_opts = {0};
|
static const iniopts_t default_opts = {
|
||||||
|
.key_value_divider = '='
|
||||||
|
};
|
||||||
|
|
||||||
|
static iniopts_t setDefaultOptions(const iniopts_t *options);
|
||||||
static initable_t *findTable(ini_t *ctx, strview_t name);
|
static initable_t *findTable(ini_t *ctx, strview_t name);
|
||||||
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key);
|
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key);
|
||||||
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options);
|
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options);
|
||||||
|
|
@ -32,14 +35,13 @@ void _iniParseInternal(ini_t *ini, const iniopts_t *options) {
|
||||||
}
|
}
|
||||||
istrSkipWhitespace(&in);
|
istrSkipWhitespace(&in);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_t iniParse(const char *filename, const iniopts_t *options) {
|
ini_t iniParse(const char *filename, const iniopts_t *options) {
|
||||||
ini_t ini = { .text = fileReadWholeText(filename) };
|
ini_t ini = { .text = fileReadWholeText(filename) };
|
||||||
if (strIsEmpty(ini.text)) return ini;
|
if (strIsEmpty(ini.text)) return ini;
|
||||||
if (!options) options = &default_opts;
|
iniopts_t opts = setDefaultOptions(options);
|
||||||
_iniParseInternal(&ini, options);
|
_iniParseInternal(&ini, &opts);
|
||||||
return ini;
|
return ini;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +267,23 @@ winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) {
|
||||||
|
|
||||||
// == PRIVATE FUNCTIONS ========================================================
|
// == PRIVATE FUNCTIONS ========================================================
|
||||||
|
|
||||||
|
static iniopts_t setDefaultOptions(const iniopts_t *options) {
|
||||||
|
if (!options) return default_opts;
|
||||||
|
|
||||||
|
iniopts_t opts = default_opts;
|
||||||
|
|
||||||
|
if (options->merge_duplicate_keys)
|
||||||
|
opts.merge_duplicate_keys = options->merge_duplicate_keys;
|
||||||
|
|
||||||
|
if (options->merge_duplicate_tables)
|
||||||
|
opts.merge_duplicate_tables = options->merge_duplicate_tables;
|
||||||
|
|
||||||
|
if (options->key_value_divider)
|
||||||
|
opts.key_value_divider = options->key_value_divider;
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
static initable_t *findTable(ini_t *ctx, strview_t name) {
|
static initable_t *findTable(ini_t *ctx, strview_t name) {
|
||||||
if (strvIsEmpty(name)) return NULL;
|
if (strvIsEmpty(name)) return NULL;
|
||||||
for (uint32 i = 1; i < vecLen(ctx->tables); ++i) {
|
for (uint32 i = 1; i < vecLen(ctx->tables); ++i) {
|
||||||
|
|
@ -312,7 +331,7 @@ static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) {
|
||||||
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) {
|
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) {
|
||||||
if (!table) fatal("table is null");
|
if (!table) fatal("table is null");
|
||||||
|
|
||||||
strview_t key = strvTrim(istrGetview(in, '='));
|
strview_t key = strvTrim(istrGetview(in, options->key_value_divider));
|
||||||
istrSkip(in, 1);
|
istrSkip(in, 1);
|
||||||
strview_t value = strvTrim(istrGetview(in, '\n'));
|
strview_t value = strvTrim(istrGetview(in, '\n'));
|
||||||
// value might be until EOF, in that case no use in skipping
|
// value might be until EOF, in that case no use in skipping
|
||||||
|
|
@ -27,8 +27,9 @@ typedef struct {
|
||||||
} ini_t;
|
} ini_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool merge_duplicate_tables;
|
bool merge_duplicate_tables; // default false
|
||||||
bool merge_duplicate_keys;
|
bool merge_duplicate_keys; // default false
|
||||||
|
char key_value_divider; // default =
|
||||||
} iniopts_t;
|
} iniopts_t;
|
||||||
|
|
||||||
ini_t iniParse(const char *filename, const iniopts_t *options);
|
ini_t iniParse(const char *filename, const iniopts_t *options);
|
||||||
|
|
@ -63,7 +64,8 @@ typedef struct {
|
||||||
} iniwriter_t;
|
} iniwriter_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool no_discalimer;
|
bool no_discalimer; // default false
|
||||||
|
char key_value_divider; // default =
|
||||||
} winiopts_t;
|
} winiopts_t;
|
||||||
|
|
||||||
iniwriter_t winiInit();
|
iniwriter_t winiInit();
|
||||||
144
colla/jobpool.c
Normal file
144
colla/jobpool.c
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
#include "jobpool.h"
|
||||||
|
|
||||||
|
#include <vec.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
cthread_func_t func;
|
||||||
|
void *arg;
|
||||||
|
} job_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
vec(job_t) jobs;
|
||||||
|
uint32 head;
|
||||||
|
cmutex_t work_mutex;
|
||||||
|
condvar_t work_cond;
|
||||||
|
condvar_t working_cond;
|
||||||
|
int32 working_count;
|
||||||
|
int32 thread_count;
|
||||||
|
bool stop;
|
||||||
|
} _pool_internal_t;
|
||||||
|
|
||||||
|
static job_t _getJob(_pool_internal_t *pool);
|
||||||
|
static int _poolWorker(void *arg);
|
||||||
|
|
||||||
|
jobpool_t poolInit(uint32 num) {
|
||||||
|
if (!num) num = 2;
|
||||||
|
|
||||||
|
_pool_internal_t *pool = malloc(sizeof(_pool_internal_t));
|
||||||
|
*pool = (_pool_internal_t){
|
||||||
|
.work_mutex = mtxInit(),
|
||||||
|
.work_cond = condInit(),
|
||||||
|
.working_cond = condInit(),
|
||||||
|
.thread_count = (int32)num
|
||||||
|
};
|
||||||
|
|
||||||
|
for (usize i = 0; i < num; ++i) {
|
||||||
|
thrDetach(thrCreate(_poolWorker, pool));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
void poolFree(jobpool_t pool_in) {
|
||||||
|
_pool_internal_t *pool = pool_in;
|
||||||
|
if (!pool) return;
|
||||||
|
|
||||||
|
mtxLock(pool->work_mutex);
|
||||||
|
pool->stop = true;
|
||||||
|
condWakeAll(pool->work_cond);
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
|
||||||
|
poolWait(pool);
|
||||||
|
|
||||||
|
vecFree(pool->jobs);
|
||||||
|
mtxFree(pool->work_mutex);
|
||||||
|
condFree(pool->work_cond);
|
||||||
|
condFree(pool->working_cond);
|
||||||
|
|
||||||
|
free(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) {
|
||||||
|
_pool_internal_t *pool = pool_in;
|
||||||
|
if (!pool) return false;
|
||||||
|
|
||||||
|
mtxLock(pool->work_mutex);
|
||||||
|
|
||||||
|
if (pool->head > vecLen(pool->jobs)) {
|
||||||
|
vecClear(pool->jobs);
|
||||||
|
pool->head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_t job = { func, arg };
|
||||||
|
vecAppend(pool->jobs, job);
|
||||||
|
|
||||||
|
condWake(pool->work_cond);
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void poolWait(jobpool_t pool_in) {
|
||||||
|
_pool_internal_t *pool = pool_in;
|
||||||
|
if (!pool) return;
|
||||||
|
|
||||||
|
mtxLock(pool->work_mutex);
|
||||||
|
// while its either
|
||||||
|
// - working and there's still some threads doing some work
|
||||||
|
// - not working and there's still some threads exiting
|
||||||
|
while ((!pool->stop && pool->working_count > 0) ||
|
||||||
|
(pool->stop && pool->thread_count > 0)
|
||||||
|
) {
|
||||||
|
condWait(pool->working_cond, pool->work_mutex);
|
||||||
|
}
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == PRIVATE FUNCTIONS ===================================
|
||||||
|
|
||||||
|
static job_t _getJob(_pool_internal_t *pool) {
|
||||||
|
if (pool->head >= vecLen(pool->jobs)) {
|
||||||
|
pool->head = 0;
|
||||||
|
}
|
||||||
|
job_t job = pool->jobs[pool->head++];
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _poolWorker(void *arg) {
|
||||||
|
_pool_internal_t *pool = arg;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
mtxLock(pool->work_mutex);
|
||||||
|
// wait for a new job
|
||||||
|
while (pool->head >= vecLen(pool->jobs) && !pool->stop) {
|
||||||
|
condWait(pool->work_cond, pool->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pool->stop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_t job = _getJob(pool);
|
||||||
|
pool->working_count++;
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
|
||||||
|
if (job.func) {
|
||||||
|
job.func(job.arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
mtxLock(pool->work_mutex);
|
||||||
|
pool->working_count--;
|
||||||
|
if (!pool->stop &&
|
||||||
|
pool->working_count == 0 &&
|
||||||
|
pool->head == vecLen(pool->jobs)
|
||||||
|
) {
|
||||||
|
condWake(pool->working_cond);
|
||||||
|
}
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->thread_count--;
|
||||||
|
condWake(pool->working_cond);
|
||||||
|
mtxUnlock(pool->work_mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
12
colla/jobpool.h
Normal file
12
colla/jobpool.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <collatypes.h>
|
||||||
|
#include <cthreads.h>
|
||||||
|
|
||||||
|
typedef void *jobpool_t;
|
||||||
|
|
||||||
|
jobpool_t poolInit(uint32 num);
|
||||||
|
void poolFree(jobpool_t pool);
|
||||||
|
|
||||||
|
bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg);
|
||||||
|
void poolWait(jobpool_t pool);
|
||||||
|
|
@ -21,6 +21,7 @@ static const char *_win_skGetErrorString();
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h> // strerror
|
#include <string.h> // strerror
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
#define INVALID_SOCKET (-1)
|
#define INVALID_SOCKET (-1)
|
||||||
#define SOCKET_ERROR (-1)
|
#define SOCKET_ERROR (-1)
|
||||||
|
|
@ -187,6 +188,14 @@ int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *fr
|
||||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
|
#if SOCK_WINDOWS
|
||||||
|
return WSAPoll(to_poll, num_to_poll, timeout);
|
||||||
|
#elif SOCK_POSIX
|
||||||
|
return poll(to_poll, num_to_poll, timeout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool skIsValid(socket_t sock) {
|
bool skIsValid(socket_t sock) {
|
||||||
return sock != INVALID_SOCKET;
|
return sock != INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ extern "C" {
|
||||||
|
|
||||||
typedef struct sockaddr sk_addr_t;
|
typedef struct sockaddr sk_addr_t;
|
||||||
typedef struct sockaddr_in sk_addrin_t;
|
typedef struct sockaddr_in sk_addrin_t;
|
||||||
|
typedef struct pollfd skpoll_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SOCK_TCP,
|
SOCK_TCP,
|
||||||
|
|
@ -54,12 +55,15 @@ socket_t skOpenEx(const char *protocol);
|
||||||
// Opens a socket, check socket_t with skValid
|
// Opens a socket, check socket_t with skValid
|
||||||
socket_t skOpenPro(int af, int type, int protocol);
|
socket_t skOpenPro(int af, int type, int protocol);
|
||||||
|
|
||||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
// Checks that a opened socket is valid, returns true on success
|
||||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
bool skIsValid(socket_t sock);
|
||||||
|
|
||||||
// Closes a socket, returns true on success
|
// Closes a socket, returns true on success
|
||||||
bool skClose(socket_t sock);
|
bool skClose(socket_t sock);
|
||||||
|
|
||||||
|
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||||
|
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||||
|
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
|
|
@ -97,8 +101,8 @@ int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from);
|
||||||
// Receives a datagram and stores the source address.
|
// Receives a datagram and stores the source address.
|
||||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen);
|
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen);
|
||||||
|
|
||||||
// Checks that a opened socket is valid, returns true on success
|
// Wait for an event on some sockets
|
||||||
bool skIsValid(socket_t sock);
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
||||||
|
|
||||||
// Returns latest socket error, returns 0 if there is no error
|
// Returns latest socket error, returns 0 if there is no error
|
||||||
int skGetError(void);
|
int skGetError(void);
|
||||||
|
|
@ -72,7 +72,15 @@ void traceLog(int level, const char *fmt, ...) {
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "cthreads.h"
|
||||||
|
|
||||||
|
static cmutex_t g_mtx = 0;
|
||||||
|
|
||||||
void traceLogVaList(int level, const char *fmt, va_list args) {
|
void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
|
if (!g_mtx) g_mtx = mtxInit();
|
||||||
|
|
||||||
|
mtxLock(g_mtx);
|
||||||
|
|
||||||
char buffer[MAX_TRACELOG_MSG_LENGTH];
|
char buffer[MAX_TRACELOG_MSG_LENGTH];
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
|
||||||
|
|
@ -115,6 +123,8 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||||
if (level == LogFatal) exit(1);
|
if (level == LogFatal) exit(1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
mtxUnlock(g_mtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void traceUseNewline(bool newline) {
|
void traceUseNewline(bool newline) {
|
||||||
|
|
@ -21,6 +21,15 @@ void traceLog(int level, const char *fmt, ...);
|
||||||
void traceLogVaList(int level, const char *fmt, va_list args);
|
void traceLogVaList(int level, const char *fmt, va_list args);
|
||||||
void traceUseNewline(bool use_newline);
|
void traceUseNewline(bool use_newline);
|
||||||
|
|
||||||
|
#ifdef NO_LOG
|
||||||
|
#define tall(...)
|
||||||
|
#define trace(...)
|
||||||
|
#define debug(...)
|
||||||
|
#define info(...)
|
||||||
|
#define warn(...)
|
||||||
|
#define err(...)
|
||||||
|
#define fatal(...)
|
||||||
|
#else
|
||||||
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
||||||
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
|
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
|
||||||
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
|
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
|
||||||
|
|
@ -28,6 +37,7 @@ void traceUseNewline(bool use_newline);
|
||||||
#define warn(...) traceLog(LogWarning, __VA_ARGS__)
|
#define warn(...) traceLog(LogWarning, __VA_ARGS__)
|
||||||
#define err(...) traceLog(LogError, __VA_ARGS__)
|
#define err(...) traceLog(LogError, __VA_ARGS__)
|
||||||
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
|
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
@ -6,10 +6,12 @@ extern "C" {
|
||||||
|
|
||||||
#define vec(T) T *
|
#define vec(T) T *
|
||||||
|
|
||||||
#define vecFree(vec) ((vec) ? free(_vecheader(vec)),NULL : NULL)
|
#define vecFree(vec) ((vec) ? free(_vecheader(vec)), NULL : NULL)
|
||||||
#define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src)))
|
#define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src)))
|
||||||
|
|
||||||
#define vecAppend(vec, val) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (val), _veclen(vec)++)
|
#define vecAppend(vec, ...) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (__VA_ARGS__), _veclen(vec)++)
|
||||||
|
#define vecRemove(vec, ind) ((vec) ? (vec)[(ind)] = (vec)[--_veclen(vec)], NULL : 0)
|
||||||
|
#define vecRemoveIt(vec, it) (vecRemove((vec), (it) - (vec)))
|
||||||
#define vecLen(vec) ((vec) ? _veclen(vec) : 0)
|
#define vecLen(vec) ((vec) ? _veclen(vec) : 0)
|
||||||
#define vecCap(vec) ((vec) ? _veccap(vec) : 0)
|
#define vecCap(vec) ((vec) ? _veccap(vec) : 0)
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue