Add export screen
This commit is contained in:
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;
|
||||
}
|
Reference in New Issue
Block a user