1 /*
2 * KCemu -- The emulator for the KC85 homecomputer series and much more.
3 * Copyright (C) 1997-2010 Torsten Paul
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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <fstream>
21 #include <algorithm>
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <assert.h>
27 #include <stdlib.h>
28 #include <sys/stat.h>
29
30 #include "kc/system.h"
31
32 #include "kc/kc.h"
33 #include "kc/prefs/types.h"
34 #include "kc/prefs/prefs.h"
35 #include "kc/prefs/parser.h"
36 #include "kc/prefs/dirlist.h"
37 #include "kc/prefs/strlist.h"
38
39 #include "sys/sysdep.h"
40
41 #include "libdbg/dbg.h"
42
43 using namespace std;
44
45 struct find_by_type : public unary_function<EmulationType *, void> {
46 int _type;
47 EmulationType *_result;
find_by_typefind_by_type48 find_by_type(int type) : _type(type), _result(NULL) {}
operator ()find_by_type49 void operator()(EmulationType *emulation_type) {
50 if ((_result == NULL) && emulation_type->get_type() == _type)
51 _result = emulation_type;
52 }
53 };
54
55 struct find_by_type_and_variant : public unary_function<SystemType *, void> {
56 int _type;
57 int _variant;
58 SystemType *_result;
find_by_type_and_variantfind_by_type_and_variant59 find_by_type_and_variant(int type, int variant) : _type(type), _variant(variant), _result(NULL) {}
operator ()find_by_type_and_variant60 void operator()(SystemType *system) {
61 if ((_result == NULL) && (system->get_kc_type() == _type) && (system->get_kc_variant() == _variant))
62 _result = system;
63 }
64 };
65
66 struct SaveVisitor : ProfileVisitor {
67 Preferences *_preferences;
SaveVisitorSaveVisitor68 SaveVisitor(Preferences *preferences) : _preferences(preferences) {}
handle_profileSaveVisitor69 void handle_profile(Profile *profile) {
70 _preferences->save_profile(profile);
71 }
72 };
73
74 struct RejectVisitor : ProfileVisitor {
75 Preferences *_preferences;
RejectVisitorRejectVisitor76 RejectVisitor(Preferences *preferences) : _preferences(preferences) {}
handle_profileRejectVisitor77 void handle_profile(Profile *profile) {
78 _preferences->reject_changes(profile);
79 }
80 };
81
82 Preferences *Preferences::_instance = 0;
83
84 const char *Preferences::PROFILE_NAME_ROOT = "root";
85 const char *Preferences::PROFILE_NAME_DEFAULT = "default";
86 const char *Preferences::PROFILE_KEY_NAME = "name";
87 const char *Preferences::PROFILE_KEY_SYSTEM = "system";
88 const char *Preferences::PROFILE_KEY_VARIANT = "variant";
89 const char *Preferences::CONFIG_FILE_EXTENSION = ".conf";
90 const char *Preferences::USER_CONFIG_PREFIX = "_";
91
Preferences(const char * sys_dir,const char * usr_dir,const char * add_dir)92 Preferences::Preferences(const char *sys_dir, const char *usr_dir, const char *add_dir) : _sys_dir(sys_dir), _usr_dir(usr_dir), _add_dir(add_dir) {
93 load_default_profiles();
94 load_system_profiles();
95 load_user_profiles();
96 }
97
98 void
load_default_profiles(void)99 Preferences::load_default_profiles(void)
100 {
101 _root_profile = get_profile(get_profile_path(_sys_dir, PROFILE_NAME_DEFAULT), PROFILE_LEVEL_ROOT, PROFILE_NAME_DEFAULT, PROFILE_NAME_ROOT);
102 _default_profile = get_profile(get_profile_path(_usr_dir, PROFILE_NAME_DEFAULT), PROFILE_LEVEL_DEFAULT, PROFILE_NAME_DEFAULT, PROFILE_NAME_DEFAULT);
103
104 _default_profile->set_parent(_root_profile);
105 _root_profile->set_changed(false);
106 _default_profile->set_changed(false);
107 }
108
109 void
load_system_profiles(void)110 Preferences::load_system_profiles(void)
111 {
112 emulation_type_list_t list = EmulationType::get_emulation_types();
113 for (emulation_type_list_t::iterator it = list.begin();it != list.end();it++) {
114 string config_name = (*it)->get_config_name();
115
116 Profile *sys_profile = get_profile(get_profile_path(_sys_dir, config_name), PROFILE_LEVEL_SYSTEM, config_name, (*it)->get_name());
117 Profile *usr_profile = get_profile(get_profile_path(_usr_dir, config_name), PROFILE_LEVEL_SYSTEM_USER, config_name, (*it)->get_name());
118
119 sys_profile->set_int_value(PROFILE_KEY_SYSTEM, (*it)->get_kc_type());
120 usr_profile->set_int_value(PROFILE_KEY_SYSTEM, (*it)->get_kc_type());
121
122 sys_profile->set_parent(_default_profile);
123 usr_profile->set_parent(sys_profile);
124
125 sys_profile->set_changed(false);
126 usr_profile->set_changed(false);
127
128 _sys_profiles[config_name] = sys_profile;
129 _usr_profiles[config_name] = usr_profile;
130
131 _usr_profiles_by_type[(*it)->get_kc_type()] = usr_profile;
132 }
133 }
134
135 void
load_user_profiles(void)136 Preferences::load_user_profiles(void)
137 {
138 DirectoryList dir(_add_dir);
139 for (DirectoryList::iterator it = dir.begin();it != dir.end();it++) {
140 string config_name = string(USER_CONFIG_PREFIX) + (*it)->get_filename();
141 Profile *profile = get_profile((*it)->get_path(), PROFILE_LEVEL_USER, config_name, (*it)->get_filename());
142
143 kc_type_t kc_type = (kc_type_t)profile->get_int_value(PROFILE_KEY_SYSTEM, KC_TYPE_NONE);
144 map<kc_type_t, Profile *>::iterator i = _usr_profiles_by_type.find(kc_type);
145 if (i == _usr_profiles_by_type.end()) {
146 delete profile;
147 continue;
148 }
149
150 if (profile->contains_key(PROFILE_KEY_NAME)) {
151 const char *name = profile->get_string_value(PROFILE_KEY_NAME, "");
152 if (strlen(name) > 0)
153 profile->set_name(name);
154 }
155
156 profile->set_parent((*i).second);
157 profile->set_changed(false);
158 _add_profiles[config_name] = profile;
159 }
160 }
161
162 string
get_profile_path(string dir,string config_name)163 Preferences::get_profile_path(string dir, string config_name) {
164 string path = dir + "/" + config_name + CONFIG_FILE_EXTENSION;
165 return path;
166 }
167
168 Profile *
get_profile(string path,profile_level_t level,string config_name,string name)169 Preferences::get_profile(string path, profile_level_t level, string config_name, string name) {
170 ProfileParser *parser = new SingleProfileParser(path.c_str(), level, config_name, name);
171 parser->parse();
172 profile_list_t *list = parser->get_profiles();
173 assert(list->size() == 1);
174 Profile *profile = list->front();
175 delete parser;
176 return profile;
177 }
178
~Preferences(void)179 Preferences::~Preferences(void) {
180 }
181
182 void
init(const char * system_dir,const char * user_dir,const char * add_dir)183 Preferences::init(const char *system_dir, const char *user_dir, const char *add_dir) {
184 if (_instance == 0)
185 _instance = new Preferences(system_dir, user_dir, add_dir);
186 }
187
188 Preferences *
instance(void)189 Preferences::instance(void) {
190 if (_instance == 0) {
191 cerr << "Preferences::instance(void): Preferences::init() not called!" << endl;
192 exit(1);
193 }
194 return _instance;
195 }
196
197 Profile *
find_profile(const char * key)198 Preferences::find_profile(const char *key) {
199 if (strcmp(key, PROFILE_NAME_DEFAULT) == 0)
200 return _default_profile;
201
202 profile_map_t::iterator it1 = _usr_profiles.find(key);
203 if (it1 != _usr_profiles.end())
204 return (*it1).second;
205
206 profile_map_t::iterator it2 = _add_profiles.find(key);
207 if (it2 != _add_profiles.end())
208 return (*it2).second;
209
210 return NULL;
211 }
212
213 Profile *
find_profile_by_name(const char * name)214 Preferences::find_profile_by_name(const char *name) {
215 if (strcmp(name, PROFILE_NAME_DEFAULT) == 0)
216 return _default_profile;
217
218 for (profile_map_t::iterator it1 = _usr_profiles.begin();it1 != _usr_profiles.end();it1++) {
219 if (strcmp(name, (*it1).second->get_name()) == 0)
220 return (*it1).second;
221 }
222
223 for (profile_map_t::iterator it2 = _add_profiles.begin();it2 != _add_profiles.end();it2++) {
224 if (strcmp(name, (*it2).second->get_name()) == 0)
225 return (*it2).second;
226 }
227
228 return NULL;
229 }
230
231 list<Profile *>
find_child_profiles(const char * key)232 Preferences::find_child_profiles(const char *key) {
233 list<Profile *> child_profiles;
234 for (profile_map_t::iterator it = _add_profiles.begin();it != _add_profiles.end();it++) {
235 Profile *child_profile = (*it).second;
236 const char *config_name = child_profile->get_parent()->get_config_name();
237 if (strcmp(key, config_name) != 0)
238 continue;
239
240 child_profiles.push_back(child_profile);
241 }
242 return child_profiles;
243 }
244
245 Profile *
create_user_profile(Profile * parent)246 Preferences::create_user_profile(Profile *parent) {
247 char name[1024];
248 struct stat statbuf;
249
250 if (parent == NULL)
251 return NULL;
252
253 kc_type_t kc_type = (kc_type_t)parent->get_int_value(PROFILE_KEY_SYSTEM, KC_TYPE_NONE);
254 if (kc_type == KC_TYPE_NONE)
255 return NULL;
256
257 for (int a = 0;a < 10000;a++) {
258 snprintf(name, sizeof(name), "profile_%04d", a);
259 string config_name = string(name) + CONFIG_FILE_EXTENSION;
260 string path = _add_dir + "/" + config_name;
261
262 if (Preferences::instance()->find_profile(config_name.c_str()) != NULL)
263 continue;
264
265 if (stat(path.c_str(), &statbuf) == 0)
266 continue;
267
268 Profile *profile = new Profile(PROFILE_LEVEL_USER, path, config_name, name);
269 profile->set_int_value(PROFILE_KEY_SYSTEM, kc_type);
270 profile->set_parent(parent);
271 _add_profiles[config_name] = profile;
272 return profile;
273 }
274
275 return NULL;
276 }
277
278 Profile *
copy_user_profile(Profile * profile)279 Preferences::copy_user_profile(Profile *profile)
280 {
281 Profile *copy = create_user_profile(profile->get_parent());
282 if (copy == NULL)
283 return NULL;
284
285 list<string> *keys = profile->get_keys();
286 for (list<string>::iterator it = keys->begin();it != keys->end();it++) {
287 const char *key = (*it).c_str();
288 if (strcmp(key, PROFILE_KEY_NAME) == 0)
289 continue;
290
291 char *encoded_value = profile->get_encoded_value(key);
292 copy->set_value(key, new ProfileValue(profile->get_value(key)));
293 free(encoded_value);
294 }
295
296 return copy;
297 }
298
299 bool
has_changed_profiles(void)300 Preferences::has_changed_profiles(void) {
301 if (_default_profile->is_changed())
302 return true;
303
304 for (profile_map_t::iterator it = _usr_profiles.begin();it != _usr_profiles.end();it++)
305 if ((*it).second->is_changed())
306 return true;
307
308 for (profile_map_t::iterator it = _add_profiles.begin();it != _add_profiles.end();it++)
309 if ((*it).second->is_changed())
310 return true;
311
312 return false;
313 }
314
315 void
visit_changed(ProfileVisitor & visitor)316 Preferences::visit_changed(ProfileVisitor &visitor) {
317 DBG(1, form("KCemu/Preferences",
318 "Preferences::visit_changed(): Start visiting changed profiles:\n"));
319
320 if (_default_profile->is_changed()) {
321 DBG(1, form("KCemu/Preferences",
322 "Preferences::visit_changed(): ... default profile changed\n"));
323 visitor.handle_profile(_default_profile);
324 }
325
326 for (profile_map_t::iterator it = _usr_profiles.begin();it != _usr_profiles.end();it++) {
327 Profile *profile = (*it).second;
328 if (profile->is_changed()) {
329 DBG(1, form("KCemu/Preferences",
330 "Preferences::visit_changed(): ... system profile '%s' changed\n",
331 profile->get_name()));
332 visitor.handle_profile(profile);
333 }
334 }
335
336 for (profile_map_t::iterator it = _add_profiles.begin();it != _add_profiles.end();it++) {
337 Profile *profile = (*it).second;
338 if (profile->is_changed()) {
339 DBG(1, form("KCemu/Preferences",
340 "Preferences::visit_changed(): ... user profile '%s' changed\n",
341 profile->get_name()));
342 visitor.handle_profile(profile);
343 }
344 }
345
346 DBG(1, form("KCemu/Preferences",
347 "Preferences::visit_changed(): End visiting changed profiles.\n"));
348 }
349
350 void
save(void)351 Preferences::save(void) {
352 DBG(1, form("KCemu/Preferences",
353 "Preferences::save(): Start save profiles:\n"));
354
355 mkdirs(_usr_dir);
356 mkdirs(_add_dir);
357
358 SaveVisitor visitor(this);
359 visit_changed(visitor);
360
361 DBG(1, form("KCemu/Preferences",
362 "Preferences::save(): End save profiles.\n"));
363 }
364
365 void
reject(void)366 Preferences::reject(void) {
367 DBG(1, form("KCemu/Preferences",
368 "Preferences::reject(): Start rejecting changes\n"));
369
370 RejectVisitor visitor(this);
371 visit_changed(visitor);
372
373 DBG(1, form("KCemu/Preferences",
374 "Preferences::reject(): End rejecting changes\n"));
375 }
376
377 bool
mkdirs(string dir)378 Preferences::mkdirs(string dir) {
379 struct stat statbuf;
380
381 if (stat(dir.c_str(), &statbuf) == 0) {
382 if (S_ISDIR(statbuf.st_mode)) {
383 DBG(1, form("KCemu/Preferences",
384 "Preferences::mkdirs(): Directory '%s' exists.\n",
385 dir.c_str()));
386 return true;
387 }
388 }
389
390 DBG(1, form("KCemu/Preferences",
391 "Preferences::mkdirs(): Directory '%s' doesn't exist, trying to create...\n",
392 dir.c_str()));
393
394 StringList dirs(dir, '/');
395
396 string path;
397 for (StringList::const_iterator it = dirs.begin();it != dirs.end();it++) {
398 string entry = (*it);
399
400 if ((path.size() == 0) && (entry.size() == 2) && (entry[1] == ':')) {
401 DBG(1, form("KCemu/Preferences",
402 "Preferences::mkdirs(): Detected windows style path, skipping drive letter '%s'...\n",
403 entry.c_str()));
404 path += entry;
405 continue;
406 }
407
408 path += "/";
409 path += entry;
410
411 if (stat(path.c_str(), &statbuf) == 0) {
412 if (S_ISDIR(statbuf.st_mode)) {
413 DBG(1, form("KCemu/Preferences",
414 "Preferences::mkdirs(): .. Directory '%s' already exists.\n",
415 path.c_str()));
416 continue;
417 } else {
418 DBG(1, form("KCemu/Preferences",
419 "Preferences::mkdirs(): Path '%s' already exists, but it is not a directory. Create failed!\n",
420 path.c_str()));
421 return false;
422 }
423 }
424
425 if (sys_mkdir(path.c_str(), 0700) != 0) {
426 DBG(1, form("KCemu/Preferences",
427 "Preferences::mkdirs(): Could not create directory '%s' (%s)!\n",
428 path.c_str(), strerror(errno)));
429 return false;
430 }
431
432 DBG(1, form("KCemu/Preferences",
433 "Preferences::mkdirs(): .. Created directory '%s'.\n",
434 path.c_str()));
435 }
436
437 DBG(1, form("KCemu/Preferences",
438 "Preferences::mkdirs(): Done.\n"));
439
440 return true;
441 }
442
443 bool
save_profile(Profile * profile)444 Preferences::save_profile(Profile *profile) {
445 dump_profile(profile);
446
447 if (profile->is_deleted()) {
448 DBG(1, form("KCemu/Preferences",
449 "Preferences::save_profile(): Deleting profile '%s'\n",
450 profile->get_name()));
451 if (unlink(profile->get_path()) != 0) {
452 DBG(1, form("KCemu/Preferences",
453 "Preferences::save_profile(): Could not delete file '%s' (%s)!\n",
454 profile->get_path(), strerror(errno)));
455 }
456 if (_add_profiles.erase(profile->get_config_name()) == 1) {
457 delete profile;
458 return true;
459 }
460 return false;
461 }
462
463 ofstream os(profile->get_path(), ios_base::trunc);
464 if (!os) {
465 DBG(1, form("KCemu/Preferences",
466 "Preferences::save_profile(): could not create file '%s'\n",
467 profile->get_path()));
468 return false;
469 }
470
471 os << ProfileParser::signature << endl;
472
473 list<string> *keys = profile->get_keys();
474 for (list<string>::iterator it = keys->begin();it != keys->end();it++) {
475 char *value = profile->get_encoded_value(*it);
476 os << (*it).c_str() << " = " << value << endl;
477 free(value);
478 }
479 profile->set_changed(false);
480
481 return true;
482 }
483
484 void
reject_changes(Profile * profile)485 Preferences::reject_changes(Profile *profile) {
486 profile->reject_changes();
487 }
488
489 void
dump_profile(Profile * profile)490 Preferences::dump_profile(Profile *profile) {
491 DBG(1, form("KCemu/Preferences",
492 "Preferences::dump_profile(): dumping profile '%s' (flags: %c%c)\n",
493 profile->get_name(),
494 profile->is_changed() ? 'C' : 'c',
495 profile->is_deleted() ? 'D' : 'd'));
496
497 string path = get_profile_path(_usr_dir, profile->get_config_name());
498 DBG(1, form("KCemu/Preferences",
499 "Preferences::dump_profile(): path is '%s'\n",
500 path.c_str()));
501
502 list<string> *keys = profile->get_keys();
503 for (list<string>::iterator it = keys->begin();it != keys->end();it++) {
504 char *value = profile->get_encoded_value(*it);
505 DBG(1, form("KCemu/Preferences",
506 "Preferences::dump_profile(): .. %s = %s\n",
507 (*it).c_str(), value));
508 free(value);
509 }
510 DBG(1, form("KCemu/Preferences",
511 "Preferences::dump_profile(): end.\n"));
512 }
513
514 /**
515 * find the profile to select as current profile:
516 *
517 * 1.) check for the given name as config_name in the profile
518 * 2.) check for the given name as display name of the profile
519 * 3.) check for the profile matching the given type
520 * 4.) revert to "default" profile
521 */
522 void
set_current_profile(const char * name,int type)523 Preferences::set_current_profile(const char *name, int type) {
524 Profile *profile = NULL;
525
526 if (name != NULL) {
527 profile = Preferences::instance()->find_profile(name);
528 }
529 if ((profile == NULL) && (name != NULL)) {
530 profile = Preferences::instance()->find_profile_by_name(name);
531 }
532 if ((profile == NULL) && (type >= 0)) {
533 emulation_type_list_t& list = EmulationType::get_emulation_types();
534 find_by_type ft = for_each(list.begin(), list.end(), find_by_type(type));
535 if (ft._result != NULL) {
536 profile = Preferences::instance()->find_profile(ft._result->get_config_name());
537 }
538 }
539 if (profile == NULL) {
540 profile = Preferences::instance()->find_profile(PROFILE_NAME_DEFAULT);
541 }
542
543 if (profile != NULL) {
544 kc_type_t kc_type = (kc_type_t)profile->get_int_value(PROFILE_KEY_SYSTEM, -1);
545 kc_variant_t kc_variant = (kc_variant_t)profile->get_int_value(PROFILE_KEY_VARIANT, 0);
546
547 SystemType *system_type = find_system_type(kc_type, kc_variant);
548 if (system_type == NULL)
549 system_type = find_system_type(kc_type, KC_VARIANT_NONE);
550 if (system_type == NULL)
551 system_type = find_system_type(KC_TYPE_85_4, KC_VARIANT_NONE);
552
553 if (system_type != NULL) {
554 _current_profile = profile;
555 _current_system_type = system_type;
556 return;
557 }
558 }
559
560 abort();
561 }
562
563 SystemType *
find_system_type(kc_type_t kc_type,kc_variant_t kc_variant)564 Preferences::find_system_type(kc_type_t kc_type, kc_variant_t kc_variant) {
565 system_type_list_t& list = SystemInformation::instance()->get_system_types();
566 find_by_type_and_variant f = for_each(list.begin(), list.end(), find_by_type_and_variant(kc_type, kc_variant));
567 return f._result;
568 }
569
570 int
get_int_value(string key,int default_value)571 Preferences::get_int_value(string key, int default_value) {
572 return _current_profile->get_int_value(key, default_value);
573 }
574
575 const char *
get_string_value(string key,const char * default_value)576 Preferences::get_string_value(string key, const char *default_value) {
577 return _current_profile->get_string_value(key, default_value);
578 }
579
580 SystemType *
get_system_type(void)581 Preferences::get_system_type(void) {
582 return _current_system_type;
583 }
584
585 kc_type_t
get_kc_type(void)586 Preferences::get_kc_type(void) {
587 return _current_system_type->get_kc_type();
588 }
589
590 kc_variant_t
get_kc_variant(void)591 Preferences::get_kc_variant(void) {
592 return _current_system_type->get_kc_variant();
593 }
594
595 const char *
get_kc_type_name(void)596 Preferences::get_kc_type_name(void) {
597 return _current_system_type->get_emulation_type().get_name();
598 }
599
600 const char *
get_kc_variant_name(void)601 Preferences::get_kc_variant_name(void) {
602 return _current_system_type->get_kc_variant_name();
603 }
604