source: trunk/gtk/conf.c @ 242

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

Add IPC code for another process to communicate with a running

transmission-gtk instance.

Try to add any filenames found on the command-line, using IPC if

transmission-gtk is already running.

Some minor code cleanups.
Remove lockfile on a normal exit, justfor the sake of being tidy.

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