Initial import.
[doldaconnect.git] / config / dolconf.c
CommitLineData
eb54b70d 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
38struct validation {
39 int (*check)(const char *val);
40 char *invmsg;
41};
42
43struct cfvar {
44 char *name;
45 char *rname;
46 char *val;
47 struct validation *vld;
48};
49
50char *cfname;
51GtkWindow *rootwnd = NULL;
52GtkListStore *shares;
53
54int v_nonempty(const char *val)
55{
56 return(strlen(val) > 0);
57}
58
59int v_dcstring(const char *val)
60{
61 return((strchr(val, ' ') == NULL) &&
62 (strchr(val, '|') == NULL) &&
63 (strchr(val, '$') == NULL));
64}
65
66int 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
79struct validation nonempty = {
80 .check = v_nonempty,
81 .invmsg = _("%s must not be empty"),
82};
83
84struct validation dcstring = {
85 .check = v_dcstring,
86 .invmsg = _("%s must not contain spaces, `|' or `$'"),
87};
88
89struct validation numeric = {
90 .check = v_numeric,
91 .invmsg = _("%s must be numeric"),
92};
93
94struct validation *vldxlate[] = {
95 &nonempty, &dcstring, &numeric,
96 NULL
97};
98
99struct 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
113void astcancel(GtkWidget *widget, gpointer uudata);
114void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata);
115void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata);
116void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata);
117void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata);
118void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata);
119
120#include "dolconf-assistant.gtk"
121
122struct 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
133void 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
142int 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
160void 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
174char *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
202char *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
240int 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
304void 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
353void astcancel(GtkWidget *widget, gpointer uudata)
354{
355 gtk_main_quit();
356}
357
358#define bufcats(buf, str) bufcat(buf, str, strlen(str))
359
360void 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
394void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
395{
396 writeconfig();
397 gtk_main_quit();
398}
399
400void 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
408int 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
426void 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
473void 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
483int 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}