From e77a9ee2c4394d395f89ffb007a9a83a6b049dbb Mon Sep 17 00:00:00 2001 From: cancel Date: Wed, 15 Jan 2020 17:45:11 +0900 Subject: [PATCH] Factor out conf file writing --- sysmisc.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++ sysmisc.h | 18 ++++++ tui_main.c | 154 +++++++++------------------------------------------ 3 files changed, 203 insertions(+), 129 deletions(-) diff --git a/sysmisc.c b/sysmisc.c index 9d8953a..9e29f53 100644 --- a/sysmisc.c +++ b/sysmisc.c @@ -385,3 +385,163 @@ bool ezconf_read_step(Ezconf_read *ezcr, char const *const *names, return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer, sizeof ezcr->buffer, &ezcr->index, &ezcr->value); } + +enum { + Confwflag_add_newline = 1 << 0, +}; + +void ezconf_write_start(Ezconf_write *ezcw, Confopt_w *opts, size_t optscount) { + for (size_t i = 0; i < optscount; i++) + opts[i].written = 0; + *ezcw = (Ezconf_write){0}; + ezcw->opts = opts; + ezcw->optscount = optscount; + Prefs_save_error error = Prefs_save_unknown_error; + switch (conf_save_start(&ezcw->save)) { + case Conf_save_start_ok: + error = Prefs_save_ok; + break; + case Conf_save_start_alloc_failed: + error = Prefs_save_oom; + break; + case Conf_save_start_no_home: + error = Prefs_save_no_home; + break; + case Conf_save_start_mkdir_failed: + error = Prefs_save_mkdir_failed; + break; + case Conf_save_start_conf_dir_not_dir: + error = Prefs_save_conf_dir_not_dir; + break; + case Conf_save_start_old_temp_file_stuck: + error = Prefs_save_old_temp_file_stuck; + break; + case Conf_save_start_temp_file_perm_denied: + error = Prefs_save_temp_file_perm_denied; + break; + case Conf_save_start_temp_file_open_failed: + error = Prefs_save_temp_open_failed; + break; + } + ezcw->error = error; +} + +bool ezconf_write_step(Ezconf_write *ezcw) { + U32 stateflags = ezcw->stateflags; + FILE *origfile = ezcw->save.origfile, *tempfile = ezcw->save.tempfile; + Confopt_w *opts = ezcw->opts, *chosen = NULL; + size_t optscount = ezcw->optscount; + if (ezcw->error || !tempfile) // Already errored or finished ok + return false; + // If we set a flag to write a closing newline the last time we were called, + // write it now. + if (stateflags & Confwflag_add_newline) { + fputs("\n", tempfile); + stateflags &= ~(U32)Confwflag_add_newline; + } + if (!optscount) + goto commit; + if (!origfile) + goto write_leftovers; + for (;;) { // Scan through file looking for known keys in key=value lines + char linebuff[1024]; + char *left, *right; + Usz leftsz, rightsz; + Conf_read_result res = conf_read_line(origfile, linebuff, sizeof linebuff, + &left, &leftsz, &right, &rightsz); + switch (res) { + case Conf_read_left_and_right: { + for (size_t i = 0; i < optscount; i++) { + char const *name = opts[i].name; + if (!name) + continue; + if (strcmp(name, left) != 0) + continue; + // If we already wrote this one, comment out the line instead, and move + // on to the next line. + if (opts[i].written) { + fputs("# ", tempfile); + goto write_landr; + } + chosen = opts + i; + goto return_for_writing; + } + write_landr: + fputs(left, tempfile); + fputs(" = ", tempfile); + fputs(right, tempfile); + fputs("\n", tempfile); + continue; + } + case Conf_read_irrelevant: + fputs(left, tempfile); + fputs("\n", tempfile); + continue; + case Conf_read_eof: + goto end_original; + case Conf_read_buffer_too_small: + ezcw->error = Prefs_save_line_too_long; + goto cancel; + case Conf_read_io_error: + ezcw->error = Prefs_save_existing_read_error; + goto cancel; + } + } +end_original: // Don't need original file anymore + fclose(origfile); + ezcw->save.origfile = origfile = NULL; +write_leftovers: // Write out any guys that weren't in original file. + for (;;) { // Find the first guy that wasn't already written. + if (!optscount) + goto commit; + chosen = opts; + // Drop the guy from the front of the list. This is to reduce super-linear + // complexity growth as the number of conf key-value pairs are increased. + // (Otherwise, we iterate the full set of guys on each call during the + // "write the leftovers" phase.) + opts++; + optscount--; + if (!chosen->written) + break; + } + // Once control has reached here, we're going to return true to the caller. + // Which means we expect to be called at least one more time. So update the + // pointers stored in the persistent state, so that we don't have to scan + // through as much of this list next time. (This might even end up finishing + // it off, making it empty.) + ezcw->opts = opts; + ezcw->optscount = optscount; +return_for_writing: + chosen->written = true; + fputs(chosen->name, tempfile); + fputs(" = ", tempfile); + ezcw->optid = chosen->id; + stateflags |= (U32)Confwflag_add_newline; + ezcw->stateflags = stateflags; + return true; +cancel: + conf_save_cancel(&ezcw->save); + // ^- Sets tempfile to null, which we use as a guard at the top of this + // function. + ezcw->stateflags = 0; + return false; +commit:; + Prefs_save_error error = Prefs_save_unknown_error; + switch (conf_save_commit(&ezcw->save)) { + case Conf_save_commit_ok: + error = Prefs_save_ok; + break; + case Conf_save_commit_temp_fsync_failed: + error = Prefs_save_temp_fsync_failed; + break; + case Conf_save_commit_temp_close_failed: + error = Prefs_save_temp_close_failed; + break; + case Conf_save_commit_rename_failed: + error = Prefs_save_rename_failed; + break; + } + ezcw->error = error; + ezcw->stateflags = 0; + return false; +} diff --git a/sysmisc.h b/sysmisc.h index 641ba7c..4e74fb6 100644 --- a/sysmisc.h +++ b/sysmisc.h @@ -107,3 +107,21 @@ typedef struct { void ezconf_read_start(Ezconf_read *ezcr); bool ezconf_read_step(Ezconf_read *ezcr, char const *const *names, Usz nameslen); + +typedef struct { + char const *name; + intptr_t id; + U8 written : 1; +} Confopt_w; + +typedef struct { + Conf_save save; + Confopt_w *opts; + size_t optscount; + intptr_t optid; + Prefs_save_error error; + U32 stateflags; +} Ezconf_write; + +void ezconf_write_start(Ezconf_write *ezcw, Confopt_w *opts, size_t optscount); +bool ezconf_write_step(Ezconf_write *ezcw); diff --git a/tui_main.c b/tui_main.c index fd9f07f..e609cab 100644 --- a/tui_main.c +++ b/tui_main.c @@ -2395,45 +2395,11 @@ Prefs_load_error prefs_load_from_conf_file(Prefs *p) { return Prefs_load_ok; } -static void put_conf_pair(FILE *file, char const *left, char const *right) { - fputs(left, file); - fputs(" = ", file); - fputs(right, file); - fputs("\n", file); -} - -Prefs_save_error save_prefs_to_disk(Midi_mode const *midi_mode, - int softmargin_y, int softmargin_x, - bool softmargins_touched_by_user) { - Conf_save save; - Conf_save_start_error starterr = conf_save_start(&save); - switch (starterr) { - case Conf_save_start_ok: - break; - case Conf_save_start_alloc_failed: - return Prefs_save_oom; - case Conf_save_start_no_home: - return Prefs_save_no_home; - case Conf_save_start_mkdir_failed: - return Prefs_save_mkdir_failed; - case Conf_save_start_conf_dir_not_dir: - return Prefs_save_conf_dir_not_dir; - case Conf_save_start_old_temp_file_stuck: - return Prefs_save_old_temp_file_stuck; - case Conf_save_start_temp_file_perm_denied: - return Prefs_save_temp_file_perm_denied; - case Conf_save_start_temp_file_open_failed: - return Prefs_save_temp_open_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; - bool needs_write_margins = softmargins_touched_by_user; - Prefs_save_error error; +void save_prefs_with_error_message(Midi_mode const *midi_mode, int softmargin_y, + int softmargin_x, + bool softmargins_touched_by_user) { + Confopt_w wopts[Confoptslen]; + Usz i = 0; oso *midi_output_device_name = NULL; switch (midi_mode->any.type) { case Midi_mode_type_null: @@ -2450,104 +2416,34 @@ Prefs_save_error save_prefs_to_disk(Midi_mode const *midi_mode, osowipe(&midi_output_device_name); break; } - midi_output_pref = Midi_output_pref_portmidi; + wopts[i].name = confopts[Confopt_portmidi_output_device]; + wopts[i].id = Confopt_portmidi_output_device; + i++; } 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(confopts[Confopt_portmidi_output_device], left) == 0) { - if (midi_output_pref != Midi_output_pref_portmidi) - continue; - midi_output_pref = Midi_output_pref_none; - put_conf_pair(save.tempfile, confopts[Confopt_portmidi_output_device], - osoc(midi_output_device_name)); - osowipe(&midi_output_device_name); - continue; - } -#endif - if (strcmp(confopts[Confopt_margins], left) == 0) { - if (!needs_write_margins) - continue; - needs_write_margins = false; - fprintf(save.tempfile, "%s = %dx%d\n", confopts[Confopt_margins], - softmargin_x, softmargin_y); - continue; - } - put_conf_pair(save.tempfile, left, right); - 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; - } + if (softmargins_touched_by_user) { + wopts[i].name = confopts[Confopt_margins]; + wopts[i].id = Confopt_margins; + i++; } -done_reading_existing: - switch (midi_output_pref) { - case Midi_output_pref_none: - break; + Ezconf_write ez; + ezconf_write_start(&ez, wopts, i); + while (ezconf_write_step(&ez)) { + switch (ez.optid) { #ifdef FEAT_PORTMIDI - case Midi_output_pref_portmidi: - put_conf_pair(save.tempfile, confopts[Confopt_portmidi_output_device], - osoc(midi_output_device_name)); - osowipe(&midi_output_device_name); - break; + case Confopt_portmidi_output_device: + fputs(osoc(midi_output_device_name), ez.save.tempfile); + break; #endif + case Confopt_margins: + fprintf(ez.save.tempfile, "%dx%d", softmargin_x, softmargin_y); + break; + } } - if (needs_write_margins) { - needs_write_margins = false; - fprintf(save.tempfile, "%s = %dx%d\n", confopts[Confopt_margins], - softmargin_x, softmargin_y); // TODO redundant - } - need_cancel_save = false; - Conf_save_commit_error comerr = conf_save_commit(&save); - error = Prefs_save_unknown_error; - switch (comerr) { - case Conf_save_commit_ok: - error = Prefs_save_ok; - break; - case Conf_save_commit_temp_fsync_failed: - error = Prefs_save_temp_fsync_failed; - break; - case Conf_save_commit_temp_close_failed: - error = Prefs_save_temp_close_failed; - break; - case Conf_save_commit_rename_failed: - error = Prefs_save_rename_failed; - break; - } -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, int softmargin_y, - int softmargin_x, - bool softmargins_touched_by_user) { - Prefs_save_error err = save_prefs_to_disk( - midi_mode, softmargin_y, softmargin_x, softmargins_touched_by_user); - if (err) { - char const *msg = prefs_save_error_string(err); + if (ez.error) { + char const *msg = prefs_save_error_string(ez.error); qmsg_printf_push("Config Error", "Error when writing configuration file:\n%s", msg); }