1 /*
2    FSearch - A fast file search utility
3    Copyright © 2016 Christian Boxdörfer
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, see <http://www.gnu.org/licenses/>.
17    */
18 
19 #include <glib.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "fsearch_config.h"
26 #include "fsearch_limits.h"
27 #include "debug.h"
28 
29 const char *config_file_name = "fsearch.conf";
30 const char *config_folder_name = "fsearch";
31 
32 void
config_build_dir(char * path,size_t len)33 config_build_dir (char *path, size_t len)
34 {
35     g_assert (path != NULL);
36     g_assert (len >= 0);
37 
38     const gchar *xdg_conf_dir = g_get_user_config_dir ();
39     snprintf (path, len, "%s/%s", xdg_conf_dir, config_folder_name);
40     return;
41 }
42 
43 static void
config_build_path(char * path,size_t len)44 config_build_path (char *path, size_t len)
45 {
46     g_assert (path != NULL);
47     g_assert (len >= 0);
48 
49     const gchar *xdg_conf_dir = g_get_user_config_dir ();
50     snprintf (path, len, "%s/%s/%s", xdg_conf_dir, config_folder_name, config_file_name);
51     return;
52 }
53 
54 bool
config_make_dir(void)55 config_make_dir (void)
56 {
57     gchar config_dir[PATH_MAX] = "";
58     config_build_dir (config_dir, sizeof (config_dir));
59     return !g_mkdir_with_parents (config_dir, 0700);
60 }
61 
62 static void
config_load_handle_error(GError * error)63 config_load_handle_error (GError *error)
64 {
65     if (!error) {
66         return;
67     }
68     switch (error->code) {
69         case G_KEY_FILE_ERROR_INVALID_VALUE:
70             fprintf(stderr, "load_config: invalid value: %s\n", error->message);
71             break;
72         case G_KEY_FILE_ERROR_KEY_NOT_FOUND:
73         case G_KEY_FILE_ERROR_GROUP_NOT_FOUND:
74             // new config, use default value and don't report anything
75             break;
76         default:
77             fprintf(stderr, "load_config: unknown error: %s\n", error->message);
78     }
79     g_error_free (error);
80 }
81 
82 static uint32_t
config_load_integer(GKeyFile * key_file,const char * group_name,const char * key,uint32_t default_value)83 config_load_integer (GKeyFile *key_file,
84                      const char *group_name,
85                      const char *key,
86                      uint32_t default_value)
87 {
88     GError *error = NULL;
89     uint32_t result = g_key_file_get_integer (key_file, group_name, key, &error);
90     if (error != NULL) {
91         result = default_value;
92         config_load_handle_error (error);
93     }
94     return result;
95 }
96 
97 static bool
config_load_boolean(GKeyFile * key_file,const char * group_name,const char * key,bool default_value)98 config_load_boolean (GKeyFile *key_file,
99                      const char *group_name,
100                      const char *key,
101                      bool default_value)
102 {
103     GError *error = NULL;
104     bool result = g_key_file_get_boolean (key_file, group_name, key, &error);
105     if (error != NULL) {
106         result = default_value;
107         config_load_handle_error (error);
108     }
109     return result;
110 }
111 
112 static char *
config_load_string(GKeyFile * key_file,const char * group_name,const char * key,char * default_value)113 config_load_string (GKeyFile *key_file,
114                     const char *group_name,
115                     const char *key,
116                     char *default_value)
117 {
118     GError *error = NULL;
119     char *result = g_key_file_get_string (key_file, group_name, key, &error);
120     if (error != NULL) {
121         result = default_value;
122         config_load_handle_error (error);
123     }
124     return result;
125 }
126 
127 bool
config_load(FsearchConfig * config)128 config_load (FsearchConfig *config)
129 {
130     g_assert (config != NULL);
131 
132     bool result = false;
133     GKeyFile *key_file = g_key_file_new ();
134     g_assert (key_file != NULL);
135 
136     gchar config_path[PATH_MAX] = "";
137     config_build_path (config_path, sizeof (config_path));
138 
139     GError *error = NULL;
140     if (g_key_file_load_from_file (key_file, config_path, G_KEY_FILE_NONE, &error)) {
141         trace ("loaded config file\n");
142         // Interface
143         config->restore_column_config = config_load_boolean (key_file,
144                                                             "Interface",
145                                                             "restore_column_configuration",
146                                                             false);
147         config->enable_list_tooltips = config_load_boolean (key_file,
148                                                             "Interface",
149                                                             "enable_list_tooltips",
150                                                             true);
151         config->enable_dark_theme = config_load_boolean (key_file,
152                                                          "Interface",
153                                                          "enable_dark_theme",
154                                                          false);
155         config->show_menubar = config_load_boolean (key_file,
156                                                     "Interface",
157                                                     "show_menubar",
158                                                     true);
159         config->show_statusbar = config_load_boolean (key_file,
160                                                       "Interface",
161                                                       "show_statusbar",
162                                                       true);
163         config->show_filter = config_load_boolean (key_file,
164                                                    "Interface",
165                                                    "show_filter",
166                                                    true);
167         config->show_search_button = config_load_boolean (key_file,
168                                                           "Interface",
169                                                           "show_search_button",
170                                                           true);
171         config->show_base_2_units = config_load_boolean (key_file,
172                                                          "Interface",
173                                                          "show_base_2_units",
174                                                          false);
175         config->action_after_file_open = config_load_integer(key_file,
176                                                              "Interface",
177                                                              "action_after_file_open",
178                                                              0);
179         config->action_after_file_open_keyboard = config_load_boolean (key_file,
180                                                                     "Interface",
181                                                                     "action_after_file_open_keyboard",
182                                                                     false);
183         config->action_after_file_open_mouse = config_load_boolean (key_file,
184                                                                     "Interface",
185                                                                     "action_after_file_open_mouse",
186                                                                     false);
187 
188         // Warning Dialogs
189         config->show_dialog_failed_opening = config_load_boolean(key_file,
190                                                                  "Dialogs",
191                                                                  "show_dialog_failed_opening",
192                                                                  true);
193 
194         // Applications
195         config->folder_open_cmd = config_load_string (key_file,
196                                                       "Applications",
197                                                       "folder_open_cmd",
198                                                       NULL);
199 
200         // Window
201         config->restore_window_size = config_load_boolean (key_file,
202                                                          "Interface",
203                                                          "restore_window_size",
204                                                          false);
205         config->window_width = config_load_integer (key_file,
206                                                     "Interface",
207                                                     "window_width",
208                                                     800);
209         config->window_height = config_load_integer (key_file,
210                                                      "Interface",
211                                                      "window_height",
212                                                      600);
213 
214         // Columns
215         config->show_listview_icons = config_load_boolean (key_file,
216                                                         "Interface",
217                                                         "show_listview_icons",
218                                                         true);
219         config->show_path_column = config_load_boolean (key_file,
220                                                         "Interface",
221                                                         "show_path_column",
222                                                         true);
223         config->show_type_column = config_load_boolean (key_file,
224                                                         "Interface",
225                                                         "show_type_column",
226                                                         true);
227         config->show_size_column = config_load_boolean (key_file,
228                                                         "Interface",
229                                                         "show_size_column",
230                                                         true);
231         config->show_modified_column = config_load_boolean (key_file,
232                                                             "Interface",
233                                                             "show_modified_column",
234                                                             true);
235 
236         // Column Size
237         config->name_column_width = config_load_integer (key_file,
238                                                    "Interface",
239                                                    "name_column_width",
240                                                    250);
241         config->path_column_width = config_load_integer (key_file,
242                                                    "Interface",
243                                                    "path_column_width",
244                                                    250);
245         config->type_column_width = config_load_integer (key_file,
246                                                    "Interface",
247                                                    "type_column_width",
248                                                    100);
249         config->size_column_width = config_load_integer (key_file,
250                                                    "Interface",
251                                                    "size_column_width",
252                                                    75);
253         config->modified_column_width = config_load_integer (key_file,
254                                                    "Interface",
255                                                    "modified_column_width",
256                                                    75);
257 
258         // Column position
259         config->name_column_pos = config_load_integer (key_file,
260                                                    "Interface",
261                                                    "name_column_pos",
262                                                    0);
263         config->path_column_pos = config_load_integer (key_file,
264                                                    "Interface",
265                                                    "path_column_pos",
266                                                    1);
267         config->type_column_pos = config_load_integer (key_file,
268                                                    "Interface",
269                                                    "type_column_pos",
270                                                    2);
271         config->size_column_pos = config_load_integer (key_file,
272                                                    "Interface",
273                                                    "size_column_pos",
274                                                    3);
275         config->modified_column_pos = config_load_integer (key_file,
276                                                    "Interface",
277                                                    "modified_column_pos",
278                                                    4);
279 
280         // Search
281         config->search_as_you_type = config_load_boolean (key_file,
282                                                           "Search",
283                                                           "search_as_you_type",
284                                                           true);
285         config->auto_search_in_path = config_load_boolean (key_file,
286                                                            "Search",
287                                                            "auto_search_in_path",
288                                                            true);
289         config->match_case = config_load_boolean (key_file,
290                                                   "Search",
291                                                   "match_case",
292                                                   false);
293         config->enable_regex = config_load_boolean (key_file,
294                                                     "Search",
295                                                     "enable_regex",
296                                                     false);
297         config->search_in_path = config_load_boolean (key_file,
298                                                       "Search",
299                                                       "search_in_path",
300                                                       false);
301         config->hide_results_on_empty_search = config_load_boolean (key_file,
302                                                                     "Search",
303                                                                     "hide_results_on_empty_search",
304                                                                     true);
305         config->limit_results = config_load_boolean (key_file,
306                                                      "Search",
307                                                      "limit_results",
308                                                      false);
309         config->num_results = config_load_integer (key_file,
310                                                    "Search",
311                                                    "num_results",
312                                                    1000);
313 
314         // Database
315         config->update_database_on_launch = config_load_boolean (key_file,
316                                                                  "Database",
317                                                                  "update_database_on_launch",
318                                                                  false);
319         config->exclude_hidden_items = config_load_boolean (key_file,
320                                                             "Database",
321                                                             "exclude_hidden_files_and_folders",
322                                                             false);
323         config->follow_symlinks = config_load_boolean (key_file,
324                                                        "Database",
325                                                        "follow_symbolic_links",
326                                                        false);
327 
328         char *exclude_files_str = config_load_string (key_file, "Database", "exclude_files", NULL);
329         if (exclude_files_str) {
330             config->exclude_files = g_strsplit (exclude_files_str, ";", -1);
331             free (exclude_files_str);
332             exclude_files_str = NULL;
333         }
334 
335         // Locations
336         uint32_t pos = 1;
337         while (true) {
338             char key[100] = "";
339             snprintf (key, sizeof (key), "location_%d", pos++);
340             char *value = config_load_string (key_file, "Database", key, NULL);
341             if (value) {
342                 config->locations = g_list_append (config->locations, value);
343             }
344             else {
345                 break;
346             }
347         }
348         // Exclude
349         pos = 1;
350         while (true) {
351             char key[100] = "";
352             snprintf (key, sizeof (key), "exclude_location_%d", pos++);
353             char *value = config_load_string (key_file, "Database", key, NULL);
354             if (value) {
355                 config->exclude_locations = g_list_append (config->exclude_locations, value);
356             }
357             else {
358                 break;
359             }
360         }
361 
362         result = true;
363     }
364     else {
365         fprintf(stderr, "load config failed: %s\n", error->message);
366         g_error_free (error);
367     }
368 
369     g_key_file_free (key_file);
370     return result;
371 }
372 
373 bool
config_load_default(FsearchConfig * config)374 config_load_default (FsearchConfig *config)
375 {
376     g_assert (config != NULL);
377 
378     // Search
379     config->auto_search_in_path = true;
380     config->search_as_you_type = true;
381     config->match_case = false;
382     config->enable_regex = false;
383     config->search_in_path = false;
384     config->hide_results_on_empty_search = true;
385     config->limit_results = true;
386     config->num_results = 1000;
387 
388     // Interface
389     config->enable_dark_theme = false;
390     config->enable_list_tooltips = true;
391     config->restore_column_config = false;
392     config->show_menubar = true;
393     config->show_statusbar = true;
394     config->show_filter = true;
395     config->show_search_button = true;
396     config->show_base_2_units = false;
397     config->action_after_file_open = 0;
398     config->action_after_file_open_keyboard = false;
399     config->action_after_file_open_mouse = false;
400 
401     // Columns
402     config->show_listview_icons = true;
403     config->show_path_column = true;
404     config->show_type_column = true;
405     config->show_size_column = true;
406     config->show_modified_column = true;
407 
408     config->name_column_pos = 0;
409     config->path_column_pos = 1;
410     config->type_column_pos = 2;
411     config->size_column_pos = 3;
412     config->modified_column_pos = 4;
413 
414     config->name_column_width = 250;
415     config->path_column_width = 250;
416     config->type_column_width = 100;
417     config->size_column_width = 75;
418     config->modified_column_width = 125;
419 
420     // Warning Dialogs
421     config->show_dialog_failed_opening = true;
422 
423     // Window
424     config->restore_window_size = false;
425     config->window_width = 800;
426     config->window_height = 600;
427 
428     // Database
429     config->update_database_on_launch = false;
430     config->exclude_hidden_items = false;
431     config->follow_symlinks = false;
432 
433     // Locations
434     config->locations = NULL;
435     config->exclude_locations = NULL;
436 
437     return true;
438 }
439 
440 bool
config_save(FsearchConfig * config)441 config_save (FsearchConfig *config)
442 {
443     g_assert (config != NULL);
444 
445     bool result = false;
446     GKeyFile *key_file = g_key_file_new ();
447     g_assert (key_file != NULL);
448 
449     // Interface
450     g_key_file_set_boolean (key_file, "Interface", "restore_column_configuration", config->restore_column_config);
451     g_key_file_set_boolean (key_file, "Interface", "enable_list_tooltips", config->enable_list_tooltips);
452     g_key_file_set_boolean (key_file, "Interface", "enable_dark_theme", config->enable_dark_theme);
453     g_key_file_set_boolean (key_file, "Interface", "show_menubar", config->show_menubar);
454     g_key_file_set_boolean (key_file, "Interface", "show_statusbar", config->show_statusbar);
455     g_key_file_set_boolean (key_file, "Interface", "show_filter", config->show_filter);
456     g_key_file_set_boolean (key_file, "Interface", "show_search_button", config->show_search_button);
457     g_key_file_set_boolean (key_file, "Interface", "show_base_2_units", config->show_base_2_units);
458     g_key_file_set_integer (key_file, "Interface", "action_after_file_open", config->action_after_file_open);
459     g_key_file_set_boolean (key_file, "Interface", "action_after_file_open_keyboard", config->action_after_file_open_keyboard);
460     g_key_file_set_boolean (key_file, "Interface", "action_after_file_open_mouse", config->action_after_file_open_mouse);
461 
462     // Warning Dialogs
463     g_key_file_set_boolean (key_file, "Dialogs", "show_dialog_failed_opening", config->show_dialog_failed_opening);
464 
465     // Window
466     g_key_file_set_boolean (key_file, "Interface", "restore_window_size", config->restore_window_size);
467     g_key_file_set_integer (key_file, "Interface", "window_width", config->window_width);
468     g_key_file_set_integer (key_file, "Interface", "window_height", config->window_height);
469 
470     // Columns visibility
471     g_key_file_set_boolean (key_file, "Interface", "show_listview_icons", config->show_listview_icons);
472     g_key_file_set_boolean (key_file, "Interface", "show_path_column", config->show_path_column);
473     g_key_file_set_boolean (key_file, "Interface", "show_type_column", config->show_type_column);
474     g_key_file_set_boolean (key_file, "Interface", "show_size_column", config->show_size_column);
475     g_key_file_set_boolean (key_file, "Interface", "show_modified_column", config->show_modified_column);
476 
477     // Column width
478     g_key_file_set_integer (key_file, "Interface", "name_column_width", config->name_column_width);
479     g_key_file_set_integer (key_file, "Interface", "path_column_width", config->path_column_width);
480     g_key_file_set_integer (key_file, "Interface", "type_column_width", config->type_column_width);
481     g_key_file_set_integer (key_file, "Interface", "size_column_width", config->size_column_width);
482     g_key_file_set_integer (key_file, "Interface", "modified_column_width", config->modified_column_width);
483 
484     // Column position
485     g_key_file_set_integer (key_file, "Interface", "name_column_pos", config->name_column_pos);
486     g_key_file_set_integer (key_file, "Interface", "path_column_pos", config->path_column_pos);
487     g_key_file_set_integer (key_file, "Interface", "type_column_pos", config->type_column_pos);
488     g_key_file_set_integer (key_file, "Interface", "size_column_pos", config->size_column_pos);
489     g_key_file_set_integer (key_file, "Interface", "modified_column_pos", config->modified_column_pos);
490 
491     // Applications
492     if (config->folder_open_cmd) {
493         g_key_file_set_string (key_file, "Applications", "folder_open_cmd", config->folder_open_cmd);
494     }
495 
496     // Search
497     g_key_file_set_boolean (key_file, "Search", "search_as_you_type", config->search_as_you_type);
498     g_key_file_set_boolean (key_file, "Search", "auto_search_in_path", config->auto_search_in_path);
499     g_key_file_set_boolean (key_file, "Search", "search_in_path", config->search_in_path);
500     g_key_file_set_boolean (key_file, "Search", "enable_regex", config->enable_regex);
501     g_key_file_set_boolean (key_file, "Search", "match_case", config->match_case);
502     g_key_file_set_boolean (key_file, "Search", "hide_results_on_empty_search", config->hide_results_on_empty_search);
503     g_key_file_set_boolean (key_file, "Search", "limit_results", config->limit_results);
504     g_key_file_set_integer (key_file, "Search", "num_results", config->num_results);
505 
506     // Database
507     g_key_file_set_boolean (key_file, "Database", "update_database_on_launch", config->update_database_on_launch);
508     g_key_file_set_boolean (key_file, "Database", "exclude_hidden_files_and_folders", config->exclude_hidden_items);
509     g_key_file_set_boolean (key_file, "Database", "follow_symbolic_links", config->follow_symlinks);
510 
511     if (config->locations) {
512         uint32_t pos = 1;
513         for (GList *l = config->locations; l != NULL; l = l->next) {
514             char location[100] = "";
515             snprintf (location, sizeof (location), "location_%d", pos);
516             g_key_file_set_string (key_file, "Database", location, l->data);
517             pos++;
518         }
519     }
520 
521     if (config->exclude_locations) {
522         uint32_t pos = 1;
523         for (GList *l = config->exclude_locations; l != NULL; l = l->next) {
524             char location[100] = "";
525             snprintf (location, sizeof (location), "exclude_location_%d", pos);
526             g_key_file_set_string (key_file, "Database", location, l->data);
527             pos++;
528         }
529     }
530 
531     if (config->exclude_files) {
532         char *exclude_files_str = g_strjoinv (";", config->exclude_files);
533         g_key_file_set_string (key_file, "Database", "exclude_files", exclude_files_str);
534         free (exclude_files_str);
535     }
536 
537     gchar config_path[PATH_MAX] = "";
538     config_build_path (config_path, sizeof (config_path));
539 
540     GError *error = NULL;
541     if (g_key_file_save_to_file (key_file, config_path, &error)) {
542         trace ("saved config file\n");
543         result = true;
544     }
545     else {
546         fprintf(stderr, "save config failed: %s\n", error->message);
547     }
548 
549     g_key_file_free (key_file);
550     return result;
551 }
552 
553 void
config_free(FsearchConfig * config)554 config_free (FsearchConfig *config)
555 {
556     g_assert (config != NULL);
557 
558     if (config->folder_open_cmd) {
559         free (config->folder_open_cmd);
560         config->folder_open_cmd = NULL;
561     }
562     if (config->locations) {
563         g_list_free_full (config->locations, (GDestroyNotify)free);
564         config->locations = NULL;
565     }
566     if (config->exclude_locations) {
567         g_list_free_full (config->exclude_locations, (GDestroyNotify)free);
568         config->exclude_locations = NULL;
569     }
570     if (config->exclude_files) {
571         g_strfreev (config->exclude_files);
572         config->exclude_files = NULL;
573     }
574     free (config);
575     config = NULL;
576 }
577 
578