Add manpage comment.
[doldaconnect.git] / config / dolconf.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2007 Fredrik Tolf (fredrik@dolda2000.com)
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <locale.h>
29 #include <libintl.h>
30 #include <pwd.h>
31 #include <stdarg.h>
32
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif
36 #include <utils.h>
37
38 struct validation {
39     int (*check)(const char *val);
40     char *invmsg;
41 };
42
43 struct cfvar {
44     char *name;
45     char *rname;
46     char *val;
47     struct validation *vld;
48 };
49
50 char *cfname;
51 GtkWindow *rootwnd = NULL;
52 GtkListStore *shares;
53
54 int v_nonempty(const char *val)
55 {
56     return(strlen(val) > 0);
57 }
58
59 int v_dcstring(const char *val)
60 {
61     return((strchr(val, ' ') == NULL) &&
62            (strchr(val, '|') == NULL) &&
63            (strchr(val, '$') == NULL));
64 }
65
66 int v_numeric(const char *val)
67 {
68     int f;
69     
70     for(f = 1; *val; val++, f = 0) {
71         if(!isdigit(val) && (!f || (*val != '-')))
72             return(0);
73     }
74     return(1);
75 }
76
77 #define _(text) text
78
79 struct validation nonempty = {
80     .check = v_nonempty,
81     .invmsg = _("%s must not be empty"),
82 };
83
84 struct validation dcstring = {
85     .check = v_dcstring,
86     .invmsg = _("%s must not contain spaces, `|' or `$'"),
87 };
88
89 struct validation numeric = {
90     .check = v_numeric,
91     .invmsg = _("%s must be numeric"),
92 };
93
94 struct validation *vldxlate[] = {
95     &nonempty, &dcstring, &numeric,
96     NULL
97 };
98
99 struct cfvar config[] = {
100     {"cli.defnick", _("Nickname"), "", &dcstring},
101     {"net.mode", NULL, "0", &numeric},
102     {"ui.onlylocal", NULL, "0", &numeric},
103     {"auth.authless", NULL, "0", &numeric},
104     {"transfer.slots", _("Upload slots"), "6", &numeric},
105     {"dc.speedstring", _("Connection speed"), "DSL", &dcstring},
106     {"dc.desc", _("Share description"), "", NULL},
107     {NULL}
108 };
109
110 #undef _
111 #define _(text) gettext(text)
112
113 void astcancel(GtkWidget *widget, gpointer uudata);
114 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata);
115 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata);
116 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata);
117 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata);
118 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata);
119
120 #include "dolconf-assistant.gtk"
121
122 struct cfvar *findcfvar(char *name)
123 {
124     struct cfvar *v;
125     
126     for(v = config; v->name != NULL; v++) {
127         if(!strcmp(v->name, name))
128             return(v);
129     }
130     return(NULL);
131 }
132
133 void setcfvar(char *name, const char *val)
134 {
135     struct cfvar *v;
136     
137     v = findcfvar(name);
138     free(v->val);
139     v->val = sstrdup(val);
140 }
141
142 int msgbox(int type, int buttons, char *format, ...)
143 {
144     GtkWidget *swnd;
145     va_list args;
146     char *buf;
147     int resp;
148     
149     va_start(args, format);
150     buf = vsprintf2(format, args);
151     va_end(args);
152     swnd = gtk_message_dialog_new(rootwnd, GTK_DIALOG_MODAL, type, buttons, "%s", buf);
153     gtk_window_set_title(GTK_WINDOW(swnd), _("Dolda Connect configurator"));
154     resp = gtk_dialog_run(GTK_DIALOG(swnd));
155     gtk_widget_destroy(swnd);
156     free(buf);
157     return(resp);
158 }
159
160 void prepstatic(void)
161 {
162     struct validation **v;
163     struct cfvar *c;
164     
165     for(v = vldxlate; *v != NULL; v++)
166         (*v)->invmsg = gettext((*v)->invmsg);
167     for(c = config; c->name != NULL; c++) {
168         if(c->rname != NULL)
169             c->rname = gettext(c->rname);
170         c->val = sstrdup(c->val);
171     }
172 }
173
174 char *getword(char **p)
175 {
176     char *buf, *p2, delim;
177     size_t len;
178     
179     if(**p == '\"')
180         delim = '\"';
181     else
182         delim = ' ';
183     p2 = *p;
184     while((p2 = strchr(p2 + 1, delim)) != NULL) {
185         if(p2[-1] != '\\')
186             break;
187     }
188     if(p2 == NULL)
189         p2 = *p + strlen(*p);
190     len = p2 - *p;
191     buf = smalloc(len + 1);
192     memcpy(buf, *p, len);
193     buf[len] = 0;
194     *p = p2 + ((*p2 == '\"')?1:0);
195     for(p2 = buf; *p2; p2++, len--) {
196         if(*p2 == '\\')
197             memmove(p2, p2 + 1, len--);
198     }
199     return(buf);
200 }
201
202 char *quoteword(char *word)
203 {
204     char *wp, *buf, *bp;
205     int dq, numbs, numc;
206     
207     dq = 0;
208     numbs = 0;
209     numc = 0;
210     if(*word == '\0')
211     {
212         dq = 1;
213     } else {
214         for(wp = word; *wp != '\0'; wp++)
215         {
216             if(!dq && isspace(*wp))
217                 dq = 1;
218             if((*wp == '\\') || (*wp == '\"'))
219                 numbs++;
220             numc++;
221         }
222     }
223     if(!dq && !numbs)
224         return(NULL);
225     bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
226     if(dq)
227         *(bp++) = '\"';
228     for(wp = word; *wp != '\0'; wp++)
229     {
230         if((*wp == '\\') || (*wp == '\"'))
231             *(bp++) = '\\';
232         *(bp++) = *wp;
233     }
234     if(dq)
235         *(bp++) = '\"';
236     *(bp++) = '\0';
237     return(buf);
238 }
239
240 int readconfig(void)
241 {
242     int rv;
243     FILE *cf;
244     char lbuf[1024];
245     char *key, *val, *p;
246     size_t len;
247     struct cfvar *var;
248     GtkTreeIter iter;
249     
250     rv = 0;
251     if((cf = fopen(cfname, "r")) == NULL) {
252         msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for reading: %s"), strerror(errno));
253         return(-1);
254     }
255     key = val = NULL;
256     while(fgets(lbuf, sizeof(lbuf), cf) != NULL) {
257         len = strlen(lbuf);
258         if(lbuf[len - 1] == '\n')
259             lbuf[len - 1] = 0;
260         if(key != NULL) {
261             free(key);
262             key = NULL;
263         }
264         if(val != NULL) {
265             free(val);
266             val = NULL;
267         }
268         if(!strncmp(lbuf, "set ", 4)) {
269             p = lbuf + 4;
270             if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
271                 rv = 1;
272                 continue;
273             }
274             for(var = config; var->name != NULL; var++) {
275                 if(!strcmp(var->name, key)) {
276                     free(var->val);
277                     var->val = sstrdup(val);
278                     break;
279                 }
280             }
281             if(var->name == NULL)
282                 rv = 1;
283         } else if(!strncmp(lbuf, "share ", 6)) {
284             p = lbuf + 6;
285             if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
286                 rv = 1;
287                 continue;
288             }
289             gtk_list_store_append(shares, &iter);
290             gtk_list_store_set(shares, &iter, 0, key, 1, val, -1);
291         } else if(!lbuf[0] || lbuf[0] == '#') {
292         } else {
293             rv = 1;
294         }
295     }
296     if(key != NULL)
297         free(key);
298     if(val != NULL)
299         free(val);
300     fclose(cf);
301     return(rv);
302 }
303
304 void writeconfig(void)
305 {
306     FILE *cf;
307     struct cfvar *var;
308     GtkTreeIter iter;
309     char *buf, *buf2;
310     
311     if((cf = fopen(cfname, "w")) == NULL) {
312         msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for writing: %s"), strerror(errno));
313         return;
314     }
315     fputs("# This file was generated by dolconf v" VERSION "\n\n", cf);
316     for(var = config; var->name != NULL; var++) {
317         fprintf(cf, "set %s ", var->name);
318         if((buf = quoteword(var->val)) == NULL) {
319             fputs(var->val, cf);
320         } else {
321             fputs(buf, cf);
322             free(buf);
323         }
324         fputc('\n', cf);
325     }
326     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
327         fputc('\n', cf);
328         do {
329             fputs("share ", cf);
330             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 0, &buf2, -1);
331             if((buf = quoteword(buf2)) == NULL) {
332                 fputs(buf2, cf);
333             } else {
334                 fputs(buf, cf);
335                 free(buf);
336             }
337             g_free(buf2);
338             fputc(' ', cf);
339             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf2, -1);
340             if((buf = quoteword(buf2)) == NULL) {
341                 fputs(buf2, cf);
342             } else {
343                 fputs(buf, cf);
344                 free(buf);
345             }
346             g_free(buf2);
347             fputc('\n', cf);
348         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
349     }
350     fclose(cf);
351 }
352
353 void astcancel(GtkWidget *widget, gpointer uudata)
354 {
355     gtk_main_quit();
356 }
357
358 #define bufcats(buf, str) bufcat(buf, str, strlen(str))
359
360 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata)
361 {
362     char *s, *buf;
363     size_t sdata, ssize;
364     struct cfvar *var;
365     GtkTreeIter iter;
366     
367     setcfvar("cli.defnick", gtk_entry_get_text(GTK_ENTRY(ast_nick)));
368     setcfvar("dc.desc", gtk_entry_get_text(GTK_ENTRY(ast_desc)));
369     s = NULL;
370     sdata = ssize = 0;
371     for(var = config; var->name != NULL; var++) {
372         if(var->rname == NULL)
373             continue;
374         bufcats(s, var->rname);
375         bufcats(s, ": ");
376         bufcat(s, var->val, strlen(var->val));
377         addtobuf(s, '\n');
378     }
379     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
380         addtobuf(s, '\n');
381         bufcats(s, _("Shares:\n"));
382         do {
383             addtobuf(s, '\t');
384             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf, -1);
385             bufcats(s, buf);
386             g_free(buf);
387             addtobuf(s, '\n');
388         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
389     }
390     gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ast_summary)), s, sdata);
391     free(s);
392 }
393
394 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
395 {
396     writeconfig();
397     gtk_main_quit();
398 }
399
400 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata)
401 {
402     if(v_dcstring(gtk_entry_get_text(GTK_ENTRY(ast_nick))))
403         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, TRUE);
404     else
405         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, FALSE);
406 }
407
408 int hasshare(int col, char *name)
409 {
410     GtkTreeIter iter;
411     char *buf;
412     
413     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
414         do {
415             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, col, &buf, -1);
416             if(!strcmp(buf, name)) {
417                 g_free(buf);
418                 return(1);
419             }
420             g_free(buf);
421         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
422     }
423     return(0);
424 }
425
426 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata)
427 {
428     int i;
429     GSList *fns, *next;
430     char *fn, *sn, *p;
431     GtkTreeIter iter;
432     GtkWidget *chd;
433     int resp;
434     
435     chd = gtk_file_chooser_dialog_new(_("Shared directories"), rootwnd, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
436     gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chd), TRUE);
437     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chd), TRUE);
438     resp = gtk_dialog_run(GTK_DIALOG(chd));
439     if(resp != GTK_RESPONSE_ACCEPT) {
440         gtk_widget_destroy(chd);
441         return;
442     }
443     fns = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chd));
444     gtk_widget_destroy(chd);
445     while(fns != NULL) {
446         fn = fns->data;
447         if(!hasshare(1, fn)) {
448             if((p = strrchr(fn, '/')) == NULL)
449                 p = fn;
450             else
451                 p++;
452             sn = sstrdup(p);
453             if(hasshare(0, sn)) {
454                 for(i = 2; 1; i++) {
455                     free(sn);
456                     sn = sprintf2("%s%i", p, i);
457                     if(!hasshare(0, sn))
458                         break;
459                 }
460             }
461             gtk_list_store_append(shares, &iter);
462             gtk_list_store_set(shares, &iter, 0, sn, 1, fn, -1);
463             free(sn);
464             gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, TRUE);
465         }
466         g_free(fn);
467         next = fns->next;
468         g_slist_free_1(fns);
469         fns = next;
470     }
471 }
472
473 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata)
474 {
475     GtkTreeIter iter;
476     
477     if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(ast_sharelist)), NULL, &iter))
478         gtk_list_store_remove(shares, &iter);
479     if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(shares), NULL) == 0)
480         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, FALSE);
481 }
482
483 int main(int argc, char **argv)
484 {
485     struct passwd *pwd;
486     
487     setlocale(LC_ALL, "");
488     bindtextdomain(PACKAGE, LOCALEDIR);
489     textdomain(PACKAGE);
490     prepstatic();
491     
492     gtk_init(&argc, &argv);
493     create_ast_wnd();
494     shares = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
495     gtk_tree_view_set_model(GTK_TREE_VIEW(ast_sharelist), GTK_TREE_MODEL(shares));
496     
497     cfname = NULL;
498     if(getenv("HOME") != NULL) {
499         cfname = sprintf2("%s/.doldacond.conf", getenv("HOME"));
500     } else {
501         if((pwd = getpwuid(getuid())) != NULL)
502             cfname = sprintf2("%s/.doldacond.conf", pwd->pw_dir);
503     }
504     if(cfname == NULL) {
505         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
506         exit(1);
507     }
508     
509     if(access(cfname, F_OK)) {
510         if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("It appears that you have not run this setup program before. Would you like to run the first-time setup assistant?")) == GTK_RESPONSE_YES) {
511             gtk_window_set_default_size(GTK_WINDOW(ast_wnd), 500, 350);
512             gtk_widget_show(ast_wnd);
513         }
514     } else {
515         if(readconfig() == 1) {
516             if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("The configuration file appears to have been edited outside the control of this program. If you continue using this program, all settings not handled by it will be lost. Do you wish to continue?")) == GTK_RESPONSE_NO)
517                 exit(1);
518         }
519     }
520     gtk_main();
521     return(0);
522 }