1 /*
2  * config.c
3  * Copyright 2011-2013 John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include "internal.h"
21 #include "runtime.h"
22 
23 #include <assert.h>
24 #include <string.h>
25 
26 #include "audstrings.h"
27 #include "hook.h"
28 #include "inifile.h"
29 #include "multihash.h"
30 #include "runtime.h"
31 #include "vfs.h"
32 
33 #define DEFAULT_SECTION "audacious"
34 
35 static const char * const core_defaults[] = {
36     /* clang-format off */
37     /* general */
38     "advance_on_delete", "FALSE",
39     "always_resume_paused", "TRUE",
40     "clear_playlist", "TRUE",
41     "open_to_temporary", "TRUE",
42     "recurse_folders", "TRUE",
43     "resume_playback_on_startup", "TRUE",
44     "show_interface", "TRUE",
45     "use_qt", "TRUE",
46 
47     /* equalizer */
48     "eqpreset_default_file", "",
49     "eqpreset_extension", "",
50     "equalizer_active", "FALSE",
51     "equalizer_bands", "0,0,0,0,0,0,0,0,0,0",
52     "equalizer_preamp", "0",
53 
54     /* info popup / info window */
55     "cover_name_exclude", "back",
56     "cover_name_include", "album,cover,front,folder",
57     "filepopup_delay", "5",
58     "filepopup_showprogressbar", "FALSE",
59     "recurse_for_cover", "FALSE",
60     "recurse_for_cover_depth", "0",
61     "show_filepopup_for_tuple", "TRUE",
62     "use_file_cover", "FALSE",
63 
64     /* network */
65     "net_buffer_kb", "128",
66     "save_url_history", "TRUE",
67     "socks_proxy", "FALSE",
68     "socks_type", "0",
69     "use_proxy", "FALSE",
70     "use_proxy_auth", "FALSE",
71 
72     /* output */
73     "default_gain", "0",
74     "enable_replay_gain", "TRUE",
75     "enable_clipping_prevention", "TRUE",
76     "output_bit_depth", "-1",
77     "output_buffer_size", "500",
78     "record", "FALSE",
79     "record_stream", aud::numeric_string<(int) OutputStream::AfterReplayGain>::str,
80     "replay_gain_mode", aud::numeric_string<(int) ReplayGainMode::Track>::str,
81     "replay_gain_preamp", "0",
82     "soft_clipping", "FALSE",
83     "software_volume_control", "FALSE",
84     "sw_volume_left", "100",
85     "sw_volume_right", "100",
86     "volume_delta", "5",
87 
88     /* playback */
89     "album_shuffle", "FALSE",
90     "no_playlist_advance", "FALSE",
91     "repeat", "FALSE",
92     "shuffle", "FALSE",
93     "step_size", "5",
94     "stop_after_current_song", "FALSE",
95 
96     /* playlist */
97     "chardet_fallback", "ISO-8859-1",
98 #ifdef _WIN32
99     "convert_backslash", "TRUE",
100 #else
101     "convert_backslash", "FALSE",
102 #endif
103     "export_relative_paths", "TRUE",
104     "folders_in_playlist", "FALSE",
105     "generic_title_format", "${?artist:${artist} - }${?album:${album} - }${title}",
106     "leading_zero", "FALSE",
107     "show_hours", "TRUE",
108     "metadata_fallbacks", "TRUE",
109     "metadata_on_play", "FALSE",
110     "show_numbers_in_pl", "FALSE",
111     "slow_probe", "FALSE",
112     /* clang-format on */
113     nullptr};
114 
115 enum OpType
116 {
117     OP_IS_DEFAULT,
118     OP_GET,
119     OP_SET,
120     OP_SET_NO_FLAG,
121     OP_CLEAR,
122     OP_CLEAR_NO_FLAG
123 };
124 
125 struct ConfigItem
126 {
127     String section;
128     String key;
129     String value;
130 };
131 
132 struct ConfigNode;
133 
134 // combined Data and Operation class
135 struct ConfigOp
136 {
137     OpType type;
138     const char * section;
139     const char * key;
140     String value;
141     unsigned hash;
142     bool result;
143 
144     ConfigNode * add(const ConfigOp *);
145     bool found(ConfigNode * node);
146 };
147 
148 struct ConfigNode : public MultiHash::Node, public ConfigItem
149 {
matchConfigNode150     bool match(const ConfigOp * op) const
151     {
152         return !strcmp(section, op->section) && !strcmp(key, op->key);
153     }
154 };
155 
156 typedef MultiHash_T<ConfigNode, ConfigOp> ConfigTable;
157 
158 static ConfigTable s_defaults, s_config;
159 static volatile bool s_modified;
160 
add(const ConfigOp *)161 ConfigNode * ConfigOp::add(const ConfigOp *)
162 {
163     switch (type)
164     {
165     case OP_IS_DEFAULT:
166         result = !value[0]; /* empty string is default */
167         return nullptr;
168 
169     case OP_SET:
170         result = true;
171         s_modified = true;
172         // fall-through
173 
174     case OP_SET_NO_FLAG:
175     {
176         ConfigNode * node = new ConfigNode;
177         node->section = String(section);
178         node->key = String(key);
179         node->value = value;
180         return node;
181     }
182 
183     default:
184         return nullptr;
185     }
186 }
187 
found(ConfigNode * node)188 bool ConfigOp::found(ConfigNode * node)
189 {
190     switch (type)
191     {
192     case OP_IS_DEFAULT:
193         result = !strcmp(node->value, value);
194         return false;
195 
196     case OP_GET:
197         value = node->value;
198         return false;
199 
200     case OP_SET:
201         result = !!strcmp(node->value, value);
202         if (result)
203             s_modified = true;
204         // fall-through
205 
206     case OP_SET_NO_FLAG:
207         node->value = value;
208         return false;
209 
210     case OP_CLEAR:
211         result = true;
212         s_modified = true;
213         // fall-through
214 
215     case OP_CLEAR_NO_FLAG:
216         delete node;
217         return true;
218 
219     default:
220         return false;
221     }
222 }
223 
config_op_run(ConfigOp & op,ConfigTable & table)224 static bool config_op_run(ConfigOp & op, ConfigTable & table)
225 {
226     if (!op.hash)
227         op.hash = str_calc_hash(op.section) + str_calc_hash(op.key);
228 
229     op.result = false;
230     table.lookup(&op, op.hash, op);
231     return op.result;
232 }
233 
234 class ConfigParser : public IniParser
235 {
236 private:
237     String section;
238 
handle_heading(const char * heading)239     void handle_heading(const char * heading) { section = String(heading); }
240 
handle_entry(const char * key,const char * value)241     void handle_entry(const char * key, const char * value)
242     {
243         if (!section)
244             return;
245 
246         ConfigOp op = {OP_SET_NO_FLAG, section, key, String(value)};
247         config_op_run(op, s_config);
248     }
249 };
250 
config_load()251 void config_load()
252 {
253     StringBuf path = filename_build({aud_get_path(AudPath::UserDir), "config"});
254     if (VFSFile::test_file(path, VFS_EXISTS))
255     {
256         VFSFile file(path, "r");
257         if (file)
258             ConfigParser().parse(file);
259     }
260 
261     aud_config_set_defaults(nullptr, core_defaults);
262 
263     /* migrate from previous versions */
264     if (aud_get_bool("replay_gain_album"))
265     {
266         aud_set_str("replay_gain_album", "");
267         aud_set_int("replay_gain_mode", (int)ReplayGainMode::Album);
268     }
269 
270     double step_size = aud_get_double("gtkui", "step_size");
271     if (step_size > 0)
272     {
273         aud_set_int("step_size", (int)step_size);
274         aud_set_str("gtkui", "step_size", "");
275     }
276 
277     int volume_delta = aud_get_int("statusicon", "volume_delta");
278     if (volume_delta > 0)
279     {
280         aud_set_int("volume_delta", volume_delta);
281         aud_set_str("statusicon", "volume_delta", "");
282     }
283 }
284 
config_save()285 void config_save()
286 {
287     if (!s_modified)
288         return;
289 
290     Index<ConfigItem> list;
291 
292     auto add_to_list = [&](ConfigNode * node) {
293         list.append(*node);
294         return false;
295     };
296     auto finish = []() {
297         s_modified = false; // must be inside MultiHash lock
298     };
299 
300     s_config.iterate(add_to_list, finish);
301 
302     list.sort([](const ConfigItem & a, const ConfigItem & b) {
303         if (a.section == b.section)
304             return strcmp(a.key, b.key);
305         else
306             return strcmp(a.section, b.section);
307     });
308 
309     String current_heading;
310 
311     VFSFile file(filename_build({aud_get_path(AudPath::UserDir), "config"}),
312                  "w");
313     if (!file)
314         goto FAILED;
315 
316     for (const ConfigItem & item : list)
317     {
318         if (item.section != current_heading)
319         {
320             if (!inifile_write_heading(file, item.section))
321                 goto FAILED;
322 
323             current_heading = item.section;
324         }
325 
326         if (!inifile_write_entry(file, item.key, item.value))
327             goto FAILED;
328     }
329 
330     if (file.fflush() < 0)
331         goto FAILED;
332 
333     return;
334 
335 FAILED:
336     AUDWARN("Error saving configuration.\n");
337 }
338 
aud_config_set_defaults(const char * section,const char * const * entries)339 EXPORT void aud_config_set_defaults(const char * section,
340                                     const char * const * entries)
341 {
342     if (!section)
343         section = DEFAULT_SECTION;
344 
345     while (1)
346     {
347         const char * name = *entries++;
348         const char * value = *entries++;
349         if (!name || !value)
350             break;
351 
352         ConfigOp op = {OP_SET_NO_FLAG, section, name, String(value)};
353         config_op_run(op, s_defaults);
354     }
355 }
356 
config_cleanup()357 void config_cleanup()
358 {
359     s_config.clear();
360     s_defaults.clear();
361 }
362 
aud_set_str(const char * section,const char * name,const char * value)363 EXPORT void aud_set_str(const char * section, const char * name,
364                         const char * value)
365 {
366     assert(name && value);
367 
368     ConfigOp op = {OP_IS_DEFAULT, section ? section : DEFAULT_SECTION, name,
369                    String(value)};
370     bool is_default = config_op_run(op, s_defaults);
371 
372     op.type = is_default ? OP_CLEAR : OP_SET;
373     bool changed = config_op_run(op, s_config);
374 
375     if (changed && !section)
376         event_queue(str_concat({"set ", name}), nullptr);
377 }
378 
aud_get_str(const char * section,const char * name)379 EXPORT String aud_get_str(const char * section, const char * name)
380 {
381     assert(name);
382 
383     ConfigOp op = {OP_GET, section ? section : DEFAULT_SECTION, name};
384     config_op_run(op, s_config);
385 
386     if (!op.value)
387         config_op_run(op, s_defaults);
388 
389     return op.value ? op.value : String("");
390 }
391 
aud_set_bool(const char * section,const char * name,bool value)392 EXPORT void aud_set_bool(const char * section, const char * name, bool value)
393 {
394     aud_set_str(section, name, value ? "TRUE" : "FALSE");
395 }
396 
aud_get_bool(const char * section,const char * name)397 EXPORT bool aud_get_bool(const char * section, const char * name)
398 {
399     return !strcmp(aud_get_str(section, name), "TRUE");
400 }
401 
aud_toggle_bool(const char * section,const char * name)402 EXPORT void aud_toggle_bool(const char * section, const char * name)
403 {
404     // FIXME: not thread-safe
405     aud_set_bool(section, name, !aud_get_bool(section, name));
406 }
407 
aud_set_int(const char * section,const char * name,int value)408 EXPORT void aud_set_int(const char * section, const char * name, int value)
409 {
410     aud_set_str(section, name, int_to_str(value));
411 }
412 
aud_get_int(const char * section,const char * name)413 EXPORT int aud_get_int(const char * section, const char * name)
414 {
415     return str_to_int(aud_get_str(section, name));
416 }
417 
aud_set_double(const char * section,const char * name,double value)418 EXPORT void aud_set_double(const char * section, const char * name,
419                            double value)
420 {
421     aud_set_str(section, name, double_to_str(value));
422 }
423 
aud_get_double(const char * section,const char * name)424 EXPORT double aud_get_double(const char * section, const char * name)
425 {
426     return str_to_double(aud_get_str(section, name));
427 }
428