shithub: orca

Download patch

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