source: trunk/gtk/conf.c @ 249

Last change on this file since 249 was 249, checked in by joshe, 15 years ago

Some minor code cleanups.
Handle things a little better when quitting.

File size: 13.4 KB
Line 
1/*
2  Copyright (c) 2005-2006 Joshua Elsasser. All rights reserved.
3   
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions
6  are met:
7   
8   1. Redistributions of source code must retain the above copyright
9      notice, this list of conditions and the following disclaimer.
10   2. Redistributions in binary form must reproduce the above copyright
11      notice, this list of conditions and the following disclaimer in the
12      documentation and/or other materials provided with the distribution.
13   
14  THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
15  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  POSSIBILITY OF SUCH DAMAGE.
25*/
26
27#include <sys/types.h>
28#include <ctype.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <pwd.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <unistd.h>
37
38#include <glib.h>
39#include <glib/gi18n.h>
40
41#include "conf.h"
42#include "util.h"
43
44#define CONF_SUBDIR             "gtk"
45#define FILE_LOCK               "lock"
46#define FILE_SOCKET             "socket"
47#define FILE_PREFS              "prefs"
48#define FILE_PREFS_TMP          "prefs.tmp"
49#define FILE_STATE              "state"
50#define FILE_STATE_TMP          "state.tmp"
51#define OLD_FILE_LOCK           "gtk_lock" /* remove this after next release */
52#define OLD_FILE_PREFS          "gtk_prefs"
53#define OLD_FILE_STATE          "gtk_state"
54#define PREF_SEP_KEYVAL         '\t'
55#define PREF_SEP_LINE           '\n'
56#define STATE_SEP               '\n'
57
58static int
59lockfile(const char *file, char **errstr);
60static void
61cf_removelocks(void);
62static char *
63cf_readfile(const char *file, const char *oldfile, gsize *len,
64            gboolean *usedold, char **errstr);
65static void
66cf_benc_append(benc_val_t *val, char type, int incsize);
67static void
68cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
69             char **errstr);
70static gboolean
71writefile_traverse(gpointer key, gpointer value, gpointer data);
72static char *
73getstateval(benc_val_t *state, char *line);
74
75static char *gl_confdir = NULL;
76static char *gl_old_confdir = NULL;
77static GTree *gl_prefs = NULL;
78static char *gl_lockpath = NULL;
79static char *gl_old_lockpath = NULL;
80
81/* errstr may be NULL, this might be called before GTK is initialized */
82static int
83lockfile(const char *file, char **errstr) {
84  int fd, savederr;
85  struct flock lk;
86
87  if(NULL != errstr)
88    *errstr = NULL;
89
90  if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
91    savederr = errno;
92    if(NULL != errstr)
93      *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
94                                file, strerror(errno));
95    errno = savederr;
96    return -1;
97  }
98
99  bzero(&lk, sizeof(lk));
100  lk.l_start = 0;
101  lk.l_len = 0;
102  lk.l_type = F_WRLCK;
103  lk.l_whence = SEEK_SET;
104  if(-1 == fcntl(fd, F_SETLK, &lk)) {
105    savederr = errno;
106    if(NULL != errstr) {
107      if(EAGAIN == errno)
108        *errstr = g_strdup_printf(_("Another copy of %s is already running."),
109                                  g_get_application_name());
110      else
111        *errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"),
112                                  file, strerror(errno));
113    }
114    close(fd);
115    errno = savederr;
116    return -1;
117  }
118
119  return fd;
120}
121
122/* errstr may be NULL, this might be called before GTK is initialized */
123gboolean
124cf_init(const char *dir, char **errstr) {
125  if(NULL != errstr)
126    *errstr = NULL;
127  gl_old_confdir = g_strdup(dir);
128  gl_confdir = g_build_filename(dir, CONF_SUBDIR, NULL);
129
130  if(mkdir_p(gl_confdir, 0777))
131    return TRUE;
132
133  if(NULL != errstr)
134    *errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"),
135                              gl_confdir, strerror(errno));
136  return FALSE;
137}
138
139/* errstr may be NULL, this might be called before GTK is initialized */
140gboolean
141cf_lock(char **errstr) {
142  char *path = g_build_filename(gl_old_confdir, OLD_FILE_LOCK, NULL);
143  int fd = lockfile(path, errstr);
144
145  if(0 > fd)
146    g_free(path);
147  else {
148    gl_old_lockpath = path;
149    path = g_build_filename(gl_confdir, FILE_LOCK, NULL);
150    fd = lockfile(path, errstr);
151    if(0 > fd)
152      g_free(path);
153    else
154      gl_lockpath = path;
155  }
156
157  g_atexit(cf_removelocks);
158
159  return 0 <= fd;
160}
161
162static void
163cf_removelocks(void) {
164  if(NULL != gl_lockpath) {
165    unlink(gl_lockpath);
166    g_free(gl_lockpath);
167  }
168
169  if(NULL != gl_old_lockpath) {
170    unlink(gl_old_lockpath);
171    g_free(gl_old_lockpath);
172  }
173}
174
175char *
176cf_sockname(void) {
177  return g_build_filename(gl_confdir, FILE_SOCKET, NULL);
178}
179
180static char *
181cf_readfile(const char *file, const char *oldfile, gsize *len,
182            gboolean *usedold, char **errstr) {
183  char *path;
184  GIOChannel *io;
185  GError *err;
186  char *ret;
187
188  *errstr = NULL;
189  *usedold = FALSE;
190  ret = NULL;
191  err = NULL;
192  *len = 0;
193
194  path = g_build_filename(gl_confdir, file, NULL);
195  io = g_io_channel_new_file(path, "r", &err);
196  if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
197    g_error_free(err);
198    err = NULL;
199    g_free(path);
200    path = g_build_filename(gl_old_confdir, oldfile, NULL);
201    io = g_io_channel_new_file(path, "r", &err);
202    *usedold = TRUE;
203  }
204  if(NULL != err) {
205    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
206      *errstr = g_strdup_printf(
207        _("Failed to open the file %s for reading:\n%s"), path, err->message);
208    goto done;
209  }
210  g_io_channel_set_encoding(io, NULL, NULL);
211
212  if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(io, &ret, len, &err)) {
213    *errstr = g_strdup_printf(
214      _("Error while reading from the file %s:\n%s"), path, err->message);
215    goto done;
216  }
217
218 done:
219  if(NULL != err)
220    g_error_free(err);
221  if(NULL != io) 
222    g_io_channel_unref(io);
223  return ret;
224}
225
226void
227cf_loadprefs(char **errstr) {
228  char *data, *line, *eol, *sep, *key;
229  gsize len;
230  benc_val_t val;
231  gboolean usedold;
232  int ii;
233
234  *errstr = NULL;
235
236  if(NULL != gl_prefs)
237    g_tree_destroy(gl_prefs);
238
239  gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
240                          g_free, g_free);
241
242  data = cf_readfile(FILE_PREFS, OLD_FILE_PREFS, &len, &usedold, errstr);
243  if(NULL != *errstr) {
244    g_assert(NULL == data);
245    return;
246  }
247
248  if(NULL == data)
249    return;
250
251  bzero(&val, sizeof(val));
252  if(!usedold && !tr_bencLoad(data, len, &val, NULL)) {
253    if(TYPE_DICT == val.type) {
254      key = NULL;
255      for(ii = 0; ii < val.val.l.count; ii++) {
256        if(NULL == key) {
257          g_assert(TYPE_STR == val.val.l.vals[ii].type);
258          key = val.val.l.vals[ii].val.s.s;
259        } else {
260          if(TYPE_INT == val.val.l.vals[ii].type)
261            g_tree_insert(gl_prefs, g_strdup(key),
262                         g_strdup_printf("%"PRIu64, val.val.l.vals[ii].val.i));
263          else if(TYPE_STR == val.val.l.vals[ii].type)
264            g_tree_insert(gl_prefs, g_strdup(key),
265                          g_strdup(val.val.l.vals[ii].val.s.s));
266          key = NULL;
267        }
268      }
269    }
270
271  } else {
272    /* XXX remove this in a release or two */
273    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
274        line = eol + 1) {
275      *eol = '\0';
276      if(g_utf8_validate(line, -1, NULL) &&
277         NULL != (sep = strchr(line, PREF_SEP_KEYVAL))) {
278        *sep = '\0';
279        g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep+1));
280      }
281    }
282    cf_saveprefs(errstr);
283  }
284
285  g_free(data);
286}
287
288benc_val_t *
289cf_loadstate(char **errstr) {
290  char *data, *line, *eol, *prog;
291  gsize len;
292  gboolean usedold;
293  benc_val_t *state, *torstate;
294
295  *errstr = NULL;
296
297  data = cf_readfile(FILE_STATE, OLD_FILE_STATE, &len, &usedold, errstr);
298  if(NULL != *errstr) {
299    g_assert(NULL == data);
300    return NULL;
301  }
302
303  if(NULL == data)
304    return NULL;
305
306  state = g_new0(benc_val_t, 1);
307  if(usedold || tr_bencLoad(data, len, state, NULL)) {
308    /* XXX all this evil compat code should go away at some point */
309    tr_bencFree(state);
310    bzero(state, sizeof(benc_val_t));
311    state->type = TYPE_LIST;
312    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
313        line = eol + 1) {
314      *eol = '\0';
315      if(g_utf8_validate(line, -1, NULL)) {
316        cf_benc_append(state, TYPE_DICT, 10);
317        torstate = state->val.l.vals + state->val.l.count - 1;
318        prog = line;
319        while(NULL != (prog = getstateval(torstate, prog)))
320          ;
321      }
322    }
323  }
324
325  g_free(data);
326
327  return state;
328}
329
330static void
331cf_benc_append(benc_val_t *val, char type, int incsize) {
332  if(++val->val.l.count > val->val.l.alloc) {
333    val->val.l.alloc += incsize;
334    val->val.l.vals = g_renew(benc_val_t, val->val.l.vals, val->val.l.alloc);
335    bzero(val->val.l.vals + val->val.l.alloc - incsize,
336          incsize * sizeof(benc_val_t));
337  }
338  val->val.l.vals[val->val.l.count-1].type = type;
339}
340
341const char *
342cf_getpref(const char *name) {
343  g_assert(NULL != gl_prefs);
344
345  return g_tree_lookup(gl_prefs, name);
346}
347
348void
349cf_setpref(const char *name, const char *value) {
350  g_assert(NULL != gl_prefs);
351
352  g_tree_insert(gl_prefs, g_strdup(name), g_strdup(value));
353}
354
355static void
356cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
357             char **errstr) {
358  char *path = g_build_filename(gl_confdir, file, NULL);
359  char *pathtmp = g_build_filename(gl_confdir, tmp, NULL);
360  GIOChannel *io = NULL;
361  GError *err;
362  char *datastr;
363  size_t len;
364  gsize written;
365
366  *errstr = NULL;
367  err = NULL;
368  datastr = NULL;
369
370  io = g_io_channel_new_file(pathtmp, "w", &err);
371  if(NULL != err) {
372    *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
373                              pathtmp, err->message);
374    goto done;
375  }
376  g_io_channel_set_encoding(io, NULL, NULL);
377
378  len = 0;
379  datastr = tr_bencSaveMalloc(data, &len);
380
381  written = 0;
382  g_io_channel_write_chars(io, datastr, len, &written, &err);
383  if(NULL != err)
384    g_io_channel_flush(io, &err);
385  if(NULL != err) {
386    *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
387                              pathtmp, err->message);
388    goto done;
389  }
390
391  if(0 > rename(pathtmp, path)) {
392    *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"),
393                              pathtmp, file, strerror(errno));
394    goto done;
395  }
396
397 done:
398  g_free(path);
399  g_free(pathtmp);
400  if(NULL != io)
401    g_io_channel_unref(io);
402  if(NULL != datastr)
403    free(datastr);
404}
405
406void
407cf_saveprefs(char **errstr) {
408  benc_val_t val;
409  benc_val_t *ptr;
410
411  *errstr = NULL;
412
413  bzero(&val, sizeof(val));
414  val.type = TYPE_DICT;
415  val.val.l.alloc = val.val.l.count = g_tree_nnodes(gl_prefs) * 2;
416  val.val.l.vals = g_new0(benc_val_t, val.val.l.alloc);
417
418  ptr = val.val.l.vals;
419  g_tree_foreach(gl_prefs, writefile_traverse, &ptr);
420  g_assert(ptr - val.val.l.vals == val.val.l.alloc);
421
422  cf_writebenc(FILE_PREFS, FILE_PREFS_TMP, &val, errstr);
423  tr_bencFree(&val);
424}
425
426static gboolean
427writefile_traverse(gpointer key, gpointer value, gpointer data) {
428  benc_val_t **ptr = data;
429  benc_val_t *bkey = *ptr;
430  benc_val_t *bval = (*ptr) + 1;
431
432  *ptr = (*ptr) + 2;
433
434  bkey->type = TYPE_STR;
435  bkey->val.s.s = key;
436  bkey->val.s.i = strlen(key);
437
438  bval->type = TYPE_STR;
439  bval->val.s.s = value;
440  bval->val.s.i = strlen(value);
441
442  return FALSE;
443}
444
445static char *
446getstateval(benc_val_t *state, char *line) {
447  char *start, *end;
448
449  /* skip any leading whitespace */
450  while(g_ascii_isspace(*line))
451    line++;
452
453  /* walk over the key, which may be alphanumerics as well as - or _ */
454  for(start = line; g_ascii_isalnum(*start)
455        || '_' == *start || '-' == *start; start++)
456    ;
457
458  /* they key must be immediately followed by an = */
459  if('=' != *start)
460    return NULL;
461  *(start++) = '\0';
462
463  /* then the opening quote for the value */
464  if('"' != *(start++))
465    return NULL;
466
467  /* walk over the value */
468  for(end = start; '\0' != *end && '"' != *end; end++)
469    /* skip over escaped quotes */
470    if('\\' == *end && '\0' != *(end + 1))
471      end++;
472
473  /* make sure we didn't hit the end of the string */
474  if('"' != *end)
475    return NULL;
476  *end = '\0';
477
478  /* if it's a key we recognize then save the data */
479  if(0 == strcmp(line, "torrent") || 0 == strcmp(line, "dir") ||
480     0 == strcmp(line, "paused")) {
481    cf_benc_append(state, TYPE_STR, 6);
482    state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(line);
483    state->val.l.vals[state->val.l.count-1].val.s.i = strlen(line);
484    if('p' == *line) {
485      cf_benc_append(state, TYPE_INT, 6);
486      state->val.l.vals[state->val.l.count-1].val.i = strbool(start);
487    } else {
488      cf_benc_append(state, TYPE_STR, 6);
489      state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(start);
490      state->val.l.vals[state->val.l.count-1].val.s.i = strlen(start);
491    }
492  }
493
494  /* return a pointer to just past the end of the value */
495  return end + 1;
496}
497
498void
499cf_savestate(benc_val_t *state, char **errstr) {
500  *errstr = NULL;
501  cf_writebenc(FILE_STATE, FILE_STATE_TMP, state, errstr);
502}
503
504void
505cf_freestate(benc_val_t *state) {
506  tr_bencFree(state);
507  g_free(state);
508}
Note: See TracBrowser for help on using the repository browser.