source: trunk/gtk/conf.c @ 24

Last change on this file since 24 was 24, checked in by root, 16 years ago

Update 2006-01-03

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