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