source: trunk/gtk/conf.c @ 214

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

Instead of storing files specific to the gtk frontend directly
in ~/.transmission/, store them in a gtk subdirectory.

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