|
/*
|
|
* DWN - Desktop Window Manager
|
|
* retoor <retoor@molodetz.nl>
|
|
* Specialized Worker Threads - Implementation
|
|
*/
|
|
|
|
#include "thread_workers.h"
|
|
#include "util.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <curl/curl.h>
|
|
#include <errno.h>
|
|
|
|
/* ============================================================================
|
|
* I/O Worker Implementation
|
|
* ============================================================================ */
|
|
|
|
typedef struct {
|
|
char path[512];
|
|
IoCallback callback;
|
|
void *user_data;
|
|
void *data;
|
|
size_t size;
|
|
int error;
|
|
} IoOperation;
|
|
|
|
static AsyncContext *g_io_context = NULL;
|
|
|
|
static size_t dwn_curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
size_t total = size * nmemb;
|
|
IoOperation *op = userp;
|
|
|
|
void *new_data = realloc(op->data, op->size + total + 1);
|
|
if (!new_data) return 0;
|
|
|
|
op->data = new_data;
|
|
memcpy((char*)op->data + op->size, contents, total);
|
|
op->size += total;
|
|
((char*)op->data)[op->size] = '\0';
|
|
|
|
return total;
|
|
}
|
|
|
|
static void io_read_file_worker(void *user_data, atomic_int *cancelled)
|
|
{
|
|
(void)cancelled;
|
|
IoOperation *op = user_data;
|
|
|
|
int fd = open(op->path, O_RDONLY);
|
|
if (fd < 0) {
|
|
op->error = errno;
|
|
return;
|
|
}
|
|
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0) {
|
|
op->error = errno;
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
op->size = st.st_size;
|
|
op->data = malloc(op->size + 1);
|
|
|
|
if (!op->data) {
|
|
op->error = ENOMEM;
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
ssize_t n = read(fd, op->data, op->size);
|
|
close(fd);
|
|
|
|
if (n < 0) {
|
|
op->error = errno;
|
|
free(op->data);
|
|
op->data = NULL;
|
|
return;
|
|
}
|
|
|
|
((char*)op->data)[n] = '\0';
|
|
}
|
|
|
|
static void io_write_file_worker(void *user_data, atomic_int *cancelled)
|
|
{
|
|
(void)cancelled;
|
|
IoOperation *op = user_data;
|
|
|
|
int fd = open(op->path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (fd < 0) {
|
|
op->error = errno;
|
|
return;
|
|
}
|
|
|
|
ssize_t n = write(fd, op->data, op->size);
|
|
close(fd);
|
|
|
|
if (n < 0 || (size_t)n != op->size) {
|
|
op->error = errno;
|
|
return;
|
|
}
|
|
|
|
op->error = 0;
|
|
}
|
|
|
|
static void io_read_dir_worker(void *user_data, atomic_int *cancelled)
|
|
{
|
|
(void)cancelled;
|
|
IoOperation *op = user_data;
|
|
|
|
DIR *dir = opendir(op->path);
|
|
if (!dir) {
|
|
op->error = errno;
|
|
return;
|
|
}
|
|
|
|
size_t total_size = 0;
|
|
struct dirent *entry;
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
total_size += strlen(entry->d_name) + 1;
|
|
}
|
|
|
|
rewinddir(dir);
|
|
|
|
op->data = malloc(total_size + 1);
|
|
if (!op->data) {
|
|
op->error = ENOMEM;
|
|
closedir(dir);
|
|
return;
|
|
}
|
|
|
|
char *p = op->data;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
size_t name_len = strlen(entry->d_name);
|
|
size_t remaining = total_size - (size_t)(p - (char*)op->data);
|
|
if (name_len + 1 > remaining) break;
|
|
memcpy(p, entry->d_name, name_len);
|
|
p[name_len] = '\0';
|
|
p += name_len + 1;
|
|
}
|
|
*p = '\0';
|
|
op->size = p - (char*)op->data;
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
static void io_http_get_worker(void *user_data, atomic_int *cancelled)
|
|
{
|
|
(void)cancelled;
|
|
IoOperation *op = user_data;
|
|
CURL *curl = curl_easy_init();
|
|
|
|
if (!curl) {
|
|
op->error = -1;
|
|
return;
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, op->path);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dwn_curl_write_cb);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, op);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
|
|
|
/* Check for cancellation periodically */
|
|
/* Note: curl doesn't easily support cancellation mid-transfer */
|
|
|
|
CURLcode res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
op->error = res;
|
|
if (op->data) {
|
|
free(op->data);
|
|
op->data = NULL;
|
|
op->size = 0;
|
|
}
|
|
}
|
|
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
static void io_operation_cleanup(Task *task, void *user_data, ThreadStatus status)
|
|
{
|
|
(void)task;
|
|
(void)status;
|
|
IoOperation *op = user_data;
|
|
|
|
if (op->callback) {
|
|
op->callback(op->data, op->size, op->error, op->user_data);
|
|
}
|
|
|
|
if (op->data && op->error != 0) {
|
|
free(op->data);
|
|
}
|
|
free(op);
|
|
}
|
|
|
|
bool io_worker_init(void)
|
|
{
|
|
if (g_io_context) return true;
|
|
g_io_context = async_context_create("io");
|
|
return g_io_context != NULL;
|
|
}
|
|
|
|
void io_worker_shutdown(void)
|
|
{
|
|
if (g_io_context) {
|
|
async_context_destroy(g_io_context);
|
|
g_io_context = NULL;
|
|
}
|
|
}
|
|
|
|
TaskHandle io_read_file_async(const char *path, IoCallback callback, void *user_data)
|
|
{
|
|
if (!g_io_context || !path) return NULL;
|
|
|
|
IoOperation *op = calloc(1, sizeof(IoOperation));
|
|
if (!op) return NULL;
|
|
|
|
strncpy(op->path, path, sizeof(op->path) - 1);
|
|
op->callback = callback;
|
|
op->user_data = user_data;
|
|
|
|
TaskHandle task = task_create_with_callback(
|
|
io_read_file_worker, op, TASK_PRIORITY_NORMAL,
|
|
io_operation_cleanup, op
|
|
);
|
|
|
|
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
|
|
free(op);
|
|
return NULL;
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
TaskHandle io_write_file_async(const char *path, const void *data, size_t size,
|
|
IoCallback callback, void *user_data)
|
|
{
|
|
if (!g_io_context || !path || !data) return NULL;
|
|
|
|
IoOperation *op = calloc(1, sizeof(IoOperation));
|
|
if (!op) return NULL;
|
|
|
|
strncpy(op->path, path, sizeof(op->path) - 1);
|
|
op->data = malloc(size);
|
|
if (!op->data) {
|
|
free(op);
|
|
return NULL;
|
|
}
|
|
memcpy(op->data, data, size);
|
|
op->size = size;
|
|
op->callback = callback;
|
|
op->user_data = user_data;
|
|
|
|
TaskHandle task = task_create_with_callback(
|
|
io_write_file_worker, op, TASK_PRIORITY_NORMAL,
|
|
io_operation_cleanup, op
|
|
);
|
|
|
|
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
|
|
free(op->data);
|
|
free(op);
|
|
return NULL;
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
TaskHandle io_read_dir_async(const char *path, IoCallback callback, void *user_data)
|
|
{
|
|
if (!g_io_context || !path) return NULL;
|
|
|
|
IoOperation *op = calloc(1, sizeof(IoOperation));
|
|
if (!op) return NULL;
|
|
|
|
strncpy(op->path, path, sizeof(op->path) - 1);
|
|
op->callback = callback;
|
|
op->user_data = user_data;
|
|
|
|
TaskHandle task = task_create_with_callback(
|
|
io_read_dir_worker, op, TASK_PRIORITY_NORMAL,
|
|
io_operation_cleanup, op
|
|
);
|
|
|
|
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
|
|
free(op);
|
|
return NULL;
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
TaskHandle io_http_get_async(const char *url, IoCallback callback, void *user_data)
|
|
{
|
|
if (!g_io_context || !url) return NULL;
|
|
|
|
IoOperation *op = calloc(1, sizeof(IoOperation));
|
|
if (!op) return NULL;
|
|
|
|
strncpy(op->path, url, sizeof(op->path) - 1);
|
|
op->callback = callback;
|
|
op->user_data = user_data;
|
|
|
|
TaskHandle task = task_create_with_callback(
|
|
io_http_get_worker, op, TASK_PRIORITY_NORMAL,
|
|
io_operation_cleanup, op
|
|
);
|
|
|
|
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
|
|
free(op);
|
|
return NULL;
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* System Worker Implementation
|
|
* ============================================================================ */
|
|
|
|
static AsyncContext *g_system_context = NULL;
|
|
static SystemStats g_cached_stats;
|
|
static pthread_mutex_t g_stats_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static uint32_t g_stats_timer_id = 0;
|
|
|
|
static void parse_meminfo(uint64_t *total, uint64_t *available)
|
|
{
|
|
*total = 0;
|
|
*available = 0;
|
|
|
|
FILE *f = fopen("/proc/meminfo", "r");
|
|
if (!f) return;
|
|
|
|
char line[256];
|
|
while (fgets(line, sizeof(line), f)) {
|
|
if (strncmp(line, "MemTotal:", 9) == 0) {
|
|
sscanf(line, "MemTotal: %lu", total);
|
|
*total *= 1024;
|
|
} else if (strncmp(line, "MemAvailable:", 13) == 0) {
|
|
sscanf(line, "MemAvailable: %lu", available);
|
|
*available *= 1024;
|
|
break;
|
|
} else if (strncmp(line, "MemFree:", 8) == 0 && *available == 0) {
|
|
/* Fallback if MemAvailable not present */
|
|
sscanf(line, "MemFree: %lu", available);
|
|
*available *= 1024;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
static void parse_loadavg(float load[3])
|
|
{
|
|
FILE *f = fopen("/proc/loadavg", "r");
|
|
if (!f) return;
|
|
|
|
if (fscanf(f, "%f %f %f", &load[0], &load[1], &load[2]) != 3) {
|
|
load[0] = load[1] = load[2] = 0.0f;
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
static void parse_stat(float *cpu_percent)
|
|
{
|
|
static uint64_t prev_idle = 0, prev_total = 0;
|
|
|
|
FILE *f = fopen("/proc/stat", "r");
|
|
if (!f) return;
|
|
|
|
char line[256];
|
|
if (fgets(line, sizeof(line), f)) {
|
|
unsigned long user, nice, system, idle, iowait, irq, softirq, steal;
|
|
sscanf(line, "cpu %lu %lu %lu %lu %lu %lu %lu %lu",
|
|
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal);
|
|
|
|
uint64_t idle_time = idle + iowait;
|
|
uint64_t total_time = user + nice + system + idle + iowait + irq + softirq + steal;
|
|
|
|
uint64_t diff_idle = idle_time - prev_idle;
|
|
uint64_t diff_total = total_time - prev_total;
|
|
|
|
if (diff_total > 0) {
|
|
*cpu_percent = 100.0f * (1.0f - ((float)diff_idle / diff_total));
|
|
}
|
|
|
|
prev_idle = idle_time;
|
|
prev_total = total_time;
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
static void system_stats_worker(void *user_data, atomic_int *cancelled)
|
|
{
|
|
(void)user_data;
|
|
(void)cancelled;
|
|
|
|
SystemStats stats;
|
|
memset(&stats, 0, sizeof(stats));
|
|
|
|
parse_stat(&stats.cpu_percent);
|
|
|
|
uint64_t mem_available = 0;
|
|
parse_meminfo(&stats.memory_total, &mem_available);
|
|
stats.memory_used = stats.memory_total - mem_available;
|
|
|
|
if (stats.memory_total > 0) {
|
|
stats.memory_percent = 100.0f * (float)stats.memory_used / stats.memory_total;
|
|
}
|
|
|
|
parse_loadavg(stats.load_avg);
|
|
|
|
FILE *f = fopen("/proc/uptime", "r");
|
|
if (f) {
|
|
double uptime = 0.0;
|
|
if (fscanf(f, "%lf", &uptime) == 1) {
|
|
stats.uptime_seconds = (uint64_t)uptime;
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
pthread_mutex_lock(&g_stats_mutex);
|
|
g_cached_stats = stats;
|
|
pthread_mutex_unlock(&g_stats_mutex);
|
|
}
|
|
|
|
static void stats_timer_callback(TimerId id, void *user_data)
|
|
{
|
|
(void)id;
|
|
(void)user_data;
|
|
|
|
ASYNC(system_stats_worker, NULL);
|
|
}
|
|
|
|
bool system_worker_init(void)
|
|
{
|
|
if (g_system_context) return true;
|
|
|
|
g_system_context = async_context_create("system");
|
|
if (!g_system_context) return false;
|
|
|
|
/* Initial stats read */
|
|
system_stats_worker(NULL, NULL);
|
|
|
|
/* Start periodic updates */
|
|
g_stats_timer_id = timer_schedule_repeating(1000, stats_timer_callback, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
void system_worker_shutdown(void)
|
|
{
|
|
if (g_stats_timer_id) {
|
|
timer_cancel(g_stats_timer_id);
|
|
g_stats_timer_id = 0;
|
|
}
|
|
|
|
if (g_system_context) {
|
|
async_context_destroy(g_system_context);
|
|
g_system_context = NULL;
|
|
}
|
|
}
|
|
|
|
bool system_worker_get_cached_stats(SystemStats *stats)
|
|
{
|
|
if (!stats) return false;
|
|
|
|
pthread_mutex_lock(&g_stats_mutex);
|
|
*stats = g_cached_stats;
|
|
pthread_mutex_unlock(&g_stats_mutex);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Deferred Worker Implementation
|
|
* ============================================================================ */
|
|
|
|
static bool g_deferred_initialized = false;
|
|
|
|
bool deferred_worker_init(void)
|
|
{
|
|
g_deferred_initialized = true;
|
|
return true;
|
|
}
|
|
|
|
void deferred_worker_shutdown(void)
|
|
{
|
|
g_deferred_initialized = false;
|
|
}
|
|
|
|
DeferredId deferred_after(uint64_t delay_ms, DeferredCallback callback, void *user_data)
|
|
{
|
|
if (!g_deferred_initialized) return 0;
|
|
return timer_schedule(delay_ms, (TimerCallback)callback, user_data);
|
|
}
|
|
|
|
DeferredId deferred_every(uint64_t interval_ms, DeferredCallback callback, void *user_data)
|
|
{
|
|
if (!g_deferred_initialized) return 0;
|
|
return timer_schedule_repeating(interval_ms, (TimerCallback)callback, user_data);
|
|
}
|
|
|
|
bool deferred_cancel(DeferredId id)
|
|
{
|
|
return timer_cancel(id);
|
|
}
|
|
|
|
uint32_t deferred_process(void)
|
|
{
|
|
return timer_process();
|
|
}
|
|
|
|
int deferred_get_poll_fd(void)
|
|
{
|
|
return timer_get_poll_fd();
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Integration
|
|
* ============================================================================ */
|
|
|
|
bool thread_workers_init(void)
|
|
{
|
|
if (!io_worker_init()) return false;
|
|
if (!system_worker_init()) {
|
|
io_worker_shutdown();
|
|
return false;
|
|
}
|
|
if (!deferred_worker_init()) {
|
|
system_worker_shutdown();
|
|
io_worker_shutdown();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void thread_workers_shutdown(void)
|
|
{
|
|
deferred_worker_shutdown();
|
|
system_worker_shutdown();
|
|
io_worker_shutdown();
|
|
}
|
|
|
|
bool thread_workers_process_all(void)
|
|
{
|
|
bool work_done = false;
|
|
|
|
/* Process timers */
|
|
if (deferred_process() > 0) work_done = true;
|
|
|
|
/* Process event bus */
|
|
if (event_bus_process(event_bus_default()) > 0) work_done = true;
|
|
|
|
return work_done;
|
|
}
|
|
|
|
int thread_workers_get_poll_fds(int *fds)
|
|
{
|
|
int count = 0;
|
|
|
|
int fd = deferred_get_poll_fd();
|
|
if (fd >= 0) fds[count++] = fd;
|
|
|
|
/* Note: event bus fd would be added here if exposed */
|
|
|
|
return count;
|
|
}
|