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

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

Add internationalization support.

File size: 13.0 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(_("Error opening 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(_("Error obtaining lock on 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 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 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(_("Error opening file %s for reading:\n%s"),
159                                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(_("Error reading file %s:\n%s"),
170                                  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(_("Error opening or locking 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 writing to 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(_("Error renaming %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(_("Error opening file %s for reading:\n%s"),
322                                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(_("Error reading file %s:\n%s"),
333                                  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(isspace(*line))
377    line++;
378
379  /* walk over the key, which may be alphanumerics as well as - or _ */
380  for(start = line; isalnum(*start) || '_' == *start || '-' == *start; start++)
381    ;
382
383  /* they key must be immediately followed by an = */
384  if('=' != *start)
385    return NULL;
386  *(start++) = '\0';
387
388  /* then the opening quote for the value */
389  if('"' != *(start++))
390    return NULL;
391
392  /* walk over the value */
393  for(end = start; '\0' != *end && '"' != *end; end++)
394    /* skip over escaped quotes */
395    if('\\' == *end && '\0' != *(end + 1))
396      end++;
397
398  /* make sure we didn't hit the end of the string */
399  if('"' != *end)
400    return NULL;
401  *end = '\0';
402
403  /* if it's a key we recognize then save the data */
404  if(0 == strcmp(line, "torrent"))
405    state->ts_torrent = g_strcompress(start);
406  else if(0 == strcmp(line, "dir"))
407    state->ts_directory = g_strcompress(start);
408  else if(0 == strcmp(line, "paused"))
409    state->ts_paused = strbool(start);
410
411  /* return a pointer to just past the end of the value */
412  return end + 1;
413}
414
415gboolean
416cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
417  char *file = g_build_filename(confdir, FILE_STATE, NULL);
418  char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
419  GIOChannel *io = NULL;
420  GError *err;
421  int fd, ii;
422  char *torrentfile, *torrentdir, *line;
423  gsize written;
424  gboolean paused;
425  GIOStatus res;
426
427  *errstr = NULL;
428
429  if(0 > (fd = lockfile(tmpfile, errstr))) {
430    g_free(errstr);
431    *errstr = g_strdup_printf(_("Error opening or locking file %s:\n%s"),
432                              tmpfile, strerror(errno));
433    goto done;
434  }
435
436#ifdef NDEBUG
437  ftruncate(fd, 0);
438#else
439  assert(0 == ftruncate(fd, 0));
440#endif
441
442  io = g_io_channel_unix_new(fd);
443  g_io_channel_set_close_on_unref(io, TRUE);
444
445  err = NULL;
446  for(ii = 0; ii < count; ii++) {
447    /* XXX need a better way to query running/stopped state */
448    paused = ((TR_STATUS_STOPPING | TR_STATUS_PAUSE) & torrents[ii].status);
449    torrentfile = g_strescape(torrents[ii].info.torrent, "");
450    torrentdir = g_strescape(torrents[ii].folder, "");
451    /* g_strcompress */
452    line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
453                           torrentfile, torrentdir, (paused ? "yes" : "no"),
454                           STATE_SEP);
455    res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
456    g_free(torrentfile);
457    g_free(torrentdir);
458    g_free(line);
459    switch(res) {
460      case G_IO_STATUS_ERROR:
461        goto done;
462      case G_IO_STATUS_NORMAL:
463        break;
464      default:
465        assert(!"unknown return code");
466        goto done;
467    }
468  }
469  if(NULL != err ||
470     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
471    *errstr = g_strdup_printf(_("Error writing to file %s:\n%s"),
472                              tmpfile, err->message);
473    g_error_free(err);
474    goto done;
475  }
476
477  if(0 > rename(tmpfile, file)) {
478    *errstr = g_strdup_printf(_("Error renaming %s to %s:\n%s"),
479                              tmpfile, file, strerror(errno));
480    goto done;
481  }
482
483 done:
484  g_free(file);
485  g_free(tmpfile);
486  if(NULL != io)
487    g_io_channel_unref(io);
488
489  return NULL == *errstr;
490}
491
492void
493cf_freestate(struct cf_torrentstate *state) {
494  if(NULL != state->ts_torrent)
495    g_free(state->ts_torrent);
496  if(NULL != state->ts_directory)
497    g_free(state->ts_directory);
498  g_free(state);
499}
Note: See TracBrowser for help on using the repository browser.