source: branches/joshe/gtk/conf.c @ 71

Last change on this file since 71 was 49, checked in by joshe, 16 years ago

Many small usability improvements.
Remove an ugly wart in the conf api.

File size: 12.8 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 <assert.h>
29#include <ctype.h>
30#include <errno.h>
31#include <fcntl.h>
32#include <pwd.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <unistd.h>
37
38#include <gtk/gtk.h>
39
40#include "conf.h"
41#include "transmission.h"
42#include "util.h"
43
44#define FILE_LOCK               "gtk_lock"
45#define FILE_PREFS              "gtk_prefs"
46#define FILE_PREFS_TMP          "gtk_prefs.tmp"
47#define FILE_STATE              "gtk_state"
48#define FILE_STATE_TMP          "gtk_state.tmp"
49#define PREF_SEP_KEYVAL         '\t'
50#define PREF_SEP_LINE           '\n'
51#define STATE_SEP               '\n'
52
53static int
54lockfile(const char *file, char **errstr);
55static gboolean
56writefile_traverse(gpointer key, gpointer value, gpointer data);
57static char *
58getstateval(struct cf_torrentstate *state, char *line);
59
60static char *confdir = NULL;
61static GTree *prefs = NULL;
62
63static int
64lockfile(const char *file, char **errstr) {
65  int fd, savederr;
66  struct flock lk;
67
68  *errstr = NULL;
69
70  if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
71    savederr = errno;
72    *errstr = g_strdup_printf("Error opening file %s for writing:\n%s",
73                              file, strerror(errno));
74    errno = savederr;
75    return -1;
76  }
77
78  bzero(&lk, sizeof(lk));
79  lk.l_start = 0;
80  lk.l_len = 0;
81  lk.l_type = F_WRLCK;
82  lk.l_whence = SEEK_SET;
83  if(-1 == fcntl(fd, F_SETLK, &lk)) {
84    savederr = errno;
85    if(EAGAIN == errno)
86      *errstr = g_strdup_printf("Another copy of %s is already running.",
87                                g_get_application_name());
88    else
89      *errstr = g_strdup_printf("Error obtaining lock on file %s:\n%s",
90                                file, strerror(errno));
91    close(fd);
92    errno = savederr;
93    return -1;
94  }
95
96  return fd;
97}
98
99gboolean
100cf_init(const char *dir, char **errstr) {
101  struct stat sb;
102
103  *errstr = NULL;
104  confdir = g_strdup(dir);
105
106  if(0 > stat(dir, &sb)) {
107    if(ENOENT != errno)
108      *errstr = g_strdup_printf("Failed to check directory %s:\n%s",
109                                dir, strerror(errno));
110    else {
111      if(0 == mkdir(dir, 0777))
112        return TRUE;
113      else
114        *errstr = g_strdup_printf("Failed to create directory %s:\n%s",
115                                  dir, strerror(errno));
116    }
117    return FALSE;
118  }
119
120  if(S_IFDIR & sb.st_mode)
121    return TRUE;
122
123  *errstr = g_strdup_printf("%s is not a directory", dir);
124  return FALSE;
125}
126
127gboolean
128cf_lock(char **errstr) {
129  char *path = g_build_filename(confdir, FILE_LOCK, NULL);
130  int fd = lockfile(path, errstr);
131
132  g_free(path);
133  return 0 <= fd;
134}
135
136gboolean
137cf_loadprefs(char **errstr) {
138  char *path = g_build_filename(confdir, FILE_PREFS, NULL);
139  GIOChannel *io;
140  GError *err;
141  char *line, *sep;
142  gsize len, termpos;
143  char term = PREF_SEP_LINE;
144
145  *errstr = NULL;
146
147  if(NULL != prefs)
148    g_tree_destroy(prefs);
149
150  prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
151                          g_free, g_free);
152
153  err = NULL;
154  io = g_io_channel_new_file(path, "r", &err);
155  if(NULL != err) {
156    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
157      *errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
158                                path, err->message);
159    goto done;
160  }
161  g_io_channel_set_line_term(io, &term, 1);
162
163  err = NULL;
164  for(;;) {
165    assert(NULL == err) ;
166    switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
167      case G_IO_STATUS_ERROR:
168        *errstr = g_strdup_printf("Error reading file %s:\n%s",
169                                  path, err->message);
170        goto done;
171      case G_IO_STATUS_NORMAL:
172        if(NULL != line) {
173          if(NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
174            *sep = '\0';
175            line[termpos] = '\0';
176            g_tree_insert(prefs, g_strcompress(line), g_strcompress(sep + 1));
177          }
178          g_free(line);
179        }
180        break;
181      case G_IO_STATUS_EOF:
182        goto done;
183      default:
184        assert(!"unknown return code");
185        goto done;
186    }
187  }
188
189 done:
190  if(NULL != err)
191    g_error_free(err);
192  if(NULL != io) 
193    g_io_channel_unref(io);
194  return NULL == *errstr;
195}
196
197const char *
198cf_getpref(const char *name) {
199  assert(NULL != prefs);
200
201  return g_tree_lookup(prefs, name);
202}
203
204void
205cf_setpref(const char *name, const char *value) {
206  assert(NULL != prefs);
207
208  g_tree_insert(prefs, g_strdup(name), g_strdup(value));
209}
210
211struct writeinfo {
212  GIOChannel *io;
213  GError *err;
214};
215
216gboolean
217cf_saveprefs(char **errstr) {
218  char *file = g_build_filename(confdir, FILE_PREFS, NULL);
219  char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
220  GIOChannel *io = NULL;
221  struct writeinfo info;
222  int fd;
223
224  assert(NULL != prefs);
225  assert(NULL != errstr);
226
227  *errstr = NULL;
228
229  if(0 > (fd = lockfile(tmpfile, errstr))) {
230    g_free(errstr);
231    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
232                              tmpfile, strerror(errno));
233    goto done;
234  }
235
236#ifdef NDEBUG
237  ftruncate(fd, 0);
238#else
239  assert(0 == ftruncate(fd, 0));
240#endif
241
242  info.err = NULL;
243  io = g_io_channel_unix_new(fd);
244  g_io_channel_set_close_on_unref(io, TRUE);
245
246  info.io = io;
247  info.err = NULL;
248  g_tree_foreach(prefs, writefile_traverse, &info);
249  if(NULL != info.err ||
250     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
251    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
252                              tmpfile, info.err->message);
253    g_error_free(info.err);
254    goto done;
255  }
256
257  if(0 > rename(tmpfile, file)) {
258    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
259                              tmpfile, file, strerror(errno));
260    goto done;
261  }
262
263 done:
264  g_free(file);
265  g_free(tmpfile);
266  if(NULL != io)
267    g_io_channel_unref(io);
268
269  return NULL == *errstr;
270}
271
272static gboolean
273writefile_traverse(gpointer key, gpointer value, gpointer data) {
274  struct writeinfo *info = data;
275  char *ekey, *eval, *line;
276  char sep[2];
277  int len;
278
279  ekey = g_strescape(key, NULL);
280  eval = g_strescape(value, NULL);
281  sep[0] = PREF_SEP_KEYVAL;
282  sep[1] = '\0';
283  line = g_strjoin(sep, ekey, eval, NULL);
284  len = strlen(line);
285  line[len] = PREF_SEP_LINE;
286
287  switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
288    case G_IO_STATUS_ERROR:
289      goto done;
290    case G_IO_STATUS_NORMAL:
291      break;
292    default:
293      assert(!"unknown return code");
294      goto done;
295  }
296
297 done:
298  g_free(ekey);
299  g_free(eval);
300  g_free(line);
301  return NULL != info->err;
302}
303
304GList *
305cf_loadstate(char **errstr) {
306  char *path = g_build_filename(confdir, FILE_STATE, NULL);
307  GIOChannel *io;
308  GError *err;
309  char term = STATE_SEP;
310  GList *ret = NULL;
311  char *line, *ptr;
312  gsize len, termpos;
313  struct cf_torrentstate *ts;
314
315  err = NULL;
316  io = g_io_channel_new_file(path, "r", &err);
317  if(NULL != err) {
318    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
319      *errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
320                                path, err->message);
321    goto done;
322  }
323  g_io_channel_set_line_term(io, &term, 1);
324
325  err = NULL;
326  for(;;) {
327    assert(NULL == err);
328    switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
329      case G_IO_STATUS_ERROR:
330        *errstr = g_strdup_printf("Error reading file %s:\n%s",
331                                  path, err->message);
332        goto done;
333      case G_IO_STATUS_NORMAL:
334        if(NULL != line) {
335          ts = g_new0(struct cf_torrentstate, 1);
336          ptr = line;
337          while(NULL != (ptr = getstateval(ts, ptr)))
338            ;
339          g_free(line);
340          if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
341            ret = g_list_append(ret, ts);
342          else
343            cf_freestate(ts);
344        }
345        break;
346      case G_IO_STATUS_EOF:
347        goto done;
348      default:
349        assert(!"unknown return code");
350        goto done;
351    }
352  }
353
354 done:
355  if(NULL != err)
356    g_error_free(err);
357  if(NULL != io) 
358    g_io_channel_unref(io);
359  if(NULL != *errstr && NULL != ret) {
360    g_list_foreach(ret, (GFunc)g_free, NULL);
361    g_list_free(ret);
362    ret = NULL;
363  }
364  return ret;
365}
366
367static char *
368getstateval(struct cf_torrentstate *state, char *line) {
369  char *start, *end;
370
371  /* skip any leading whitespace */
372  while(isspace(*line))
373    line++;
374
375  /* walk over the key, which may be alphanumerics as well as - or _ */
376  for(start = line; isalnum(*start) || '_' == *start || '-' == *start; start++)
377    ;
378
379  /* they key must be immediately followed by an = */
380  if('=' != *start)
381    return NULL;
382  *(start++) = '\0';
383
384  /* then the opening quote for the value */
385  if('"' != *(start++))
386    return NULL;
387
388  /* walk over the value */
389  for(end = start; '\0' != *end && '"' != *end; end++)
390    /* skip over escaped quotes */
391    if('\\' == *end && '\0' != *(end + 1))
392      end++;
393
394  /* make sure we didn't hit the end of the string */
395  if('"' != *end)
396    return NULL;
397  *end = '\0';
398
399  /* if it's a key we recognize then save the data */
400  if(0 == strcmp(line, "torrent"))
401    state->ts_torrent = g_strcompress(start);
402  else if(0 == strcmp(line, "dir"))
403    state->ts_directory = g_strcompress(start);
404  else if(0 == strcmp(line, "paused"))
405    state->ts_paused = strbool(start);
406
407  /* return a pointer to just past the end of the value */
408  return end + 1;
409}
410
411gboolean
412cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
413  char *file = g_build_filename(confdir, FILE_STATE, NULL);
414  char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
415  GIOChannel *io = NULL;
416  GError *err;
417  int fd, ii;
418  char *torrentfile, *torrentdir, *line;
419  gsize written;
420  gboolean paused;
421  GIOStatus res;
422
423  *errstr = NULL;
424
425  if(0 > (fd = lockfile(tmpfile, errstr))) {
426    g_free(errstr);
427    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
428                              tmpfile, strerror(errno));
429    goto done;
430  }
431
432#ifdef NDEBUG
433  ftruncate(fd, 0);
434#else
435  assert(0 == ftruncate(fd, 0));
436#endif
437
438  io = g_io_channel_unix_new(fd);
439  g_io_channel_set_close_on_unref(io, TRUE);
440
441  /* XXX what the hell should I be doing about unicode? */
442
443  err = NULL;
444  for(ii = 0; ii < count; ii++) {
445    /* XXX need a better way to query running/stopped state */
446    paused = ((TR_STATUS_STOPPING | TR_STATUS_PAUSE) & torrents[ii].status);
447    torrentfile = g_strescape(torrents[ii].info.torrent, "");
448    torrentdir = g_strescape(torrents[ii].folder, "");
449    /* g_strcompress */
450    line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
451                           torrentfile, torrentdir, (paused ? "yes" : "no"),
452                           STATE_SEP);
453    res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
454    g_free(torrentfile);
455    g_free(torrentdir);
456    g_free(line);
457    switch(res) {
458      case G_IO_STATUS_ERROR:
459        goto done;
460      case G_IO_STATUS_NORMAL:
461        break;
462      default:
463        assert(!"unknown return code");
464        goto done;
465    }
466  }
467  if(NULL != err ||
468     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
469    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
470                              tmpfile, err->message);
471    g_error_free(err);
472    goto done;
473  }
474
475  if(0 > rename(tmpfile, file)) {
476    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
477                              tmpfile, file, strerror(errno));
478    goto done;
479  }
480
481 done:
482  g_free(file);
483  g_free(tmpfile);
484  if(NULL != io)
485    g_io_channel_unref(io);
486
487  return NULL == *errstr;
488}
489
490void
491cf_freestate(struct cf_torrentstate *state) {
492  if(NULL != state->ts_torrent)
493    g_free(state->ts_torrent);
494  if(NULL != state->ts_directory)
495    g_free(state->ts_directory);
496  g_free(state);
497}
Note: See TracBrowser for help on using the repository browser.