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