diff --git a/pd/src/s_file.c b/pd/src/s_file.c index 6066d0bb9c4f974093109f4f0f12b2e173542265..b12b34e987fa62458733bdeef6f9293b90f66c09 100644 --- a/pd/src/s_file.c +++ b/pd/src/s_file.c @@ -248,67 +248,122 @@ static void sys_donesavepreferences( void) // prefs file that is currently the one to save to static char current_prefs[FILENAME_MAX] = "org.puredata.pd-l2ork"; -static void sys_initloadpreferences( void) -{ -} +static char *sys_prefbuf; -static int sys_getpreference(const char *key, char *value, int size) +// Maximum number of bytes to be read on each iteration. Note that the buffer +// is resized in increments of BUFSZ until the entire prefs data has been +// read. For best performance, BUFSZ should be a sizeable fraction of the +// expected preference data size. The size 4096 matches PD's internal GUI +// socket size and thus should normally be enough to read the entire prefs +// data in one go. +#define BUFSZ 4096 + +// AG: We have to go to some lengths here since 'defaults read' doesn't +// properly deal with UTF-8 characters in the prefs data. 'defaults export' +// does the trick, however, so we use that to read the entire prefs data at +// once from a pipe, using plutil to convert the resulting data to JSON format +// which can then be translated to Pd's Unix preferences file format using +// sed. The result is stored in a character buffer for efficient access. From +// there we can retrieve the individual keys in the same fashion as on Unix. A +// welcome side effect is that loading the prefs is *much* faster now than +// with the previous method which invoked 'defaults read' on each individual +// key. + +// XXXTODO: In principle, this approach should also work in reverse to import +// the data into the defaults storage in one go. Presumably this should also +// be much faster than the current implementation which invokes the shell to +// run 'defaults write' for each individual key. + +static void sys_initloadpreferences(void) { - char cmdbuf[256]; - int nread = 0, nleft = size; - char default_prefs[FILENAME_MAX]; // default prefs embedded in the package - char embedded_prefs[FILENAME_MAX]; // overrides others for standalone app - char embedded_prefs_file[FILENAME_MAX]; - char user_prefs_file[FILENAME_MAX]; - char *homedir = getenv("HOME"); - struct stat statbuf; - /* the 'defaults' command expects the filename without .plist at the end */ - snprintf(default_prefs, FILENAME_MAX, "%s/../org.puredata.pd-l2ork.default", - sys_libdir->s_name); - snprintf(embedded_prefs, FILENAME_MAX, "%s/../org.puredata.pd-l2ork", - sys_libdir->s_name); - snprintf(embedded_prefs_file, FILENAME_MAX, "%s.plist", embedded_prefs); - snprintf(user_prefs_file, FILENAME_MAX, - "%s/Library/Preferences/org.puredata.pd-l2ork.plist", homedir); - if (stat(embedded_prefs_file, &statbuf) == 0) - { - snprintf(cmdbuf, FILENAME_MAX + 20, - "defaults read '%s' %s 2> /dev/null\n", embedded_prefs, key); - strncpy(current_prefs, embedded_prefs, FILENAME_MAX); - } - else if (stat(user_prefs_file, &statbuf) == 0) - { - snprintf(cmdbuf, FILENAME_MAX + 20, - "defaults read org.puredata.pd-l2ork %s 2> /dev/null\n", key); - strcpy(current_prefs, "org.puredata.pd-l2ork"); + char cmdbuf[MAXPDSTRING], *buf; + FILE *fp; + size_t sz, n = 0; + int res; + // This looks complicated, but is rather straightforward. The individual + // stages of the pipe are: + // 1. defaults export: grab our defaults in XML format + // 2. plutil -convert json -r -o - -: convert to JSON + // 3. sed: a few edits remove the extra JSON bits (curly brances, string + // quotes, unwanted whitespace and character escapes) + snprintf(cmdbuf, MAXPDSTRING, "defaults export %s - | plutil -convert json -r -o - - | sed -E -e 's/[{}]//g' -e 's/^ *\"(([^\"]|\\\\.)*)\" *: *\"(([^\"]|\\\\.)*)\".*/\\1: \\3/' -e 's/\\\\(.)/\\1/g'", current_prefs); + // open the pipe + fp = popen(cmdbuf, "r"); + if (!fp) { + // if opening the pipe failed for some reason, bail out now + if (sys_verbose) + perror(current_prefs); + error("%s: %s", current_prefs, strerror(errno)); + return; } - else - { - snprintf(cmdbuf, FILENAME_MAX + 20, - "defaults read '%s' %s 2> /dev/null\n", default_prefs, key); - strcpy(current_prefs, "org.puredata.pd-l2ork"); + // Initialize the buffer. Note that we have to reserve one extra byte for + // the terminating NUL character. The buf variable always points to the + // current chunk of memory to be written into. + sys_prefbuf = buf = malloc((sz = BUFSZ)+1); + while (buf && (n = fread(buf, 1, BUFSZ, fp)) > 0) { + char *newbuf; + size_t oldsz = sz; + // terminating NUL byte, to be safe + buf[n] = 0; + // if the byte count is short, then all data has been read; bail out + if (n < BUFSZ) break; + // more data may follow, enlarge the buffer in BUFSZ increments + sz += BUFSZ; + if ((newbuf = realloc(sys_prefbuf, sz+1))) { + // memory allocation succeeded, prepare the new buffer for the next read + sys_prefbuf = newbuf; + // adjust the current buffer pointer + buf = newbuf + oldsz; + } else { + // memory allocation failed, bail out + buf = NULL; + } } - FILE *fp = popen(cmdbuf, "r"); - while (nread < size) - { - int newread = fread(value+nread, 1, size-nread, fp); - if (newread <= 0) - break; - nread += newread; + // close the pipe + res = pclose(fp); + if (res) + post("%s: pclose returned exit status %d", current_prefs, WEXITSTATUS(res)); + // check for memory allocation errors + if (!buf) { + error("couldn't allocate memory for preferences buffer"); + return; } - pclose(fp); - if (nread < 1) + // When we come here, n is the length of the last chunk we read into buf. + // Add the terminating NUL byte there. + buf[n] = 0; + if (sys_verbose) + post("success reading preferences from: %s", current_prefs); + //post("%s: read %d bytes of preferences data", current_prefs, strlen(sys_prefbuf)); +} + +static int sys_getpreference(const char *key, char *value, int size) +{ + char searchfor[80], *where, *whereend; + if (!sys_prefbuf) return (0); - if (nread >= size) - nread = size-1; - value[nread] = 0; - if (value[nread-1] == '\n') /* remove newline character at end */ - value[nread-1] = 0; - return(1); + sprintf(searchfor, "\n%s:", key); + where = strstr(sys_prefbuf, searchfor); + if (!where) + return (0); + where += strlen(searchfor); + while (*where == ' ' || *where == '\t') + where++; + for (whereend = where; *whereend && *whereend != '\n'; whereend++) + ; + if (*whereend == '\n') + whereend--; + if (whereend > where + size - 1) + whereend = where + size - 1; + strncpy(value, where, whereend+1-where); + value[whereend+1-where] = 0; + return (1); } static void sys_doneloadpreferences( void) { + if (sys_prefbuf) + free(sys_prefbuf); + sys_prefbuf = NULL; } static void sys_initsavepreferences( void)