source: trunk/gtk/conf.c @ 204

Last change on this file since 204 was 162, checked in by titer, 16 years ago

Merge from branches/new_api:r161

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