Add export screen

This commit is contained in:
2024-04-04 00:53:29 -07:00
parent 961179db27
commit 6ae67c4732
11 changed files with 977 additions and 24 deletions

403
src/ui/exportscreen.c Normal file
View 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;
}