From c60f7a557cf24544a5d4048eb10bb8f5948ded9f Mon Sep 17 00:00:00 2001 From: cancel Date: Sun, 12 Jan 2020 14:57:37 +0900 Subject: [PATCH] Add initial prefs/conf file saving Uses XDG_CONFIG_HOME when available. This probably has multiple bugs, and it also needs cleaning up. --- sysmisc.c | 97 ++++++++++++++++++++++++++++++++ sysmisc.h | 26 ++++++++- tui_main.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 278 insertions(+), 5 deletions(-) diff --git a/sysmisc.c b/sysmisc.c index 11a15a9..7f7420f 100644 --- a/sysmisc.c +++ b/sysmisc.c @@ -2,6 +2,8 @@ #include "gbuffer.h" #include "oso.h" #include +#include +#include ORCA_FORCE_NO_INLINE Cboard_error cboard_copy(Glyph const* gbuffer, Usz field_height, @@ -210,3 +212,98 @@ FILE* conf_file_open_for_reading(void) { osofree(path); return file; } + +Conf_save_start_error conf_save_start(Conf_save* p) { + memset(p, 0, sizeof(Conf_save)); + oso *dir = NULL, *canonpath = NULL, *temppath = NULL; + FILE *origfile = NULL, *tempfile = NULL; + Conf_save_start_error err; + if (try_get_conf_dir(&dir)) { + err = Conf_save_start_no_home; + goto cleanup; + } + if (!dir) { + err = Conf_save_start_alloc_failed; + goto cleanup; + } + osoputoso(&canonpath, dir); + osocat(&canonpath, conf_file_name); + if (!canonpath) { + err = Conf_save_start_alloc_failed; + goto cleanup; + } + osoputoso(&temppath, canonpath); + osocat(&temppath, ".tmp"); + if (!temppath) { + err = Conf_save_start_alloc_failed; + goto cleanup; + } + // Remove old temp file if it exists. If it exists and we can't remove it, + // error. + if (unlink(osoc(temppath)) == -1 && errno != ENOENT) { + err = Conf_save_start_old_temp_file_stuck; + goto cleanup; + } + tempfile = fopen(osoc(temppath), "w"); + if (!tempfile) { + // Try to create config dir, in case it doesn't exist. (XDG says we should + // do this, and use mode 0700.) + mkdir(osoc(dir), 0700); + tempfile = fopen(osoc(temppath), "w"); + } + if (!tempfile) { + err = Conf_save_start_temp_file_open_failed; + goto cleanup; + } + // This may be left as NULL. + origfile = fopen(osoc(canonpath), "r"); + // We did it, boys. + osofree(dir); + p->canonpath = canonpath; + p->temppath = temppath; + p->origfile = origfile; + p->tempfile = tempfile; + return Conf_save_start_ok; + +cleanup: + osofree(dir); + osofree(canonpath); + osofree(temppath); + if (origfile) + fclose(origfile); + if (tempfile) + fclose(tempfile); + return err; +} + +void conf_save_cancel(Conf_save* p) { + osofree(p->canonpath); + osofree(p->temppath); + if (p->origfile) + fclose(p->origfile); + if (p->tempfile) + fclose(p->tempfile); + memset(p, 0, sizeof(Conf_save)); +} + +Conf_save_commit_error conf_save_commit(Conf_save* p) { + Conf_save_commit_error err; + fclose(p->tempfile); + p->tempfile = NULL; + if (p->origfile) { + fclose(p->origfile); + p->origfile = NULL; + } + // This isn't really atomic. But if we want to close and move a file + // simultaneously, I think we have to use OS-specific facilities. So I guess + // this is the best we can do for now. I could be wrong, though. But I + // couldn't find any good information about it. + if (rename(osoc(p->temppath), osoc(p->canonpath)) == -1) { + err = Conf_save_commit_rename_failed; + goto cleanup; + } + err = Conf_save_commit_ok; +cleanup: + conf_save_cancel(p); + return err; +} diff --git a/sysmisc.h b/sysmisc.h index 50d28f9..483088c 100644 --- a/sysmisc.h +++ b/sysmisc.h @@ -28,5 +28,29 @@ Conf_read_result conf_read_line(FILE* file, char* buf, Usz bufsize, char** out_left, Usz* out_leftlen, char** out_right, Usz* out_rightlen); - FILE* conf_file_open_for_reading(void); + +typedef struct { + FILE *origfile, *tempfile; + struct oso *canonpath, *temppath; +} Conf_save; + +typedef enum { + Conf_save_start_ok = 0, + Conf_save_start_alloc_failed, + Conf_save_start_no_home, + Conf_save_start_mkdir_failed, + Conf_save_start_old_temp_file_stuck, + Conf_save_start_temp_file_open_failed, +} Conf_save_start_error; + +typedef enum { + Conf_save_commit_ok = 0, + Conf_save_commit_temp_fsync_failed, + Conf_save_commit_temp_close_failed, + Conf_save_commit_rename_failed, +} Conf_save_commit_error; + +Conf_save_start_error conf_save_start(Conf_save* p); +void conf_save_cancel(Conf_save* p); +Conf_save_commit_error conf_save_commit(Conf_save* p); diff --git a/tui_main.c b/tui_main.c index 6b79db3..c0d830f 100644 --- a/tui_main.c +++ b/tui_main.c @@ -819,6 +819,20 @@ bool portmidi_find_device_id_by_name(char const* name, Usz namelen, } return false; } +bool portmidi_find_name_of_device_id(PmDeviceID id, PmError* out_pmerror, + oso** out_name) { + *out_pmerror = portmidi_init_if_necessary(); + if (*out_pmerror) + return false; + int num = Pm_CountDevices(); + if (id < 0 || id >= num) + return false; + PmDeviceInfo const* info = Pm_GetDeviceInfo(id); + if (!info || !info->output) + return false; + osoput(out_name, info->name); + return true; +} #endif void midi_mode_deinit(Midi_mode* mm) { switch (mm->any.type) { @@ -2347,6 +2361,8 @@ typedef enum { Prefs_load_ok = 0, } Prefs_load_error; +static char const* confkey_portmidi_output_device = "portmidi_output_device"; + ORCA_FORCE_NO_INLINE Prefs_load_error prefs_load_from_conf_file(Prefs* p) { (void)p; @@ -2354,15 +2370,15 @@ Prefs_load_error prefs_load_from_conf_file(Prefs* p) { if (!conffile) { return Prefs_load_ok; } - char linebuff[512]; + char linebuff[1024]; + char *left, *right; + Usz leftsz, rightsz; for (;;) { - char *left, *right; - Usz leftsz, rightsz; Conf_read_result res = conf_read_line(conffile, linebuff, sizeof linebuff, &left, &leftsz, &right, &rightsz); switch (res) { case Conf_read_left_and_right: { - if (strcmp("portmidi_output_device", left) == 0) { + if (strcmp(confkey_portmidi_output_device, left) == 0) { osoput(&p->portmidi_output_device, right); } continue; @@ -2380,6 +2396,140 @@ Prefs_load_error prefs_load_from_conf_file(Prefs* p) { return Prefs_load_ok; } +typedef enum { + Prefs_save_ok = 0, + Prefs_save_start_failed, + Prefs_save_commit_failed, + Prefs_save_line_too_long, + Prefs_save_existing_read_error, +} Prefs_save_error; + +Prefs_save_error save_prefs_to_disk(Midi_mode const* midi_mode) { + Conf_save save; + Conf_save_start_error starterr = conf_save_start(&save); + if (starterr) + return Prefs_save_start_failed; + bool need_cancel_save = true; + enum Midi_output_pref { + Midi_output_pref_none = 0, +#ifdef FEAT_PORTMIDI + Midi_output_pref_portmidi, +#endif + } midi_output_pref = Midi_output_pref_none; + Prefs_save_error error; + oso* midi_output_device_name = NULL; + switch (midi_mode->any.type) { + case Midi_mode_type_null: + break; + case Midi_mode_type_osc_bidule: + // TODO + break; +#ifdef FEAT_PORTMIDI + case Midi_mode_type_portmidi: { + PmError pmerror; + if (!portmidi_find_name_of_device_id(midi_mode->portmidi.device_id, + &pmerror, &midi_output_device_name) || + osolen(midi_output_device_name) < 1) { + osowipe(&midi_output_device_name); + break; + } + midi_output_pref = Midi_output_pref_portmidi; + } break; +#endif + } + if (!save.origfile) + goto done_reading_existing; + for (;;) { + char linebuff[1024]; + char *left, *right; + Usz leftsz, rightsz; + Conf_read_result res = + conf_read_line(save.origfile, linebuff, sizeof linebuff, &left, &leftsz, + &right, &rightsz); + switch (res) { + case Conf_read_left_and_right: +#ifdef FEAT_PORTMIDI + if (strcmp(confkey_portmidi_output_device, left) == 0) { + if (midi_output_pref != Midi_output_pref_portmidi) + continue; + midi_output_pref = Midi_output_pref_none; + fputs(confkey_portmidi_output_device, save.tempfile); + fputs(" = ", save.tempfile); + fputs(osoc(midi_output_device_name), save.tempfile); + fputs("\n", save.tempfile); + osowipe(&midi_output_device_name); + continue; + } +#endif + fputs(left, save.tempfile); + fputs(" = ", save.tempfile); + fputs(right, save.tempfile); + fputs("\n", save.tempfile); + continue; + case Conf_read_irrelevant: + fputs(left, save.tempfile); + fputs("\n", save.tempfile); + continue; + case Conf_read_eof: + goto done_reading_existing; + case Conf_read_buffer_too_small: + error = Prefs_save_line_too_long; + goto cleanup; + case Conf_read_io_error: + error = Prefs_save_existing_read_error; + goto cleanup; + } + } +done_reading_existing: + switch (midi_output_pref) { + case Midi_output_pref_none: + break; +#ifdef FEAT_PORTMIDI + case Midi_output_pref_portmidi: + fputs(confkey_portmidi_output_device, save.tempfile); + fputs(" = ", save.tempfile); + fputs(osoc(midi_output_device_name), save.tempfile); + fputs("\n", save.tempfile); + osowipe(&midi_output_device_name); + break; +#endif + } + need_cancel_save = false; + Conf_save_commit_error comerr = conf_save_commit(&save); + if (comerr) { + error = Prefs_save_commit_failed; + goto cleanup; + } + error = Prefs_save_ok; +cleanup: + if (need_cancel_save) + conf_save_cancel(&save); + osofree(midi_output_device_name); + return error; +} + +void save_prefs_with_error_message(Midi_mode const* midi_mode) { + Prefs_save_error err = save_prefs_to_disk(midi_mode); + char const* msg = "Unknown"; + switch (err) { + case Prefs_save_ok: + return; + case Prefs_save_start_failed: + msg = "Start failed"; + break; + case Prefs_save_commit_failed: + msg = "Failed to commit save file"; + break; + case Prefs_save_line_too_long: + msg = "Line in file is too long"; + break; + case Prefs_save_existing_read_error: + msg = "Error when reading existing configuration file"; + break; + } + qmsg_printf_push("Save Error", "Error when saving:\n%s", msg); +} + void print_loading_message(char const* s) { Usz len = strlen(s); if (len > INT_MAX) @@ -3087,6 +3237,8 @@ int main(int argc, char** argv) { qmsg_printf_push("PortMidi Error", "Error setting PortMidi output device:\n%s", Pm_GetErrorText(pme)); + } else { + save_prefs_with_error_message(&midi_mode); } } break; #endif