source: trunk/gtk/conf.c @ 5

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

Update 2005-11-18

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
43#define FILE_LOCK               "gtk_lock"
44#define FILE_PREFS              "gtk_prefs"
45#define FILE_PREFS_TMP          "gtk_prefs.tmp"
46#define FILE_STATE              "gtk_state"
47#define FILE_STATE_TMP          "gtk_state.tmp"
48#define PREF_SEP_KEYVAL         '\t'
49#define PREF_SEP_LINE           '\n'
50#define STATE_SEP               '\n'
51
52static int
53lockfile(const char *file, char **errstr);
54static gboolean
55writeprefs(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_encoding(io, NULL, NULL);*/
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  return writeprefs(errstr);
213}
214
215struct writeinfo {
216  GIOChannel *io;
217  GError *err;
218};
219
220static gboolean
221writeprefs(char **errstr) {
222  char *file = g_build_filename(confdir, FILE_PREFS, NULL);
223  char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
224  GIOChannel *io = NULL;
225  struct writeinfo info;
226  int fd;
227
228  assert(NULL != prefs);
229
230  *errstr = NULL;
231
232  if(0 > (fd = lockfile(tmpfile, errstr))) {
233    g_free(errstr);
234    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
235                              tmpfile, strerror(errno));
236    goto done;
237  }
238
239  info.err = NULL;
240  io = g_io_channel_unix_new(fd);
241  /*g_io_channel_set_encoding(io, NULL, NULL);*/
242  g_io_channel_set_close_on_unref(io, TRUE);
243
244  info.io = io;
245  info.err = NULL;
246  g_tree_foreach(prefs, writefile_traverse, &info);
247  if(NULL != info.err ||
248     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
249    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
250                              tmpfile, info.err->message);
251    g_error_free(info.err);
252    goto done;
253  }
254
255  if(0 > rename(tmpfile, file)) {
256    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
257                              tmpfile, file, strerror(errno));
258    goto done;
259  }
260
261 done:
262  g_free(file);
263  g_free(tmpfile);
264  if(NULL != io)
265    g_io_channel_unref(io);
266
267  return NULL == *errstr;
268}
269
270static gboolean
271writefile_traverse(gpointer key, gpointer value, gpointer data) {
272  struct writeinfo *info = data;
273  char *ekey, *eval, *line;
274  char sep[2];
275  int len;
276
277  ekey = g_strescape(key, NULL);
278  eval = g_strescape(value, NULL);
279  sep[0] = PREF_SEP_KEYVAL;
280  sep[1] = '\0';
281  line = g_strjoin(sep, ekey, eval, NULL);
282  len = strlen(line);
283  line[len] = PREF_SEP_LINE;
284
285  switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
286    case G_IO_STATUS_ERROR:
287      goto done;
288    case G_IO_STATUS_NORMAL:
289      break;
290    default:
291      assert(!"unknown return code");
292      goto done;
293  }
294
295 done:
296  g_free(ekey);
297  g_free(eval);
298  g_free(line);
299  return NULL != info->err;
300}
301
302GList *
303cf_loadstate(char **errstr) {
304  char *path = g_build_filename(confdir, FILE_STATE, NULL);
305  GIOChannel *io;
306  GError *err;
307  char term = STATE_SEP;
308  GList *ret = NULL;
309  char *line, *ptr;
310  gsize len, termpos;
311  struct cf_torrentstate *ts;
312
313  err = NULL;
314  io = g_io_channel_new_file(path, "r", &err);
315  if(NULL != err) {
316    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
317      *errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
318                                path, err->message);
319    goto done;
320  }
321  /*g_io_channel_set_encoding(io, NULL, NULL);*/
322  g_io_channel_set_line_term(io, &term, 1);
323
324  err = NULL;
325  for(;;) {
326    assert(NULL == err);
327    switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
328      case G_IO_STATUS_ERROR:
329        *errstr = g_strdup_printf("Error reading file %s:\n%s",
330                                  path, err->message);
331        goto done;
332      case G_IO_STATUS_NORMAL:
333        if(NULL != line) {
334          ts = g_new0(struct cf_torrentstate, 1);
335          ptr = line;
336          while(NULL != (ptr = getstateval(ts, ptr)))
337            ;
338          g_free(line);
339          if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
340            ret = g_list_append(ret, ts);
341          else
342            cf_freestate(ts);
343        }
344        break;
345      case G_IO_STATUS_EOF:
346        goto done;
347      default:
348        assert(!"unknown return code");
349        goto done;
350    }
351  }
352
353 done:
354  if(NULL != err)
355    g_error_free(err);
356  if(NULL != io) 
357    g_io_channel_unref(io);
358  if(NULL != *errstr && NULL != ret) {
359    g_list_foreach(ret, (GFunc)g_free, NULL);
360    g_list_free(ret);
361    ret = NULL;
362  }
363  return ret;
364}
365
366static char *
367getstateval(struct cf_torrentstate *state, char *line) {
368  char *start, *end;
369
370  while(isspace(*line))
371    line++;
372
373  if(NULL == (start = strchr(line, '=')))
374    return NULL;
375
376  while(isspace(*(++start)))
377    ;
378
379  if('"' != *start)
380    return NULL;
381
382  for(end = ++start; '\0' != *end && '"' != *end; end++)
383    if('\\' == *end && '\0' != *(end + 1))
384      end++;
385
386  if('"' != *end)
387    return NULL;
388
389  if(0 == memcmp(line, "torrent", sizeof("torrent") - 1)) {
390    state->ts_torrent = g_new(char, end - start + 1);
391    memcpy(state->ts_torrent, start, end - start);
392    state->ts_torrent[end - start] = '\0';
393  }
394  else if(0 == memcmp(line, "dir", sizeof("dir") - 1)) {
395    state->ts_directory = g_new(char, end - start + 1);
396    memcpy(state->ts_directory, start, end - start);
397    state->ts_directory[end - start] = '\0';
398  }
399  else if(0 == memcmp(line, "paused", sizeof("paused") - 1)) {
400    state->ts_paused = (0 == memcmp("yes", start, end - start));
401  }
402
403  return end + 1;
404}
405
406/* XXX need to save download directory, also maybe running/stopped state */
407gboolean
408cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
409  char *file = g_build_filename(confdir, FILE_STATE, NULL);
410  char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
411  GIOChannel *io = NULL;
412  GError *err;
413  int fd, ii;
414  char *torrentfile, *torrentdir, *line;
415  gsize written;
416  gboolean paused;
417  GIOStatus res;
418
419  *errstr = NULL;
420
421  if(0 > (fd = lockfile(tmpfile, errstr))) {
422    g_free(errstr);
423    *errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
424                              tmpfile, strerror(errno));
425    goto done;
426  }
427
428  io = g_io_channel_unix_new(fd);
429  /* XXX what the hell should I be doing about unicode? */
430  /*g_io_channel_set_encoding(io, NULL, NULL);*/
431  g_io_channel_set_close_on_unref(io, TRUE);
432
433  err = NULL;
434  for(ii = 0; ii < count; ii++) {
435    /* XXX need a better way to query running/stopped state */
436    paused = (TR_STATUS_STOPPING == torrents[ii].status ||
437              TR_STATUS_STOPPED == torrents[ii].status ||
438              TR_STATUS_PAUSE == torrents[ii].status);
439    torrentfile = g_strescape(torrents[ii].info.torrent, "");
440    torrentdir = g_strescape(torrents[ii].folder, "");
441    /* g_strcompress */
442    line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
443                           torrentfile, torrentdir, (paused ? "yes" : "no"),
444                           STATE_SEP);
445    res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
446    g_free(torrentfile);
447    g_free(torrentdir);
448    g_free(line);
449    switch(res) {
450      case G_IO_STATUS_ERROR:
451        goto done;
452      case G_IO_STATUS_NORMAL:
453        break;
454      default:
455        assert(!"unknown return code");
456        goto done;
457    }
458  }
459  if(NULL != err ||
460     G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
461    *errstr = g_strdup_printf("Error writing to file %s:\n%s",
462                              tmpfile, err->message);
463    g_error_free(err);
464    goto done;
465  }
466
467  if(0 > rename(tmpfile, file)) {
468    *errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
469                              tmpfile, file, strerror(errno));
470    goto done;
471  }
472
473 done:
474  g_free(file);
475  g_free(tmpfile);
476  if(NULL != io)
477    g_io_channel_unref(io);
478
479  return NULL == *errstr;
480}
481
482void
483cf_freestate(struct cf_torrentstate *state) {
484  if(NULL != state->ts_torrent)
485    g_free(state->ts_torrent);
486  if(NULL != state->ts_directory)
487    g_free(state->ts_directory);
488  g_free(state);
489}
Note: See TracBrowser for help on using the repository browser.