Add export screen
This commit is contained in:
parent
961179db27
commit
6ae67c4732
6
Makefile
6
Makefile
@ -7,7 +7,8 @@ LD=clang
|
||||
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
|
||||
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
|
||||
src/ui/statrange.c src/config.c src/ui/blankscreen.c
|
||||
src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
|
||||
src/ui/exportscreen.c
|
||||
PROG=rpi4b-temp-humidity
|
||||
|
||||
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
||||
@ -40,6 +41,8 @@ bin/main.o bin/ui/statsby.o: src/ui/statsby.h
|
||||
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
|
||||
bin/main.o bin/ui/blankscreen.o: src/ui/blankscreen.h
|
||||
bin/main.o bin/ui/statrange.o: src/ui/statrange.h
|
||||
vin/main.o bin/ui/exportscreen.o: src/ui/exportscreen.h
|
||||
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
|
||||
|
||||
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
|
||||
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"
|
||||
@ -58,6 +61,7 @@ ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||
-DDEFAULT_DATABASE_LOCATION="\"${DEFAULT_DATABASE_LOCATION}\""\
|
||||
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
|
||||
-DDEFAULT_TEMP_UNIT="'${DEFAULT_TEMP_UNIT}'"\
|
||||
-DDEFAULT_EXPORT_FILE_NAME="\"${DEFAULT_EXPORT_FILE_NAME}\""\
|
||||
-o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||
|
||||
clean:
|
||||
|
@ -12,3 +12,4 @@ DEFAULT_FAIL_LIMIT=5
|
||||
DEFAULT_DATABASE_LOCATION=/var/db/rpi4-temp-humidity/db.sqlite
|
||||
DEFAULT_REFRESH_TIME=5000
|
||||
DEFAULT_TEMP_UNIT=F
|
||||
DEFAULT_EXPORT_FILE_NAME=env_data
|
||||
|
25
src/config.c
25
src/config.c
@ -93,7 +93,7 @@ static int parse_uint_arr_callback(struct figpar_config *opt, uint32_t line,
|
||||
if (last_char && !isdigit(last_char)) {
|
||||
warnx("line %" PRIu32 ": not a valid number array \"%s\"",
|
||||
line, value);
|
||||
FREE_CHECKED(arr->arr);
|
||||
free(arr->arr);
|
||||
free(arr);
|
||||
return 1;
|
||||
}
|
||||
@ -118,7 +118,7 @@ static int parse_uint_arr_callback(struct figpar_config *opt, uint32_t line,
|
||||
|
||||
static int parse_str_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
FREE_CHECKED(opt->value.str);
|
||||
free(opt->value.str);
|
||||
if (!value[0]) {
|
||||
opt->type = FIGPAR_TYPE_STR;
|
||||
opt->value.str = NULL;
|
||||
@ -200,6 +200,11 @@ static void set_options_from_entries(struct figpar_config *entries,
|
||||
GLOBAL_OPTS.temp_unit = entries[15].value.num;
|
||||
|
||||
GLOBAL_OPTS.bl_pin = entries[16].value.num;
|
||||
|
||||
entries[17].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.export_file_name = steal_opt_if_set(entries[17].value.str);
|
||||
LOG_VERBOSE("Using export_file_name: \"%s\"\n",
|
||||
GLOBAL_OPTS.export_file_name);
|
||||
}
|
||||
|
||||
static char *strdup_default_opt(const char *def) {
|
||||
@ -298,13 +303,19 @@ void parse_config_file(const char *path) {
|
||||
.directive = "temp_unit",
|
||||
.type = FIGPAR_TYPE_INT,
|
||||
.action = parse_temp_unit_callback,
|
||||
.value = {.num = DEFAULT_TEMP_UNIT}
|
||||
.value = {.num = DEFAULT_TEMP_UNIT},
|
||||
},
|
||||
{
|
||||
.directive = "bl_pin",
|
||||
.type = FIGPAR_TYPE_INT,
|
||||
.action = parse_int_callback,
|
||||
.value = {.num = -1}
|
||||
.value = {.num = -1},
|
||||
},
|
||||
{
|
||||
.directive = "export_file_name",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_EXPORT_FILE_NAME)},
|
||||
},
|
||||
{ .directive = NULL },
|
||||
};
|
||||
@ -321,11 +332,11 @@ void parse_config_file(const char *path) {
|
||||
for (size_t i = 0; i < entry_count; ++i) {
|
||||
switch (entries[i].type) {
|
||||
case FIGPAR_TYPE_STR:
|
||||
FREE_CHECKED(entries[i].value.str);
|
||||
free(entries[i].value.str);
|
||||
break;
|
||||
case CONFIG_UINT_ARR_TYPE:
|
||||
FREE_CHECKED(((struct UIntArr *) entries[i].value.data)->arr);
|
||||
FREE_CHECKED(entries[i].value.data);
|
||||
free(((struct UIntArr *) entries[i].value.data)->arr);
|
||||
free(entries[i].value.data);
|
||||
break;
|
||||
default: ; // ignore
|
||||
}
|
||||
|
228
src/drive.c
Normal file
228
src/drive.c
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* drive.c - Simple abstractions for working with file systems and drives
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "drive.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int compare_drives(const Drive *d1, const Drive *d2) {
|
||||
return strcmp(d1->name, d2->name);
|
||||
}
|
||||
|
||||
Drive *drive_list_usb_drives(size_t *count) {
|
||||
DIR *dir = opendir("/dev");
|
||||
if (!dir) {
|
||||
warn("failed to open /dev");
|
||||
return NULL;
|
||||
}
|
||||
Drive *drives = NULL;
|
||||
*count = 0;
|
||||
struct dirent *sd;
|
||||
while ((sd = readdir(dir))) {
|
||||
if (strncmp(sd->d_name, "da", 2) == 0 && isdigit(sd->d_name[2])) {
|
||||
drives = realloc_checked(drives, sizeof(Drive) * ++(*count));
|
||||
Drive *cur = &drives[*count - 1];
|
||||
cur->name = strdup_checked(sd->d_name);
|
||||
asprintf_checked(&cur->path, "/dev/%s", sd->d_name);
|
||||
cur->fs = drive_guess_fs(cur->path);
|
||||
cur->mnt = NULL;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
if (*count) {
|
||||
qsort(drives, *count, sizeof(Drive),
|
||||
(int(*)(const void *, const void *))compare_drives);
|
||||
}
|
||||
return drives;
|
||||
}
|
||||
|
||||
void drive_free_drives(Drive *drives, size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
free(drives[i].name);
|
||||
free(drives[i].path);
|
||||
free(drives[i].fs);
|
||||
free(drives[i].mnt);
|
||||
}
|
||||
free(drives);
|
||||
}
|
||||
|
||||
static void close_pipe(void *ptr) {
|
||||
int *pipe = ptr;
|
||||
if (pipe[0] >= 0) {
|
||||
close(pipe[0]);
|
||||
}
|
||||
if (pipe[1] >= 0) {
|
||||
close(pipe[1]);
|
||||
}
|
||||
}
|
||||
|
||||
char *drive_guess_fs(const char *dev) {
|
||||
char *result_fs = NULL;
|
||||
int ctop[2] = {0, 0};
|
||||
pthread_cleanup_push(close_pipe, ctop);
|
||||
pthread_cleanup_push(free, result_fs);
|
||||
if (pipe(ctop) < 0) {
|
||||
warn("failed to create pipe");
|
||||
goto cleanup;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
warn("fork(2) failed");
|
||||
goto cleanup;
|
||||
} else if (pid == 0) {
|
||||
// child
|
||||
close(ctop[0]); // close read end
|
||||
if (dup2(ctop[1], 1) < 0) {
|
||||
warn("dup2(2) failed");
|
||||
close(ctop[1]);
|
||||
exit(1);
|
||||
}
|
||||
if (!GLOBAL_OPTS.verbose) {
|
||||
freopen("/dev/null", "w", stderr); // prevent warnings for unknown fs
|
||||
}
|
||||
execl("/usr/sbin/fstyp", "/usr/sbin/fstyp", dev, NULL);
|
||||
close(ctop[1]);
|
||||
exit(1);
|
||||
}
|
||||
// parent
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
warn("waiting for child failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if (WEXITSTATUS(status) != 0) {
|
||||
// no error, we probably just don't know the FS type
|
||||
goto cleanup;
|
||||
}
|
||||
char read_buf[32];
|
||||
ssize_t read_count = read(ctop[0], read_buf, 32);
|
||||
if (read_count < 0) {
|
||||
warn("reading from pipe failed");
|
||||
goto cleanup;
|
||||
}
|
||||
read_buf[read_count - 1] = '\0';
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
result_fs = strdup_checked(read_buf);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
cleanup:
|
||||
pthread_cleanup_pop(false); // dont free result
|
||||
pthread_cleanup_pop(true);
|
||||
return result_fs;
|
||||
}
|
||||
|
||||
bool drive_newfs_msdos(Drive *drive) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
warn("fork(2) failed");
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
// child
|
||||
if (!GLOBAL_OPTS.verbose) {
|
||||
freopen("/dev/null", "w", stdout);
|
||||
}
|
||||
execl("/sbin/newfs_msdos", "/sbin/newfs_msdos", "-F32", drive->path, NULL);
|
||||
warn("execl(2) failed");
|
||||
exit(1);
|
||||
} else {
|
||||
// parent
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
warn("waiting for child failed");
|
||||
return false;
|
||||
}
|
||||
if (WEXITSTATUS(status) != 0) {
|
||||
warnx("creating FAT32 FS failed");
|
||||
return false;
|
||||
}
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
free(drive->fs);
|
||||
drive->fs = strdup_checked("msdosfs");
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool drive_mount(Drive *drive) {
|
||||
bool status = true;
|
||||
char *name = NULL;
|
||||
pthread_cleanup_push(free, name);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
name = strdup_checked("/tmp/mntXXXXXX");
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
if (!mkdtemp(name)) {
|
||||
warn("failed to create temp mount point");
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
warn("fork(2) failed");
|
||||
status = false;
|
||||
goto cleanup;
|
||||
} else if (pid == 0) {
|
||||
// child
|
||||
if (!GLOBAL_OPTS.verbose) {
|
||||
freopen("/dev/null", "w", stdout);
|
||||
}
|
||||
execl("/sbin/mount", "/sbin/mount", "-t", drive->fs, drive->path, name, NULL);
|
||||
warn("execl(2) failed");
|
||||
abort();
|
||||
}
|
||||
// parent
|
||||
int exit_code;
|
||||
if (waitpid(pid, &exit_code, 0) < 0) {
|
||||
warn("waiting for child failed");
|
||||
rmdir(name);
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
if (WEXITSTATUS(exit_code) != 0) {
|
||||
warnx("mount(8) failed: \"%s\"", drive->path);
|
||||
rmdir(name);
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
drive->mnt = strdup_checked(name);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
LOG_VERBOSE("Mounted \"%s\" with fs %s to \"%s\"\n", drive->path, drive->fs,
|
||||
drive->mnt);
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool drive_unmount(Drive *drive, bool force) {
|
||||
if (!drive->mnt) {
|
||||
warnx("\"%s\" is not mounted", drive->path);
|
||||
return false;
|
||||
}
|
||||
int flags = force ? MNT_FORCE : 0;
|
||||
if (unmount(drive->mnt, flags) < 0) {
|
||||
warn("unmount(2) failed for \"%s\"", drive->path);
|
||||
return false;
|
||||
}
|
||||
rmdir(drive->mnt);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
free(drive->mnt);
|
||||
drive->mnt = NULL;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
|
||||
return true;
|
||||
}
|
60
src/drive.h
Normal file
60
src/drive.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* drive.h - Simple abstractions for working with file systems and drives
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#ifndef INCLUDED_DRIVE_H
|
||||
#define INCLUDED_DRIVE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *path;
|
||||
char *fs;
|
||||
char *mnt;
|
||||
} Drive;
|
||||
|
||||
/*
|
||||
* Attempt to list all connected USB drives and their file systems. The list is
|
||||
* sorted alphabetically.
|
||||
* Return: all connected USB (actually any serial drive) and their file systems.
|
||||
*/
|
||||
Drive *drive_list_usb_drives(size_t *count);
|
||||
|
||||
/*
|
||||
* Free the array of drives DRIVES.
|
||||
*/
|
||||
void drive_free_drives(Drive *drives, size_t count);
|
||||
|
||||
/*
|
||||
* Use the fstyp(8) utility to try to find the FS for device file DEV.
|
||||
* Return: The FS name allocated with malloc(3), or NULL if it could not be
|
||||
* found.
|
||||
*/
|
||||
char *drive_guess_fs(const char *dev);
|
||||
|
||||
/*
|
||||
* Create an MSDOS (FAT32) FS on DRIVE. DRIVE MUST NOT BE MOUNTED!
|
||||
* Return: true on success, false otherwise
|
||||
*/
|
||||
bool drive_newfs_msdos(Drive *drive);
|
||||
|
||||
/*
|
||||
* Mount DRIVE onto a new temp. dir in /tmp. DRIVE MUST NOT BE MOUNTED!
|
||||
* Return: true on success, false otherwise
|
||||
*/
|
||||
bool drive_mount(Drive *drive);
|
||||
|
||||
/*
|
||||
* Unmount DRIVE. Force the operation if FORCE. Also rmdir(2) the mount point.
|
||||
* Return: true of success, false on failure.
|
||||
*/
|
||||
bool drive_unmount(Drive *drive, bool force);
|
||||
|
||||
#endif
|
19
src/main.c
19
src/main.c
@ -18,6 +18,7 @@
|
||||
#include "ui/datapoints.h"
|
||||
#include "ui/statrange.h"
|
||||
#include "ui/blankscreen.h"
|
||||
#include "ui/exportscreen.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
@ -105,7 +106,12 @@ int main(int argc, char *const *argv) {
|
||||
screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
|
||||
screen_manager_add(screen_manager, (Screen *) data_points_screen_new());
|
||||
screen_manager_add(screen_manager, (Screen *) stat_range_screen_new());
|
||||
screen_manager_add(screen_manager, blank_screen_new());
|
||||
if (GLOBAL_OPTS.export_file_name) {
|
||||
screen_manager_add(screen_manager, (Screen *) export_screen_new());
|
||||
}
|
||||
if (GLOBAL_OPTS.bl_pin >= 0) {
|
||||
screen_manager_add(screen_manager, blank_screen_new());
|
||||
}
|
||||
while (RUNNING) {
|
||||
lock_stat_globals();
|
||||
uint32_t temp = LAST_TEMP;
|
||||
@ -142,7 +148,7 @@ void parse_arguments(int argc, char *const *argv) {
|
||||
print_help(argv[0]);
|
||||
exit(0);
|
||||
case 'f':
|
||||
FREE_CHECKED(GLOBAL_OPTS.config_path);
|
||||
free(GLOBAL_OPTS.config_path);
|
||||
GLOBAL_OPTS.config_path = optarg;
|
||||
LOG_VERBOSE("Config file path set: \"%s\"\n", optarg);
|
||||
break;
|
||||
@ -174,13 +180,14 @@ static void exit_signal_callback(int sig) {
|
||||
}
|
||||
}
|
||||
|
||||
#define SIGNAL_SETUP_CHECKED(sig) \
|
||||
if (signal(sig, exit_signal_callback) == SIG_ERR) {\
|
||||
#define SIGNAL_SETUP_CHECKED(sig, func) \
|
||||
if (signal(sig, func) == SIG_ERR) {\
|
||||
err(1, "failed to setup signal %s", #sig);\
|
||||
}
|
||||
void setup_signals() {
|
||||
SIGNAL_SETUP_CHECKED(SIGTERM);
|
||||
SIGNAL_SETUP_CHECKED(SIGINT);
|
||||
SIGNAL_SETUP_CHECKED(SIGTERM, exit_signal_callback);
|
||||
SIGNAL_SETUP_CHECKED(SIGINT, exit_signal_callback);
|
||||
SIGNAL_SETUP_CHECKED(SIGUSR1, SIG_IGN); // used to kill export operations
|
||||
}
|
||||
|
||||
void open_database() {
|
||||
|
403
src/ui/exportscreen.c
Normal file
403
src/ui/exportscreen.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* exportscreen.c - Screen for exporting to USB drives
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "exportscreen.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
#include <pthread_np.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static void export_screen_reset(ExportScreen *screen,
|
||||
SensorState *state) {
|
||||
screen->format = EXPORT_FORMAT_SQLITE;
|
||||
screen->stage = EXPORT_SCREEN_FORMAT;
|
||||
screen->need_redraw = true;
|
||||
drive_free_drives(screen->drives, screen->ndrive);
|
||||
screen->read_drives = true;
|
||||
screen->confirm_state = false;
|
||||
screen->need_overwrite = false;
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrun: true if task is done, false otherwise
|
||||
*/
|
||||
static bool export_check_bg_task(ExportScreen *screen) {
|
||||
void *status;
|
||||
if (pthread_peekjoin_np(screen->backgroud_oper, &status) != EBUSY) {
|
||||
// done
|
||||
screen->bg_inited = false;
|
||||
if ((intptr_t) status == EXPORT_SCREEN_FAILED) {
|
||||
// error happened
|
||||
warnx("background export operation failed");
|
||||
}
|
||||
screen->stage = (intptr_t) status;
|
||||
screen->need_redraw = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *FORMAT_STRINGS[] = {
|
||||
"SQLITE",
|
||||
"CSV"
|
||||
};
|
||||
static const char *EXTENSION_STRINGS[] = {
|
||||
"sqlite3",
|
||||
"csv"
|
||||
};
|
||||
|
||||
static char *export_build_cur_path(ExportScreen *screen) {
|
||||
char *path = NULL;
|
||||
pthread_cleanup_push(free, path);
|
||||
asprintf_checked(&path, "%s/%s.%s", screen->drives[screen->cur_drive].mnt,
|
||||
GLOBAL_OPTS.export_file_name,
|
||||
EXTENSION_STRINGS[screen->format]);
|
||||
pthread_cleanup_pop(false);
|
||||
return path;
|
||||
}
|
||||
|
||||
static bool export_select_format(ExportScreen *screen,
|
||||
SensorState *state) {
|
||||
if (state->back_down) {
|
||||
export_screen_reset(screen, state);
|
||||
return true;
|
||||
}
|
||||
if (state->up_down || state->down_down) {
|
||||
screen->need_redraw = true;
|
||||
screen->format = abs((screen->format + state->up_down -
|
||||
state->down_down) % EXPORT_NFORMAT);
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
screen->need_redraw = false;
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||
LCD_DISPLAY_ON);
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Format:");
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_write_char(state->lcd, '>');
|
||||
lcd_write_string(state->lcd, FORMAT_STRINGS[screen->format]);
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
}
|
||||
if (state->sel_down) {
|
||||
++screen->stage;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void export_select_drive(ExportScreen *screen,
|
||||
SensorState *state) {
|
||||
if (state->back_down) {
|
||||
--screen->stage;
|
||||
screen->need_redraw = true;
|
||||
return;
|
||||
}
|
||||
if (state->down_down && screen->cur_drive > 0) {
|
||||
--screen->cur_drive;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (state->up_down && screen->cur_drive < screen->ndrive - 1) {
|
||||
++screen->cur_drive;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
screen->need_redraw = false;
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Drive:");
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_write_char(state->lcd, '>');
|
||||
lcd_write_string(state->lcd, screen->drives[screen->cur_drive].name);
|
||||
lcd_write_char(state->lcd, ' ');
|
||||
lcd_write_string(state->lcd, screen->drives[screen->cur_drive].fs);
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
}
|
||||
if (state->sel_down) {
|
||||
screen->need_redraw = true;
|
||||
if (!screen->drives[screen->cur_drive].fs) {
|
||||
screen->stage = EXPORT_SCREEN_CONFIRM_NEWFS;
|
||||
} else {
|
||||
screen->stage = EXPORT_SCREEN_MOUNTING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 3, 4)))
|
||||
static enum {
|
||||
EXPORT_CONFIRM_CONTINUE = 0,
|
||||
EXPORT_CONFIRM_NO,
|
||||
EXPORT_CONFIRM_YES,
|
||||
} export_confirm_action(ExportScreen *screen,
|
||||
SensorState *state,
|
||||
const char *fmt, ...) {
|
||||
int done_status = EXPORT_CONFIRM_CONTINUE;
|
||||
if (screen->bg_inited && export_check_bg_task(screen)) {
|
||||
// background task done
|
||||
done_status = EXPORT_CONFIRM_CONTINUE;
|
||||
goto confirm_done;
|
||||
}
|
||||
if (state->back_down || (state->sel_down && !screen->confirm_state)) {
|
||||
done_status = EXPORT_CONFIRM_NO;
|
||||
screen->need_redraw = true;
|
||||
goto confirm_done;
|
||||
}
|
||||
if (state->sel_down) {
|
||||
done_status = EXPORT_CONFIRM_YES;
|
||||
goto confirm_done;
|
||||
}
|
||||
if (state->up_down || state->down_down) {
|
||||
screen->confirm_state = abs((screen->confirm_state + state->up_down -
|
||||
state->down_down) % 2);
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
screen->need_redraw = false;
|
||||
char message[17];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(message, 17, fmt, args);
|
||||
va_end(args);
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, message);
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_write_string(state->lcd, ">No >Yes");
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||
LCD_DISPLAY_ON);
|
||||
lcd_move_to(state->lcd, 1, 4 * screen->confirm_state);
|
||||
}
|
||||
return EXPORT_CONFIRM_CONTINUE;
|
||||
confirm_done:
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
screen->confirm_state = false;
|
||||
return done_status;
|
||||
}
|
||||
|
||||
static bool export_show_message(ExportScreen *screen,
|
||||
SensorState *state,
|
||||
const char *message) {
|
||||
if (state->back_down || state->up_down || state->down_down ||
|
||||
state->sel_down) {
|
||||
export_screen_reset(screen, state);
|
||||
return true;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, message);
|
||||
screen->need_redraw = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static intptr_t export_newfs_action(ExportScreen *screen) {
|
||||
if (drive_newfs_msdos(&screen->drives[screen->cur_drive])) {
|
||||
return EXPORT_SCREEN_MOUNTING;
|
||||
} else {
|
||||
return EXPORT_SCREEN_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
static intptr_t export_mount_action(ExportScreen *screen) {
|
||||
Drive *drive = &screen->drives[screen->cur_drive];
|
||||
if (drive_mount(drive)) {
|
||||
struct stat statbuf;
|
||||
char *path = NULL;
|
||||
int exist;
|
||||
pthread_cleanup_push(free, path);
|
||||
path = export_build_cur_path(screen);
|
||||
exist = stat(path, &statbuf);
|
||||
screen->need_overwrite = exist == 0;
|
||||
pthread_cleanup_pop(true); // free(path)
|
||||
return screen->need_overwrite ? EXPORT_SCREEN_CONFIRM_OVERWRITE :
|
||||
EXPORT_SCREEN_WRITING;
|
||||
} else {
|
||||
return EXPORT_SCREEN_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
static intptr_t export_write_action(ExportScreen *screen) {
|
||||
char *path = NULL;
|
||||
intptr_t next_target = EXPORT_SCREEN_DONE;
|
||||
pthread_cleanup_push(free, path);
|
||||
path = export_build_cur_path(screen);
|
||||
if (screen->need_overwrite && !recursive_rm(path)) {
|
||||
next_target = EXPORT_SCREEN_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
LOG_VERBOSE("Exporting to \"%s\"\n", path);
|
||||
switch (screen->format) {
|
||||
case EXPORT_FORMAT_SQLITE:
|
||||
if (!export_database(screen->db_cache, path)) {
|
||||
warnx("export to sqlite3 db failed: \"%s\"", path);
|
||||
next_target = EXPORT_SCREEN_FAILED;
|
||||
}
|
||||
break;
|
||||
case EXPORT_FORMAT_CSV:
|
||||
if (!export_database_as_csv(screen->db_cache, path)) {
|
||||
warnx("export to CSV failed: \"%s\"", path);
|
||||
next_target = EXPORT_SCREEN_FAILED;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
warnx("unknown export format");
|
||||
next_target = EXPORT_SCREEN_FAILED;
|
||||
break;
|
||||
}
|
||||
LOG_VERBOSE("Export done\n");
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true); // free(path)
|
||||
drive_unmount(&screen->drives[screen->cur_drive], true);
|
||||
return next_target;
|
||||
}
|
||||
|
||||
static const char BACKGROUND_CHARS[] = {
|
||||
'|',
|
||||
'-'
|
||||
};
|
||||
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
|
||||
|
||||
static void export_do_background_action(ExportScreen *screen,
|
||||
SensorState *state,
|
||||
intptr_t(*action)(ExportScreen *screen)) {
|
||||
if (!screen->bg_inited) {
|
||||
if (pthread_create(&screen->backgroud_oper, NULL,
|
||||
(void*(*)(void *)) action, screen)) {
|
||||
warnx("could not start background export operation");
|
||||
screen->stage = EXPORT_SCREEN_FAILED;
|
||||
}
|
||||
screen->bg_inited = true;
|
||||
screen->working_char = 0;
|
||||
}
|
||||
if (state->down_down || state->up_down || state->back_down ||
|
||||
state->sel_down) {
|
||||
screen->last_cancel_stage = screen->stage;
|
||||
screen->stage = EXPORT_SCREEN_CONFIRM_CANCEL;
|
||||
screen->need_redraw = true;
|
||||
return;
|
||||
}
|
||||
time_t now = time(NULL);
|
||||
if (export_check_bg_task(screen)) {
|
||||
// bg task done
|
||||
return;
|
||||
}
|
||||
if (screen->need_redraw || now != screen->last_char_change) {
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
screen->last_char_change = now;
|
||||
screen->need_redraw = false;
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Working");
|
||||
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool export_screen_dispatch(ExportScreen *screen,
|
||||
SensorState *state) {
|
||||
screen->db_cache = state->db;
|
||||
if (state->force_draw) {
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (screen->read_drives) {
|
||||
screen->drives = drive_list_usb_drives(&screen->ndrive);
|
||||
if (screen->ndrive == 0) {
|
||||
screen->stage = EXPORT_SCREEN_NO_DRIVES;
|
||||
screen->need_redraw =true;
|
||||
} else {
|
||||
screen->cur_drive = 0;
|
||||
screen->read_drives = false;
|
||||
}
|
||||
}
|
||||
Drive *cur_drive = &screen->drives[screen->cur_drive];
|
||||
int confirm_status;
|
||||
switch (screen->stage) {
|
||||
case EXPORT_SCREEN_FORMAT:
|
||||
return export_select_format(screen, state);
|
||||
case EXPORT_SCREEN_DRIVE:
|
||||
export_select_drive(screen, state);
|
||||
break;
|
||||
case EXPORT_SCREEN_CONFIRM_NEWFS:
|
||||
confirm_status = export_confirm_action(screen, state, "Newfs? (%s)",
|
||||
cur_drive->name);
|
||||
if (confirm_status == EXPORT_CONFIRM_YES) {
|
||||
screen->stage = EXPORT_SCREEN_WRITE_NEWFS;
|
||||
screen->need_redraw = true;
|
||||
} else if (confirm_status == EXPORT_CONFIRM_NO) {
|
||||
screen->stage = EXPORT_SCREEN_DRIVE;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
break;
|
||||
case EXPORT_SCREEN_WRITE_NEWFS:
|
||||
export_do_background_action(screen, state, export_newfs_action);
|
||||
break;
|
||||
case EXPORT_SCREEN_MOUNTING:
|
||||
export_do_background_action(screen, state, export_mount_action);
|
||||
break;
|
||||
case EXPORT_SCREEN_CONFIRM_OVERWRITE:
|
||||
confirm_status = export_confirm_action(screen, state, "Overwrite?");
|
||||
if (confirm_status == EXPORT_CONFIRM_YES) {
|
||||
screen->stage = EXPORT_SCREEN_WRITING;
|
||||
screen->need_redraw = true;
|
||||
} else if (confirm_status == EXPORT_CONFIRM_NO) {
|
||||
screen->stage = EXPORT_SCREEN_DRIVE;
|
||||
screen->need_redraw = true;
|
||||
// on the main thread so force unmount
|
||||
// also, nothing should be writing, so kill it if it is
|
||||
drive_unmount(cur_drive, true);
|
||||
}
|
||||
break;
|
||||
case EXPORT_SCREEN_WRITING:
|
||||
export_do_background_action(screen, state, export_write_action);
|
||||
break;
|
||||
case EXPORT_SCREEN_CONFIRM_CANCEL:
|
||||
confirm_status = export_confirm_action(screen, state, "Really cancel?");
|
||||
if (confirm_status == EXPORT_CONFIRM_YES) {
|
||||
kill(0, SIGUSR1);
|
||||
pthread_cancel(screen->backgroud_oper);
|
||||
drive_unmount(cur_drive, true);
|
||||
screen->stage = EXPORT_SCREEN_CANCELED;
|
||||
screen->need_redraw = true;
|
||||
} else if (confirm_status == EXPORT_CONFIRM_NO) {
|
||||
screen->stage = screen->last_cancel_stage;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
break;
|
||||
case EXPORT_SCREEN_CANCELED:
|
||||
return export_show_message(screen, state, "Cancelled!");
|
||||
case EXPORT_SCREEN_FAILED:
|
||||
return export_show_message(screen, state, "Failed!");
|
||||
case EXPORT_SCREEN_DONE:
|
||||
return export_show_message(screen, state, "Export Done!");
|
||||
case EXPORT_SCREEN_NO_DRIVES:
|
||||
return export_show_message(screen, state, "No drives found!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ExportScreen *export_screen_new() {
|
||||
ExportScreen *s = malloc_checked(sizeof(ExportScreen));
|
||||
screen_init(&s->parent, "Export",
|
||||
(ScreenDispatchFunc) export_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
s->read_drives = true;
|
||||
s->stage = EXPORT_SCREEN_FORMAT;
|
||||
s->format = EXPORT_FORMAT_SQLITE;
|
||||
return s;
|
||||
}
|
61
src/ui/exportscreen.h
Normal file
61
src/ui/exportscreen.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* exportscreen.h - Screen for exporting to USB drives
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#ifndef INCLUDED_EXPORTSCREEN_H
|
||||
#define INCLUDED_EXPORTSCREEN_H
|
||||
|
||||
#include "screen.h"
|
||||
#include "../drive.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
typedef struct {
|
||||
Screen parent;
|
||||
enum {
|
||||
EXPORT_SCREEN_FORMAT = 0,
|
||||
EXPORT_SCREEN_DRIVE,
|
||||
EXPORT_SCREEN_CONFIRM_NEWFS,
|
||||
EXPORT_SCREEN_WRITE_NEWFS,
|
||||
EXPORT_SCREEN_MOUNTING,
|
||||
EXPORT_SCREEN_CONFIRM_OVERWRITE,
|
||||
EXPORT_SCREEN_WRITING,
|
||||
EXPORT_SCREEN_DONE,
|
||||
EXPORT_SCREEN_CONFIRM_CANCEL,
|
||||
EXPORT_SCREEN_CANCELED,
|
||||
EXPORT_SCREEN_FAILED,
|
||||
EXPORT_SCREEN_NO_DRIVES,
|
||||
} stage;
|
||||
enum {
|
||||
EXPORT_FORMAT_SQLITE = 0,
|
||||
EXPORT_FORMAT_CSV,
|
||||
EXPORT_NFORMAT,
|
||||
};
|
||||
int format;
|
||||
bool need_redraw;
|
||||
|
||||
bool read_drives;
|
||||
Drive *drives;
|
||||
size_t ndrive;
|
||||
size_t cur_drive;
|
||||
int confirm_state;
|
||||
bool bg_inited;
|
||||
pthread_t backgroud_oper;
|
||||
int working_char;
|
||||
time_t last_char_change;
|
||||
bool need_overwrite;
|
||||
sqlite3 *db_cache;
|
||||
int last_cancel_stage;
|
||||
} ExportScreen;
|
||||
|
||||
ExportScreen *export_screen_new(void);
|
||||
|
||||
#endif
|
@ -24,7 +24,7 @@ void screen_init(Screen *screen, const char *name,
|
||||
}
|
||||
|
||||
void screen_delete(Screen *screen) {
|
||||
FREE_CHECKED(screen->name);
|
||||
free(screen->name);
|
||||
if (screen->cleanup_func) {
|
||||
screen->cleanup_func(screen);
|
||||
}
|
||||
@ -53,7 +53,7 @@ void screen_manager_delete(ScreenManager *sm) {
|
||||
for (size_t i = 0; i < sm->screen_count; ++i) {
|
||||
screen_delete(sm->screens[i]);
|
||||
}
|
||||
FREE_CHECKED(sm->screens);
|
||||
free(sm->screens);
|
||||
button_delete(sm->up_btn);
|
||||
button_delete(sm->down_btn);
|
||||
button_delete(sm->back_btn);
|
||||
@ -115,6 +115,7 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) {
|
||||
if (cs->dispatch_func) {
|
||||
SensorState state = {
|
||||
.lcd = sm->lcd,
|
||||
.db = sm->db,
|
||||
.temp = temp,
|
||||
.humid = humid,
|
||||
.back_down = button_pressed(sm->back_btn),
|
||||
|
160
src/util.c
160
src/util.c
@ -12,6 +12,12 @@
|
||||
#include <err.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
|
||||
const char *PERIOD_LABELS[] = {
|
||||
"HOUR",
|
||||
@ -25,12 +31,13 @@ const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *);
|
||||
Options GLOBAL_OPTS;
|
||||
|
||||
void cleanup_options(Options *opts) {
|
||||
FREE_CHECKED(opts->config_path);
|
||||
FREE_CHECKED(opts->gpio_path);
|
||||
FREE_CHECKED(opts->temp_key);
|
||||
FREE_CHECKED(opts->humid_key);
|
||||
FREE_CHECKED(opts->fail_key);
|
||||
FREE_CHECKED(opts->database_location);
|
||||
free(opts->config_path);
|
||||
free(opts->gpio_path);
|
||||
free(opts->temp_key);
|
||||
free(opts->humid_key);
|
||||
free(opts->fail_key);
|
||||
free(opts->database_location);
|
||||
free(opts->export_file_name);
|
||||
}
|
||||
|
||||
void *malloc_checked(size_t n) {
|
||||
@ -56,6 +63,17 @@ void *strdup_checked(const char *str) {
|
||||
return new_str;
|
||||
}
|
||||
|
||||
int asprintf_checked(char **str, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int retval = vasprintf(str, format, args);
|
||||
if (!*str) {
|
||||
errx(1, "out of memory!");
|
||||
}
|
||||
va_end(args);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int days_in_month(int m, int y) {
|
||||
switch (m) {
|
||||
case 2:
|
||||
@ -160,6 +178,9 @@ static const char *AVG_FOR_RANGE_QUERY_STR =
|
||||
" max(temp), min(temp), max(humid), min(humid) FROM env_data\n"
|
||||
"WHERE time >= ?1 AND time <= ?2;";
|
||||
static sqlite3_stmt *AVG_FOR_RANGE_QUERY;
|
||||
static const char *EXPORT_CSV_QUERY_STR =
|
||||
"select printf('%d,%d,%d', time, temp, humid) from env_data;";
|
||||
static sqlite3_stmt *EXPORT_CSV_QUERY;
|
||||
void initialize_util_queries(sqlite3 *db) {
|
||||
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
|
||||
&DB_LIMITS_QUERY, NULL);
|
||||
@ -181,6 +202,11 @@ void initialize_util_queries(sqlite3 *db) {
|
||||
if (status != SQLITE_OK) {
|
||||
errx(1, "failed to compile range average query: %s", sqlite3_errstr(status));
|
||||
}
|
||||
status = sqlite3_prepare_v2(db, EXPORT_CSV_QUERY_STR, -1,
|
||||
&EXPORT_CSV_QUERY, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
errx(1, "failed to compile CSV export query: %s", sqlite3_errstr(status));
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_util_queries() {
|
||||
@ -188,6 +214,7 @@ void cleanup_util_queries() {
|
||||
sqlite3_finalize(AVG_FOR_PERIOD_QUERY);
|
||||
sqlite3_finalize(DATA_POINT_QUERY);
|
||||
sqlite3_finalize(AVG_FOR_RANGE_QUERY);
|
||||
sqlite3_finalize(EXPORT_CSV_QUERY);
|
||||
}
|
||||
|
||||
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
||||
@ -379,3 +406,124 @@ char *pad_humid_str(int humid, char *buf, size_t buf_size) {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void close_dir_if_set(void *dir) {
|
||||
if (dir) {
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
bool recursive_rm_at(int dfd, const char *path) {
|
||||
if (dfd != AT_FDCWD) {
|
||||
LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
|
||||
}
|
||||
struct stat statbuf;
|
||||
if (fstatat(dfd, path, &statbuf, 0) < 0) {
|
||||
warn("could not stat \"%s\" for removal", path);
|
||||
return false;
|
||||
}
|
||||
int flag = 0;
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
flag = AT_REMOVEDIR;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
|
||||
if (fd < 0) {
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
warn("open \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
DIR *dir = NULL;
|
||||
bool error = false;
|
||||
pthread_cleanup_push(close_dir_if_set, dir);
|
||||
dir = fdopendir(fd);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
if (!dir) {
|
||||
close(fd);
|
||||
warn("fdopendir \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
struct dirent *sd;
|
||||
while ((sd = readdir(dir))) {
|
||||
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
|
||||
!recursive_rm_at(dirfd(dir), sd->d_name)) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_cleanup_pop(true);
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (unlinkat(dfd, path, flag)< 0) {
|
||||
warn("remove \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool recursive_rm(const char *path) {
|
||||
return recursive_rm_at(AT_FDCWD, path);
|
||||
}
|
||||
|
||||
bool export_database(sqlite3 *db, const char *dest) {
|
||||
bool status = true;
|
||||
sqlite3 *new_db = NULL;
|
||||
sqlite3_backup *oper = NULL;
|
||||
pthread_cleanup_push((void(*)(void*))sqlite3_close, new_db);
|
||||
pthread_cleanup_push((void(*)(void*))sqlite3_backup_finish, oper);
|
||||
int res = sqlite3_open(dest, &new_db);
|
||||
if (res != SQLITE_OK) {
|
||||
warnx("could not open new db: \"%s\": %s", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
oper = sqlite3_backup_init(new_db, "main", db, "main");
|
||||
if (!oper) {
|
||||
warnx("could not start backup: \"%s\": %s", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
sqlite3_backup_step(oper, -1);
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true); // cleanup oper
|
||||
pthread_cleanup_pop(true); // cleanup new_db
|
||||
return status;
|
||||
}
|
||||
|
||||
static void cleanup_file_obj(void *obj) {
|
||||
if (obj) {
|
||||
fclose(obj);
|
||||
}
|
||||
}
|
||||
|
||||
bool export_database_as_csv(sqlite3 *db, const char *dest) {
|
||||
bool status = true;
|
||||
FILE *outfile = NULL;
|
||||
pthread_cleanup_push((void(*)(void *))sqlite3_reset, EXPORT_CSV_QUERY);
|
||||
pthread_cleanup_push(cleanup_file_obj, outfile);
|
||||
outfile = fopen(dest, "w");
|
||||
if (!outfile) {
|
||||
warn("fopen \"%s\"", dest);
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
int res;
|
||||
while ((res = sqlite3_step(EXPORT_CSV_QUERY)) == SQLITE_ROW) {
|
||||
const unsigned char *test = sqlite3_column_text(EXPORT_CSV_QUERY, 0);
|
||||
fputs((const char *) test, outfile);
|
||||
fputc('\n', outfile); // avoid flush
|
||||
}
|
||||
if (res != SQLITE_DONE) {
|
||||
warnx("CSV export to \"%s\" faield: %s\n", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup; // readability
|
||||
}
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true); // reset query
|
||||
pthread_cleanup_pop(true); // close file
|
||||
return status;
|
||||
}
|
||||
|
33
src/util.h
33
src/util.h
@ -49,6 +49,8 @@ typedef struct {
|
||||
gpio_pin_t sel_pin;
|
||||
|
||||
TemperatureUnit temp_unit;
|
||||
|
||||
char *export_file_name; // file to export to
|
||||
} Options;
|
||||
extern Options GLOBAL_OPTS;
|
||||
|
||||
@ -76,9 +78,11 @@ void *realloc_checked(void *old_ptr, size_t n);
|
||||
void *strdup_checked(const char *str);
|
||||
|
||||
/*
|
||||
* Like free(3), but do nothing if P is NULL.
|
||||
* Like asprintf(3), except that if allocation fails, an error will be written to
|
||||
* standard error and abort(3) called
|
||||
*/
|
||||
#define FREE_CHECKED(p) if (p) {free(p);}
|
||||
int asprintf_checked(char **str, const char *format, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
|
||||
/*
|
||||
* Call fprintf(3) to stderr only if verbose mode was enabled.
|
||||
@ -210,4 +214,29 @@ float convert_temperature(int dk);
|
||||
*/
|
||||
char *pad_humid_str(int humid, char *buf, size_t buf_size);
|
||||
|
||||
/*
|
||||
* Like recursive_rm (see below), except that PATH is resolved relative to DFD.
|
||||
*/
|
||||
bool recursive_rm_at(int dfd, const char *path);
|
||||
|
||||
/*
|
||||
* Call recursive_rm on all of FILE's children, the remove FILE
|
||||
* Return: false on error, true otherwise
|
||||
*/
|
||||
bool recursive_rm(const char *path);
|
||||
|
||||
/*
|
||||
* Use the sqlite3 backup API to export DB to dest. This function is safe to
|
||||
* call in a multi-threaded environment where pthread_cancel may be used.
|
||||
* Return: true on success, false on error
|
||||
*/
|
||||
bool export_database(sqlite3 *db, const char *dest);
|
||||
|
||||
/*
|
||||
* Export DB as a CSV file to DEST. This function is safe to call in a
|
||||
* multi-threaded environment where pthread_cancel may be used.
|
||||
* Return: true on success, false on error
|
||||
*/
|
||||
bool export_database_as_csv(sqlite3 *db, const char *dest);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user