jcs
/amend
/amendments
/146
settings: Move storage from resources to a separate preferences file
Keeping settings bundled with the app was cleaner but it meant they
would get reset every time the app was updated.
On System 7+ these will go in System Folder:Preferences, on System 6
it will go in System Folder.
jcs made amendment 146 over 2 years ago
--- settings.c Thu Jan 11 15:19:35 2024
+++ settings.c Thu Mar 21 08:48:51 2024
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ * Copyright (c) 2021-2024 joshua stein <jcs@jcs.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -14,172 +14,359 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <OSUtils.h>
+#include <Files.h>
+#include <Folders.h>
+#include <GestaltEqu.h>
+#include <Traps.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
+#include <time.h>
#include "amend.h"
#include "settings.h"
#include "tetab.h"
#include "util.h"
-struct settings settings = { 0 };
+/* used in pre-2.8 */
+#define STR_AUTHOR_ID 128
+#define STR_TABWIDTH_ID 129
+#define STR_MAX_AMENDMENTS_ID 130
+enum {
+ SETTING_STRING,
+ SETTING_SHORT,
+ SETTING_USHORT,
+ SETTING_LONG,
+ SETTING_PASSWORD,
+ SETTING_BOOL
+};
+
+struct setting {
+ char name[32];
+ short type;
+ long min;
+ long max;
+ char sdefault[32];
+ short ditl_id;
+ unsigned long offset;
+ unsigned long size;
+ char password_storage[256];
+} settings_defs[] = {
+ { "Author", SETTING_STRING, 1, 0, "unknown",
+ SETTINGS_AUTHOR_ID, offsetof(struct settings, author),
+ member_size(struct settings, author) },
+ { "Tab Width", SETTING_USHORT, 1, 32, "4",
+ SETTINGS_TABWIDTH_ID, offsetof(struct settings, tabwidth),
+ member_size(struct settings, tabwidth) },
+ { "Max Amendments to Load", SETTING_USHORT, 1, 65535, "100",
+ SETTINGS_MAX_AMENDMENTS_ID, offsetof(struct settings, max_amendments),
+ member_size(struct settings, max_amendments) },
+};
+
+void settings_find_prefs_folder(short *vrefnum, long *dirid);
+void settings_load(void);
+
void
settings_load(void)
{
- char *author;
+ HParamBlockRec pb = { 0 };
+ struct settings tsettings = { 0 };
+ char fn[] = SETTINGS_FILENAME;
+ char *res;
+ short error, vrefnum, prefrefnum;
+ long len, dirid;
+ Handle h;
+ bool use_defaults = true;
- author = xGetStringAsChar(STR_AUTHOR_ID);
- strlcpy(settings.author, author, sizeof(settings.author));
- xfree(&author);
- if (settings.author[0] == '\0')
- snprintf(settings.author, sizeof(settings.author), "unknown");
+ GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid);
+
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.ioParam.ioPermssn = fsRdPerm;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHOpen(&pb, false);
+ if (error) {
+ if (error != fnfErr)
+ panic("Failed reading preferences file: %d", error);
+
+ settings_edit(use_defaults);
+ return;
+ }
- settings.tabwidth = (short)xGetStringAsLong(STR_TABWIDTH_ID);
- if (settings.tabwidth < 1 || settings.tabwidth > 20)
- settings.tabwidth = 4;
+ len = sizeof(short);
+ FSRead(pb.ioParam.ioRefNum, &len, &(tsettings.version));
+ SetFPos(pb.ioParam.ioRefNum, fsFromStart, 0);
- settings.max_amendments = (short)xGetStringAsLong(STR_MAX_AMENDMENTS_ID);
- if (settings.max_amendments < 0)
- settings.max_amendments = 0;
+ switch (tsettings.version) {
+ case 1:
+ len = sizeof(struct settings);
+ FSRead(pb.ioParam.ioRefNum, &len, &tsettings);
+ if (len != sizeof(struct settings))
+ warn("Short read of preferences file, may be corrupt");
+ break;
+ default:
+ FSClose(pb.ioParam.ioRefNum);
+
+ if (ask("Unknown preferences version (%d), reset?")) {
+ settings_edit(true);
+ return;
+ } else
+ ExitToShell();
+ }
+ FSClose(pb.ioParam.ioRefNum);
+
+ settings = tsettings;
TETabWidth = settings.tabwidth;
}
void
-settings_save(void)
+settings_save(struct settings *tsettings)
{
- Handle res;
- size_t l;
- char tmp[8];
+ HParamBlockRec pb = { 0 };
+ char fn[] = SETTINGS_FILENAME;
+ short error, vrefnum, prefrefnum;
+ long dirid, len;
+ Handle h;
- res = xGetResource('STR ', STR_AUTHOR_ID);
- HLock(res);
- l = strlen(settings.author);
- xSetHandleSize(res, l + 1);
- memcpy(*res, settings.author, l + 1);
- CtoPstr(*res);
- ChangedResource(res);
- ReleaseResource(res);
+ GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid);
- res = xGetResource('STR ', STR_TABWIDTH_ID);
- HLock(res);
- snprintf(tmp, sizeof(tmp), "%d", settings.tabwidth);
- l = strlen(tmp);
- xSetHandleSize(res, l + 1);
- memcpy(*res, tmp, l + 1);
- CtoPstr(*res);
- ChangedResource(res);
- ReleaseResource(res);
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHCreate(&pb, false);
+ if (error && error != dupFNErr)
+ goto create_failed;
+ if (!error) {
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.fileParam.ioDirID = dirid;
+ if ((error = PBHGetFInfo(&pb, false)))
+ goto create_failed;
+
+ pb.fileParam.ioDirID = dirid; /* required after PBHGetFInfo */
+ pb.fileParam.ioFlFndrInfo.fdType = 'pref';
+ pb.fileParam.ioFlFndrInfo.fdCreator = AMEND_CREATOR;
+ if ((error = PBHSetFInfo(&pb, false)))
+ goto create_failed;
+
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioVRefNum = vrefnum;
+ PBFlushVol(&pb, false);
+ }
- res = xGetResource('STR ', STR_MAX_AMENDMENTS_ID);
- HLock(res);
- snprintf(tmp, sizeof(tmp), "%d", settings.max_amendments);
- l = strlen(tmp);
- xSetHandleSize(res, l + 1);
- memcpy(*res, tmp, l + 1);
- CtoPstr(*res);
- ChangedResource(res);
- ReleaseResource(res);
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.ioParam.ioPermssn = fsWrPerm;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHOpen(&pb, false);
+ if (error)
+ goto create_failed;
+
+ len = sizeof(struct settings);
+ tsettings->version = SETTINGS_VERSION;
+ FSWrite(pb.ioParam.ioRefNum, &len, tsettings);
+ FSClose(pb.ioParam.ioRefNum);
- UpdateResFile(CurResFile());
- if (ResError())
- warn("UpdateResFile failed! %d", ResError());
+ TETabWidth = tsettings->tabwidth;
- TETabWidth = settings.tabwidth;
+ return;
+
+create_failed:
+ panic("Failed creating preferences file: %d", error);
}
void
-settings_edit(void)
+settings_edit(bool use_defaults)
{
- Str255 txt;
- Handle ihandle;
- size_t len;
- short itype, hit;
- bool done;
+ struct settings tsettings = settings;
+ struct setting *s;
+ DialogTHndl dlgh;
DialogPtr dlg;
- DialogTHndl dhand;
- DialogTPtr dp;
+ Handle ihandle;
Rect irect;
-
- dhand = (DialogTHndl)GetResource('DLOG', SETTINGS_DLOG_ID);
- if (dhand == NULL)
- panic("No settings dialog");
- LoadResource((Handle)dhand);
+ size_t size, n, m;
+ long lval;
+ bool bval, save;
+ short hit, itype, ret;
+ char *sdata, *strval;
+ Str255 stmp;
- HLock((Handle)dhand);
- dp = (DialogTPtr)(*dhand);
- center_in_screen(dp->boundsRect.right - dp->boundsRect.left,
- dp->boundsRect.bottom - dp->boundsRect.top, false, &dp->boundsRect);
- HUnlock((Handle)dhand);
+ /* center dialog in screen */
+ dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_DLOG_ID);
+ HLock(dlgh);
+ center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left,
+ (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top,
+ true, &(**dlgh).boundsRect);
+ HUnlock(dlgh);
+
+ if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL)
+ panic("Can't find settings DLOG %d", SETTINGS_DLOG_ID);
+
+ for (n = 0; n < nitems(settings_defs); n++) {
+ s = &settings_defs[n];
+ sdata = (char *)&tsettings + s->offset;
- dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1);
- if (dlg == NULL)
- err(1, "Can't find settings DLOG %d", SETTINGS_DLOG_ID);
+ GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
- GetDItem(dlg, SETTINGS_AUTHOR_ID, &itype, &ihandle, &irect);
- strlcpy((char *)txt, settings.author, sizeof(txt));
- CtoPstr(txt);
- SetIText(ihandle, txt);
+ switch (s->type) {
+ case SETTING_PASSWORD:
+ PasswordDialogFieldFilterSetup(s->ditl_id,
+ (char *)s->password_storage, sizeof(s->password_storage));
- GetDItem(dlg, SETTINGS_TABWIDTH_ID, &itype, &ihandle, &irect);
- snprintf((char *)txt, sizeof(txt), "%d", settings.tabwidth);
- CtoPstr(txt);
- SetIText(ihandle, txt);
+ strval = (use_defaults ? (char *)s->sdefault : sdata);
+ size = strlen(strval);
+ if (size >= sizeof(s->password_storage) - 1)
+ size = 0;
+
+ /* show a masked value */
+ stmp[0] = size;
+ for (m = 1; m <= size; m++)
+ stmp[m] = '•';
+ SetIText(ihandle, stmp);
+
+ /* but store the actual password */
+ memcpy(s->password_storage, strval, size);
+ s->password_storage[size] = '\0';
+ break;
+ case SETTING_STRING:
+ strval = (use_defaults ? s->sdefault : sdata);
+ memcpy(stmp, strval, sizeof(stmp));
+ stmp[sizeof(stmp) - 1] = '\0';
+ CtoPstr(stmp);
+ SetIText(ihandle, stmp);
+ break;
+ case SETTING_SHORT:
+ case SETTING_USHORT:
+ case SETTING_LONG:
+ if (use_defaults) {
+ memcpy(stmp, s->sdefault, sizeof(stmp));
+ stmp[sizeof(stmp) - 1] = '\0';
+ } else if (s->type == SETTING_SHORT) {
+ snprintf((char *)stmp, sizeof(stmp), "%d",
+ BYTES_TO_SHORT(sdata[0], sdata[1]));
+ } else if (s->type == SETTING_USHORT) {
+ snprintf((char *)stmp, sizeof(stmp), "%u",
+ BYTES_TO_SHORT(sdata[0], sdata[1]));
+ } else if (s->type == SETTING_LONG) {
+ snprintf((char *)stmp, sizeof(stmp), "%ld",
+ BYTES_TO_LONG(sdata[0], sdata[1], sdata[2], sdata[3]));
+ }
+ CtoPstr(stmp);
+ SetIText(ihandle, stmp);
+ break;
+ case SETTING_BOOL:
+ SetCtlValue(ihandle, (use_defaults ? s->sdefault[0] :
+ sdata[0]) == 1);
+ break;
+ default:
+ panic("unknown setting type %d", s->type);
+ }
+ }
- GetDItem(dlg, SETTINGS_MAX_AMENDMENTS_ID, &itype, &ihandle, &irect);
- snprintf((char *)txt, sizeof(txt), "%d", settings.max_amendments);
- CtoPstr(txt);
- SetIText(ihandle, txt);
-
ShowWindow(dlg);
+ GetDItem(dlg, ok, &itype, &ihandle, &irect);
-dialog_wait:
- done = false;
- while (!done) {
- ModalDialog(nil, &hit);
- switch (hit) {
- case SETTINGS_SAVE_ID:
- done = true;
+get_input:
+ outline_button((ControlHandle)ihandle);
+ ModalDialog(PasswordDialogFieldFilter, &hit);
+ switch (hit) {
+ case -1:
+ case cancel:
+ goto settings_done;
+ case OK:
+ goto verify;
+ default:
+ goto get_input;
+ }
+
+ save = false;
+verify:
+ for (n = 0; n < nitems(settings_defs); n++) {
+ s = &settings_defs[n];
+ sdata = (char *)&tsettings + s->offset;
+
+ GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
+
+ if (s->type == SETTING_PASSWORD) {
+ memcpy((char *)&stmp, s->password_storage, sizeof(stmp));
+ } else if (s->type == SETTING_BOOL) {
+ snprintf((char *)&stmp, sizeof(stmp), "%d",
+ GetCtlValue(ihandle));
+ } else {
+ GetIText(ihandle, stmp);
+ PtoCstr(stmp);
+ }
+
+ switch (s->type) {
+ case SETTING_STRING:
+ case SETTING_PASSWORD:
+ if (s->min && strlen((char *)stmp) < s->min) {
+ warn("%s is too short (minimum %ld)", s->name, s->min);
+ goto get_input;
+ }
+ if (s->max && strlen((char *)stmp) > s->max) {
+ warn("%s is too long (maximum %ld)", s->name, s->max);
+ goto get_input;
+ }
+
+ if (save) {
+ memset(sdata, 0, s->size);
+ strlcpy(sdata, (char *)stmp, s->size);
+ }
break;
- case SETTINGS_CANCEL_ID:
- goto dialog_close;
+ case SETTING_SHORT:
+ case SETTING_USHORT:
+ case SETTING_LONG:
+ lval = atol((char *)stmp);
+ if (lval < s->min) {
+ if (s->type == SETTING_USHORT)
+ warn("%s must be at least %ul", s->name, s->min);
+ else
+ warn("%s must be at least %ld", s->name, s->min);
+ goto get_input;
+ }
+ if (lval > s->max) {
+ if (s->type == SETTING_USHORT)
+ warn("%s must be less than %ul", s->name, s->max);
+ else
+ warn("%s must be less than %ld", s->name, s->max);
+ goto get_input;
+ }
+ if (save) {
+ if (s->type == SETTING_LONG) {
+ sdata[0] = (lval >> 24) & 0xff;
+ sdata[1] = (lval >> 16) & 0xff;
+ sdata[2] = (lval >> 8) & 0xff;
+ sdata[3] = lval & 0xff;
+ } else {
+ sdata[0] = (lval >> 8) & 0xff;
+ sdata[1] = lval & 0xff;
+ }
+ }
+ break;
+ case SETTING_BOOL:
+ if (save)
+ sdata[0] = (stmp[0] == '1');
+ break;
}
}
- GetDItem(dlg, SETTINGS_AUTHOR_ID, &itype, &ihandle, &irect);
- GetIText(ihandle, txt);
- PtoCstr(txt);
- len = strlen((char *)txt);
- if (len >= sizeof(settings.author)) {
- warn("Author setting is too long (%ld max)",
- sizeof(settings.author) - 1);
- goto dialog_wait;
- } else if (len == 0) {
- warn("Author cannot be blank");
- goto dialog_wait;
+ if (!save) {
+ save = true;
+ goto verify;
}
- strlcpy(settings.author, (char *)txt, sizeof(settings.author));
-
- GetDItem(dlg, SETTINGS_TABWIDTH_ID, &itype, &ihandle, &irect);
- GetIText(ihandle, txt);
- PtoCstr(txt);
- settings.tabwidth = atoi((char *)&txt);
- if (settings.tabwidth < 1)
- settings.tabwidth = 1;
- HUnlock(ihandle);
-
- GetDItem(dlg, SETTINGS_MAX_AMENDMENTS_ID, &itype, &ihandle, &irect);
- GetIText(ihandle, txt);
- PtoCstr(txt);
- settings.max_amendments = atoi((char *)&txt);
- if (settings.max_amendments < 0)
- settings.max_amendments = 0;
- HUnlock(ihandle);
+ /* all validated ok and fields written */
+ settings_save(&tsettings);
+ settings = tsettings;
- settings_save();
-
-dialog_close:
+settings_done:
+ PasswordDialogFieldFinish();
DisposeDialog(dlg);
-}
\ No newline at end of file
+ ReleaseResource(dlgh);
+}
--- settings.h Thu May 4 17:18:28 2023
+++ settings.h Wed Mar 20 15:26:31 2024
@@ -17,7 +17,13 @@
#ifndef __SETTINGS_H__
#define __SETTINGS_H__
+#include "stdint.h"
+
+#define SETTINGS_FILENAME "\pAmend Preferences"
+
struct settings {
+ short version;
+#define SETTINGS_VERSION 1
char author[32];
short tabwidth;
short max_amendments;
@@ -26,7 +32,7 @@ struct settings {
extern struct settings settings;
void settings_load(void);
-void settings_save(void);
-void settings_edit(void);
+void settings_save(struct settings *tsettings);
+void settings_edit(bool use_defaults);
#endif
\ No newline at end of file