ref: c60f7a557cf24544a5d4048eb10bb8f5948ded9f
parent: 2d1427662054bfd81ce672ffe598fdb2ea83e2aa
author: cancel <cancel@cancel.fm>
date: Sun Jan 12 09:57:37 EST 2020
Add initial prefs/conf file saving Uses XDG_CONFIG_HOME when available. This probably has multiple bugs, and it also needs cleaning up.
--- a/sysmisc.c
+++ b/sysmisc.c
@@ -2,6 +2,8 @@
#include "gbuffer.h"
#include "oso.h"
#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
ORCA_FORCE_NO_INLINE
Cboard_error cboard_copy(Glyph const* gbuffer, Usz field_height,
@@ -209,4 +211,99 @@
FILE* file = fopen(osoc(path), "r");
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;
}
--- a/sysmisc.h
+++ b/sysmisc.h
@@ -28,5 +28,29 @@
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);
--- a/tui_main.c
+++ b/tui_main.c
@@ -819,6 +819,20 @@
}
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 @@
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 @@
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 @@
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 @@
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