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

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

Merge rev 68:72 from trunk.

File size: 12.9 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(g_utf8_validate(line, len, NULL) &&
174             NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
175            *sep = '\0';
176            line[termpos] = '\0';
177            g_tree_insert(prefs, g_strcompress(line), g_strcompress(sep + 1));
178          }
179          g_free(line);
180        }
181        break;
182      case G_IO_STATUS_EOF:
183        goto done;
184      default:
185        assert(!"unknown return code");
186        goto done;
187    }
188  }
189
190 done:
191  if(NULL != err)
192    g_error_free(err);
193  if(NULL != io) 
194    g_io_channel_unref(io);
195  return NULL == *errstr;
196}
197
198const char *
199cf_getpref(const char *name) {
200  assert(NULL != prefs);
201
202  return g_tree_lookup(prefs, name);
203}
204
205void
206cf_setpref(const char *name, const char *value) {
207  assert(NULL != prefs);
208
209  g_tree_insert(prefs, g_strdup(name), g_strdup(value));
210}
211
212struct writeinfo {
213  GIOChannel *io;
214  GError *err;
215};
216
217gboolean
218cf_saveprefs(char **errstr) {
219  char *file = g_build_filename(confdir, FILE_PREFS, NULL);
220  char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
221  GIOChannel *io = NULL;
222  struct writeinfo info;
223  int fd;
224
225  assert(NULL != prefs);
226  assert(NULL != errstr);
227
228  *errstr = NULL;
229
230  if(0 > (fd = lockfile(tmpfile, errstr))) {
231    g_free(errstr);
232    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
233                              tmpfile, strerror(errno));
234    goto done;
235  }
236
237#ifdef NDEBUG
238  ftruncate(fd, 0);
239#else
240  assert(0 == ftruncate(fd, 0));
241#endif
242
243  info.err = NULL;
244  io = g_io_channel_unix_new(fd);
245  g_io_channel_set_close_on_unref(io, TRUE);
246
247  info.io = io;
248  info.err = NULL;
249  g_tree_foreach(prefs, writefile_traverse, &info);
250  if(NULL != info.err ||
251     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
252    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
253                              tmpfile, info.err->message);
254    g_error_free(info.err);
255    goto done;
256  }
257
258  if(0 > rename(tmpfile, file)) {
259    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
260                              tmpfile, file, strerror(errno));
261    goto done;
262  }
263
264 done:
265  g_free(file);
266  g_free(tmpfile);
267  if(NULL != io)
268    g_io_channel_unref(io);
269
270  return NULL == *errstr;
271}
272
273static gboolean
274writefile_traverse(gpointer key, gpointer value, gpointer data) {
275  struct writeinfo *info = data;
276  char *ekey, *eval, *line;
277  char sep[2];
278  int len;
279
280  ekey = g_strescape(key, NULL);
281  eval = g_strescape(value, NULL);
282  sep[0] = PREF_SEP_KEYVAL;
283  sep[1] = '\0';
284  line = g_strjoin(sep, ekey, eval, NULL);
285  len = strlen(line);
286  line[len] = PREF_SEP_LINE;
287
288  switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
289    case G_IO_STATUS_ERROR:
290      goto done;
291    case G_IO_STATUS_NORMAL:
292      break;
293    default:
294      assert(!"unknown return code");
295      goto done;
296  }
297
298 done:
299  g_free(ekey);
300  g_free(eval);
301  g_free(line);
302  return NULL != info->err;
303}
304
305GList *
306cf_loadstate(char **errstr) {
307  char *path = g_build_filename(confdir, FILE_STATE, NULL);
308  GIOChannel *io;
309  GError *err;
310  char term = STATE_SEP;
311  GList *ret = NULL;
312  char *line, *ptr;
313  gsize len, termpos;
314  struct cf_torrentstate *ts;
315
316  err = NULL;
317  io = g_io_channel_new_file(path, "r", &err);
318  if(NULL != err) {
319    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
320      *errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
321                                path, err->message);
322    goto done;
323  }
324  g_io_channel_set_line_term(io, &term, 1);
325
326  err = NULL;
327  for(;;) {
328    assert(NULL == err);
329    switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
330      case G_IO_STATUS_ERROR:
331        *errstr = g_strdup_printf("Error reading file %s:\n%s",
332                                  path, err->message);
333        goto done;
334      case G_IO_STATUS_NORMAL:
335        if(NULL != line) {
336          if(g_utf8_validate(line, -1, NULL)) {
337            ts = g_new0(struct cf_torrentstate, 1);
338            ptr = line;
339            while(NULL != (ptr = getstateval(ts, ptr)))
340              ;
341            if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
342              ret = g_list_append(ret, ts);
343            else
344              cf_freestate(ts);
345          }
346          g_free(line);
347        }
348        break;
349      case G_IO_STATUS_EOF:
350        goto done;
351      default:
352        assert(!"unknown return code");
353        goto done;
354    }
355  }
356
357 done:
358  if(NULL != err)
359    g_error_free(err);
360  if(NULL != io) 
361    g_io_channel_unref(io);
362  if(NULL != *errstr && NULL != ret) {
363    g_list_foreach(ret, (GFunc)g_free, NULL);
364    g_list_free(ret);
365    ret = NULL;
366  }
367  return ret;
368}
369
370static char *
371getstateval(struct cf_torrentstate *state, char *line) {
372  char *start, *end;
373
374  /* skip any leading whitespace */
375  while(isspace(*line))
376    line++;
377
378  /* walk over the key, which may be alphanumerics as well as - or _ */
379  for(start = line; isalnum(*start) || '_' == *start || '-' == *start; start++)
380    ;
381
382  /* they key must be immediately followed by an = */
383  if('=' != *start)
384    return NULL;
385  *(start++) = '\0';
386
387  /* then the opening quote for the value */
388  if('"' != *(start++))
389    return NULL;
390
391  /* walk over the value */
392  for(end = start; '\0' != *end && '"' != *end; end++)
393    /* skip over escaped quotes */
394    if('\\' == *end && '\0' != *(end + 1))
395      end++;
396
397  /* make sure we didn't hit the end of the string */
398  if('"' != *end)
399    return NULL;
400  *end = '\0';
401
402  /* if it's a key we recognize then save the data */
403  if(0 == strcmp(line, "torrent"))
404    state->ts_torrent = g_strcompress(start);
405  else if(0 == strcmp(line, "dir"))
406    state->ts_directory = g_strcompress(start);
407  else if(0 == strcmp(line, "paused"))
408    state->ts_paused = strbool(start);
409
410  /* return a pointer to just past the end of the value */
411  return end + 1;
412}
413
414gboolean
415cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
416  char *file = g_build_filename(confdir, FILE_STATE, NULL);
417  char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
418  GIOChannel *io = NULL;
419  GError *err;
420  int fd, ii;
421  char *torrentfile, *torrentdir, *line;
422  gsize written;
423  gboolean paused;
424  GIOStatus res;
425
426  *errstr = NULL;
427
428  if(0 > (fd = lockfile(tmpfile, errstr))) {
429    g_free(errstr);
430    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
431                              tmpfile, strerror(errno));
432    goto done;
433  }
434
435#ifdef NDEBUG
436  ftruncate(fd, 0);
437#else
438  assert(0 == ftruncate(fd, 0));
439#endif
440
441  io = g_io_channel_unix_new(fd);
442  g_io_channel_set_close_on_unref(io, TRUE);
443
444  err = NULL;
445  for(ii = 0; ii < count; ii++) {
446    /* XXX need a better way to query running/stopped state */
447    paused = ((TR_STATUS_STOPPING | TR_STATUS_PAUSE) & torrents[ii].status);
448    torrentfile = g_strescape(torrents[ii].info.torrent, "");
449    torrentdir = g_strescape(torrents[ii].folder, "");
450    /* g_strcompress */
451    line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
452                           torrentfile, torrentdir, (paused ? "yes" : "no"),
453                           STATE_SEP);
454    res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
455    g_free(torrentfile);
456    g_free(torrentdir);
457    g_free(line);
458    switch(res) {
459      case G_IO_STATUS_ERROR:
460        goto done;
461      case G_IO_STATUS_NORMAL:
462        break;
463      default:
464        assert(!"unknown return code");
465        goto done;
466    }
467  }
468  if(NULL != err ||
469     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
470    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
471                              tmpfile, err->message);
472    g_error_free(err);
473    goto done;
474  }
475
476  if(0 > rename(tmpfile, file)) {
477    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
478                              tmpfile, file, strerror(errno));
479    goto done;
480  }
481
482 done:
483  g_free(file);
484  g_free(tmpfile);
485  if(NULL != io)
486    g_io_channel_unref(io);
487
488  return NULL == *errstr;
489}
490
491void
492cf_freestate(struct cf_torrentstate *state) {
493  if(NULL != state->ts_torrent)
494    g_free(state->ts_torrent);
495  if(NULL != state->ts_directory)
496    g_free(state->ts_directory);
497  g_free(state);
498}
Note: See TracBrowser for help on using the repository browser.