source: trunk/gtk/conf.c @ 3206

Last change on this file since 3206 was 3206, checked in by charles, 15 years ago

preferences code refresh in the gtk+ client

  • Property svn:keywords set to Date Rev Author Id
File size: 13.3 KB
Line 
1/******************************************************************************
2 * $Id: conf.c 3206 2007-09-27 20:57:58Z charles $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/types.h>
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <pwd.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sys/stat.h>
34#include <unistd.h>
35
36#include <glib.h>
37#include <glib/gi18n.h>
38
39#include <libtransmission/transmission.h>
40#include <libtransmission/bencode.h>
41
42#include "conf.h"
43#include "util.h"
44
45#define CONF_SUBDIR             "gtk"
46#define FILE_LOCK               "lock"
47#define FILE_SOCKET             "socket"
48#define FILE_PREFS              "prefs"
49#define FILE_PREFS_TMP          "prefs.tmp"
50#define FILE_STATE              "state"
51#define FILE_STATE_TMP          "state.tmp"
52#define OLD_FILE_LOCK           "gtk_lock" /* remove this after next release */
53#define OLD_FILE_PREFS          "gtk_prefs"
54#define OLD_FILE_STATE          "gtk_state"
55#define PREF_SEP_KEYVAL         '\t'
56#define PREF_SEP_LINE           '\n'
57#define STATE_SEP               '\n'
58
59static int
60lockfile(const char *file, char **errstr);
61static void
62cf_removelocks(void);
63static char *
64cf_readfile(const char *file, const char *oldfile, gsize *len,
65            gboolean *usedold, char **errstr);
66static void
67cf_benc_append(benc_val_t *val, char type, int incsize);
68static void
69cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
70             char **errstr);
71static char *
72getstateval(benc_val_t *state, char *line);
73
74static char *gl_confdir = NULL;
75static char *gl_old_confdir = NULL;
76static char *gl_lockpath = NULL;
77static char *gl_old_lockpath = NULL;
78
79/* errstr may be NULL, this might be called before GTK is initialized */
80static int
81lockfile(const char *file, char **errstr) {
82  int fd, savederr;
83  struct flock lk;
84
85  if(NULL != errstr)
86    *errstr = NULL;
87
88  if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
89    savederr = errno;
90    if(NULL != errstr)
91      *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
92                                file, strerror(errno));
93    errno = savederr;
94    return -1;
95  }
96
97  memset(&lk, 0,  sizeof(lk));
98  lk.l_start = 0;
99  lk.l_len = 0;
100  lk.l_type = F_WRLCK;
101  lk.l_whence = SEEK_SET;
102  if(-1 == fcntl(fd, F_SETLK, &lk)) {
103    savederr = errno;
104    if(NULL != errstr) {
105      if(EAGAIN == errno)
106        *errstr = g_strdup_printf(_("Another copy of %s is already running."),
107                                  g_get_application_name());
108      else
109        *errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"),
110                                  file, strerror(errno));
111    }
112    close(fd);
113    errno = savederr;
114    return -1;
115  }
116
117  return fd;
118}
119
120/* errstr may be NULL, this might be called before GTK is initialized */
121gboolean
122cf_init(const char *dir, char **errstr) {
123  if(NULL != errstr)
124    *errstr = NULL;
125  gl_old_confdir = g_strdup(dir);
126  gl_confdir = g_build_filename(dir, CONF_SUBDIR, NULL);
127
128  if(mkdir_p(gl_confdir, 0777))
129    return TRUE;
130
131  if(NULL != errstr)
132    *errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"),
133                              gl_confdir, strerror(errno));
134  return FALSE;
135}
136
137/* errstr may be NULL, this might be called before GTK is initialized */
138gboolean
139cf_lock(char **errstr) {
140  char *path = g_build_filename(gl_old_confdir, OLD_FILE_LOCK, NULL);
141  int fd = lockfile(path, errstr);
142
143  if(0 > fd)
144    g_free(path);
145  else {
146    gl_old_lockpath = path;
147    path = g_build_filename(gl_confdir, FILE_LOCK, NULL);
148    fd = lockfile(path, errstr);
149    if(0 > fd)
150      g_free(path);
151    else
152      gl_lockpath = path;
153  }
154
155  g_atexit(cf_removelocks);
156
157  return 0 <= fd;
158}
159
160static void
161cf_removelocks(void) {
162  if(NULL != gl_lockpath) {
163    unlink(gl_lockpath);
164    g_free(gl_lockpath);
165  }
166
167  if(NULL != gl_old_lockpath) {
168    unlink(gl_old_lockpath);
169    g_free(gl_old_lockpath);
170  }
171}
172
173char *
174cf_sockname(void) {
175  return g_build_filename(gl_confdir, FILE_SOCKET, NULL);
176}
177
178static char *
179cf_readfile(const char *file, const char *oldfile, gsize *len,
180            gboolean *usedold, char **errstr) {
181  char *path;
182  GIOChannel *io;
183  GError *err = NULL;
184  char *ret;
185
186  *errstr = NULL;
187  *usedold = FALSE;
188  ret = NULL;
189  err = NULL;
190  *len = 0;
191
192  path = g_build_filename(gl_confdir, file, NULL);
193  io = g_io_channel_new_file(path, "r", &err);
194  if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
195    g_error_free(err);
196    err = NULL;
197    g_free(path);
198    path = g_build_filename(gl_old_confdir, oldfile, NULL);
199    io = g_io_channel_new_file(path, "r", &err);
200    *usedold = TRUE;
201  }
202  if(NULL != err) {
203    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
204      *errstr = g_strdup_printf(
205        _("Failed to open the file %s for reading:\n%s"), path, err->message);
206    goto done;
207  }
208  g_io_channel_set_encoding(io, NULL, NULL);
209
210  if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(io, &ret, len, &err)) {
211    *errstr = g_strdup_printf(
212      _("Error while reading from the file %s:\n%s"), path, err->message);
213    goto done;
214  }
215
216 done:
217  g_free (path);
218  if(NULL != err)
219    g_error_free(err);
220  if(NULL != io) 
221    g_io_channel_unref(io);
222  return ret;
223}
224
225/**
226***  Prefs Files
227**/
228
229#define DEFAULT_GROUP "general"
230
231static char*
232getPrefsFilename( void )
233{
234    return g_build_filename( tr_getPrefsDirectory(), "gtk", "prefs", NULL );
235}
236
237static GKeyFile*
238getPrefsKeyFile( void )
239{
240    static GKeyFile * myKeyFile = NULL;
241
242    if( myKeyFile == NULL )
243    {
244        char * filename = getPrefsFilename( );
245        myKeyFile = g_key_file_new( );
246        g_key_file_load_from_file( myKeyFile, filename, 0, NULL );
247        g_free( filename );
248    }
249
250    return myKeyFile;
251}
252
253int
254pref_int_get( const char * key )
255{
256    return g_key_file_get_integer( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL );
257}
258void
259pref_int_set( const char * key, int value )
260{
261    g_key_file_set_integer( getPrefsKeyFile( ), DEFAULT_GROUP, key, value );
262}
263void
264pref_int_set_default( const char * key, int value )
265{
266    if( !g_key_file_has_key( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL ) )
267        pref_int_set( key, value );
268}
269
270gboolean
271pref_flag_get ( const char * key )
272{
273    return g_key_file_get_boolean( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL );
274}
275void
276pref_flag_set( const char * key, gboolean value )
277{
278    g_key_file_set_boolean( getPrefsKeyFile( ), DEFAULT_GROUP, key, value );
279}
280void
281pref_flag_set_default( const char * key, gboolean value )
282{
283    if( !g_key_file_has_key( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL ) )
284        pref_flag_set( key, value );
285}
286
287char*
288pref_string_get( const char * key )
289{
290    return g_key_file_get_string( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL );
291}
292void
293pref_string_set( const char * key, const char * value )
294{
295    g_key_file_set_string( getPrefsKeyFile( ), DEFAULT_GROUP, key, value );
296}
297void
298pref_string_set_default( const char * key, const char * value )
299{
300    if( !g_key_file_has_key( getPrefsKeyFile( ), DEFAULT_GROUP, key, NULL ) )
301        pref_string_set( key, value );
302}
303
304void
305pref_save(char **errstr)
306{
307    GError * err = NULL;
308    gsize datalen;
309    char * data;
310    char * filename = getPrefsFilename( );
311
312    data = g_key_file_to_data( getPrefsKeyFile(), &datalen, &err );
313    if( !err ) {
314        GIOChannel * out = g_io_channel_new_file( filename, "w+", &err );
315        g_io_channel_write_chars( out, data, datalen, NULL, &err );
316        g_io_channel_unref( out );
317    }
318
319    if( errstr != NULL )
320        *errstr = err ? g_strdup( err->message ) : NULL;
321
322    g_free( filename );
323    g_free( data );
324    g_clear_error( &err );
325}
326
327/**
328***
329**/
330
331benc_val_t *
332cf_loadstate(char **errstr) {
333  char *data, *line, *eol, *prog;
334  gsize len;
335  gboolean usedold;
336  benc_val_t *state, *torstate;
337
338  *errstr = NULL;
339
340  data = cf_readfile(FILE_STATE, OLD_FILE_STATE, &len, &usedold, errstr);
341  if(NULL != *errstr) {
342    g_assert(NULL == data);
343    return NULL;
344  }
345
346  if(NULL == data)
347    return NULL;
348
349  state = g_new0(benc_val_t, 1);
350  if(usedold || tr_bencLoad(data, len, state, NULL)) {
351    /* XXX all this evil compat code should go away at some point */
352    memset(state, 0,  sizeof(benc_val_t));
353    state->type = TYPE_LIST;
354    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
355        line = eol + 1) {
356      *eol = '\0';
357      if(g_utf8_validate(line, -1, NULL)) {
358        cf_benc_append(state, TYPE_DICT, 10);
359        torstate = state->val.l.vals + state->val.l.count - 1;
360        prog = line;
361        while(NULL != (prog = getstateval(torstate, prog)))
362          ;
363      }
364    }
365  }
366
367  g_free(data);
368
369  return state;
370}
371
372static void
373cf_benc_append(benc_val_t *val, char type, int incsize) {
374  if(++val->val.l.count > val->val.l.alloc) {
375    val->val.l.alloc += incsize;
376    val->val.l.vals = g_renew(benc_val_t, val->val.l.vals, val->val.l.alloc);
377    memset(val->val.l.vals + val->val.l.alloc - incsize, 0,
378          incsize * sizeof(benc_val_t));
379  }
380  val->val.l.vals[val->val.l.count-1].type = type;
381}
382
383static void
384cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
385             char **errstr) {
386  char *path = g_build_filename(gl_confdir, file, NULL);
387  char *pathtmp = g_build_filename(gl_confdir, tmp, NULL);
388  GIOChannel *io = NULL;
389  GError *err = NULL;
390  char *datastr;
391  int len;
392  gsize written;
393
394  *errstr = NULL;
395  err = NULL;
396  datastr = NULL;
397
398  io = g_io_channel_new_file(pathtmp, "w", &err);
399  if(NULL != err) {
400    *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
401                              pathtmp, err->message);
402    goto done;
403  }
404  g_io_channel_set_encoding(io, NULL, NULL);
405
406  len = 0;
407  datastr = tr_bencSaveMalloc(data, &len);
408
409  written = 0;
410  g_io_channel_write_chars(io, datastr, len, &written, &err);
411  if(NULL != err)
412    g_io_channel_flush(io, &err);
413  if(NULL != err) {
414    *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
415                              pathtmp, err->message);
416    goto done;
417  }
418
419  if(0 > rename(pathtmp, path)) {
420    *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"),
421                              pathtmp, file, strerror(errno));
422    goto done;
423  }
424
425 done:
426  g_free(path);
427  g_free(pathtmp);
428  if(NULL != io)
429    g_io_channel_unref(io);
430  if(NULL != datastr)
431    free(datastr);
432}
433
434static gboolean
435strbool( const char * str )
436{
437  if( !str )
438    return FALSE;
439
440  switch(str[0]) {
441    case 'y': case 't': case 'Y': case '1': case 'j': case 'e':
442      return TRUE;
443    default:
444      if(0 == g_ascii_strcasecmp("on", str))
445        return TRUE;
446      break;
447  }
448
449  return FALSE;
450}
451
452
453static char *
454getstateval(benc_val_t *state, char *line) {
455  char *start, *end;
456
457  /* skip any leading whitespace */
458  while(g_ascii_isspace(*line))
459    line++;
460
461  /* walk over the key, which may be alphanumerics as well as - or _ */
462  for(start = line; g_ascii_isalnum(*start)
463        || '_' == *start || '-' == *start; start++)
464    ;
465
466  /* they key must be immediately followed by an = */
467  if('=' != *start)
468    return NULL;
469  *(start++) = '\0';
470
471  /* then the opening quote for the value */
472  if('"' != *(start++))
473    return NULL;
474
475  /* walk over the value */
476  for(end = start; '\0' != *end && '"' != *end; end++)
477    /* skip over escaped quotes */
478    if('\\' == *end && '\0' != *(end + 1))
479      end++;
480
481  /* make sure we didn't hit the end of the string */
482  if('"' != *end)
483    return NULL;
484  *end = '\0';
485
486  /* if it's a key we recognize then save the data */
487  if(0 == strcmp(line, "torrent") || 0 == strcmp(line, "dir") ||
488     0 == strcmp(line, "paused")) {
489    cf_benc_append(state, TYPE_STR, 6);
490    state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(line);
491    state->val.l.vals[state->val.l.count-1].val.s.i = strlen(line);
492    if('p' == *line) {
493      cf_benc_append(state, TYPE_INT, 6);
494      state->val.l.vals[state->val.l.count-1].val.i = strbool(start);
495    } else {
496      cf_benc_append(state, TYPE_STR, 6);
497      state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(start);
498      state->val.l.vals[state->val.l.count-1].val.s.i = strlen(start);
499    }
500  }
501
502  /* return a pointer to just past the end of the value */
503  return end + 1;
504}
505
506void
507cf_savestate(benc_val_t *state, char **errstr) {
508  *errstr = NULL;
509  cf_writebenc(FILE_STATE, FILE_STATE_TMP, state, errstr);
510}
511
512void
513cf_freestate( benc_val_t * state )
514{
515    if( NULL != state )
516    {
517        tr_bencFree( state );
518        g_free( state );
519    }
520}
Note: See TracBrowser for help on using the repository browser.