new configuration file location algorithm

This commit is contained in:
Christopher A. Mosher 2022-12-09 21:49:59 -05:00
parent 28048d7087
commit ddf97fff28
8 changed files with 217 additions and 125 deletions

View File

@ -221,6 +221,7 @@ if(APPLE)
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
BUNDLE DESTINATION .) BUNDLE DESTINATION .)
configure_file(share/FixBundle.cmake.in ${PROJECT_BINARY_DIR}/FixBundle.cmake @ONLY) configure_file(share/FixBundle.cmake.in ${PROJECT_BINARY_DIR}/FixBundle.cmake @ONLY)
# TODO: how to make resources read-only (on Mac)
install(SCRIPT ${PROJECT_BINARY_DIR}/FixBundle.cmake) install(SCRIPT ${PROJECT_BINARY_DIR}/FixBundle.cmake)
elseif(WIN32) elseif(WIN32)
install(TARGETS ${APP_NAME} install(TARGETS ${APP_NAME}

View File

@ -178,7 +178,7 @@ bool E2wxApp::OnInit() {
this->emu = new Emulator(); this->emu = new Emulator();
Config cfg(this->arg_configfile); Config cfg(this->arg_configfile, this->opt_config_from_prefs_only);
this->emu->config(cfg); this->emu->config(cfg);
this->emu->init(); this->emu->init();
this->emu_timer = new EmuTimer(this->emu); this->emu_timer = new EmuTimer(this->emu);
@ -215,8 +215,8 @@ void E2wxApp::OnFatalException() {
static const wxCmdLineEntryDesc cmdLineDesc[] = static const wxCmdLineEntryDesc cmdLineDesc[] = {
{ { wxCMD_LINE_SWITCH, "p", "prefs", "Read config only from preferences, never an external file.", wxCMD_LINE_VAL_NONE },
{ wxCMD_LINE_PARAM, NULL, NULL, "config-file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, { wxCMD_LINE_PARAM, NULL, NULL, "config-file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
wxCMD_LINE_DESC_END wxCMD_LINE_DESC_END
}; };
@ -231,6 +231,8 @@ bool E2wxApp::OnCmdLineParsed(wxCmdLineParser& parser) {
return false; return false;
} }
this->opt_config_from_prefs_only = parser.Found("p");
const int n = parser.GetParamCount(); const int n = parser.GetParamCount();
if (n <= 0) { if (n <= 0) {

View File

@ -59,6 +59,7 @@ class E2wxApp : public wxApp {
std::filesystem::path confdir; std::filesystem::path confdir;
std::filesystem::path docsdir; std::filesystem::path docsdir;
std::filesystem::path arg_configfile; std::filesystem::path arg_configfile;
bool opt_config_from_prefs_only;
EmuTimer *emu_timer; EmuTimer *emu_timer;
Emulator *emu; Emulator *emu;

View File

@ -134,13 +134,7 @@ void PreferencesDialog::BuildItemTree() {
} }
void PreferencesDialog::OnInit() { void PreferencesDialog::OnInit() {
wxConfigBase *appconf = wxConfigBase::Get(); wxConfigBase::Get()->Read("/ActivePreferences/name", &this->active, "");
if (!appconf->Read("/ActivePreferences/name", &this->active)) {
// TODO what to do when no config?
this->active = "epple2";
appconf->Write("/ActivePreferences/name", this->active);
appconf->Flush();
}
wxXmlResource::Get()->LoadDialog(this, this->parent, "Preferences"); wxXmlResource::Get()->LoadDialog(this, this->parent, "Preferences");
@ -163,6 +157,8 @@ void PreferencesDialog::Save(const std::filesystem::path& to) {
} }
} }
// TODO: enable/disable the various buttons, etc. (currently all are enabled even if not functional)
void PreferencesDialog::OnTreeSelectionChanged(wxTreeEvent& evt) { void PreferencesDialog::OnTreeSelectionChanged(wxTreeEvent& evt) {
// note: we don't get the first select upon dialog creation, // note: we don't get the first select upon dialog creation,
// nor the final de-select upon dialog destruction // nor the final de-select upon dialog destruction

View File

@ -18,7 +18,7 @@
#include "configep2.h" #include "configep2.h"
#include "E2wxApp.h" #include "E2wxApp.h"
#include "e2filesystem.h"
#include "apple2.h" #include "apple2.h"
#include "memory.h" #include "memory.h"
#include "memoryrandomaccess.h" #include "memoryrandomaccess.h"
@ -36,6 +36,8 @@
#include <wx/config.h> #include <wx/config.h>
#include <wx/string.h> #include <wx/string.h>
#include <boost/log/trivial.hpp>
#include <iostream> #include <iostream>
#include <istream> #include <istream>
#include <fstream> #include <fstream>
@ -47,6 +49,10 @@
static const wxString DEFAULT_CONFIG_NAME{"epple2"};
#define K 1024u #define K 1024u
static std::uint16_t memory_block_size(const std::string &block_size) { static std::uint16_t memory_block_size(const std::string &block_size) {
@ -61,15 +67,11 @@ static std::uint16_t memory_block_size(const std::string &block_size) {
unsigned char Config::disk_mask(0);
Config::Config(const std::filesystem::path& f): Config::Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} {
file_path(f)
{
} }
Config::~Config() Config::~Config() {
{
} }
static void strip_comment(std::string& str) static void strip_comment(std::string& str)
@ -99,140 +101,211 @@ static void trim(std::string& str)
} }
} }
void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2)
{
std::ifstream* pConfig;
std::filesystem::path path(this->file_path);
if (!path.empty()) /*
{ * Searches for config file with the given name in the preferences areas.
pConfig = new std::ifstream(path); * The name must not be a filesystem path.
if (!pConfig->is_open()) * The actual config files themselves have ".conf" extension.
{ * The preferences areas are searched in this order:
// TODO use filename only and look in standard resources * user area
std::stringstream ss; * built-in area
ss << "Cannot open config file " << this->file_path; *
throw std::runtime_error(ss.str()); * If successful, returns an open stream, caller is responsible for deleting it
* Otherwise, returns null
*/
std::ifstream *Config::openFilePref(const wxString& s_name) {
std::ifstream *ret = nullptr;
BOOST_LOG_TRIVIAL(info) << "config file name was specified as: " << s_name;
std::filesystem::path path_name = path_from_string(s_name);
if (path_name.has_parent_path()) {
BOOST_LOG_TRIVIAL(error) << "invalid name for config file (paths are not allowed): " << path_name.c_str();
return ret;
}
if (path_name.empty()) {
BOOST_LOG_TRIVIAL(error) << "invalid: empty name for config file";
return ret;
}
// required file extension for any file to be recognized as a config file
path_name = path_from_string(s_name+".conf");
std::filesystem::path path;
path = valid_input_file(path_name, wxGetApp().GetConfigDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in user area of preferences
} }
delete ret;
ret = nullptr;
}
path = valid_input_file(path_name, wxGetApp().GetResDir());
if (!path.empty()) {
ret = new std::ifstream(path);
if (ret->is_open()) {
return ret; // Found specified config file in built-in area of preferences
}
delete ret;
ret = nullptr;
} }
if (path.empty()) return ret;
{ }
// TODO config file location, how to be backwardly compatible?
wxString user_config;
if (!wxConfigBase::Get()->Read("/ActivePreferences/name", &user_config)) {
// TODO what to do when no config?
user_config = "epple2";
}
user_config += ".conf";
std::filesystem::path user_path{user_config.wc_str()};
path = wxGetApp().GetConfigDir() / user_path; std::ifstream *Config::openFileExternal(const std::filesystem::path& path) {
std::cout << "looking for config file: " << path << std::endl; std::ifstream *ret = nullptr;
pConfig = new std::ifstream(path);
if (!pConfig->is_open()) { const std::filesystem::path p = valid_input_file(path);
path = wxGetApp().GetResDir() / user_path; if (!p.empty()) {
std::cout << "looking for config file: " << path << std::endl; ret = new std::ifstream(p);
pConfig = new std::ifstream(path); if (ret->is_open()) {
if (!pConfig->is_open()) { return ret;
path.clear(); }
delete ret;
ret = nullptr;
}
return ret;
}
static const std::array rs_path_legacy{
"./etc/epple2/epple2.conf",
ETCDIR "/epple2/epple2.conf",
"/etc/epple2/epple2.conf",
"/etc/epple2.conf",
"./epple2.conf",
};
std::ifstream *Config::openFileLegacy() {
std::ifstream *ret = nullptr;
for (const auto &s_path_legacy : rs_path_legacy) {
if ((ret = openFileExternal(std::filesystem::path{s_path_legacy})) != nullptr) {
return ret;
}
}
return ret;
}
/*
* Algorithm to locate and open the configuration file, as specified by
* the user, either on command line, or via preferences, allowing for
* backward compatibility with legacy file locations.
*/
std::ifstream *Config::openFile() {
std::ifstream *ret = nullptr;
if (this->file_path.empty()) {
wxString cname{};
const bool stored_prefs_found = wxConfigBase::Get()->Read("/ActivePreferences/name", &cname, DEFAULT_CONFIG_NAME);
if (stored_prefs_found) {
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
}
if (!this->prefs_only) {
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
} else {
if (!this->prefs_only) {
ret = openFileLegacy();
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(cname);
if (ret != nullptr) {
return ret;
} }
} }
} else {
if (!this->prefs_only) {
ret = openFileExternal(this->file_path);
if (ret != nullptr) {
return ret;
}
}
ret = openFilePref(this->file_path.c_str());
if (ret != nullptr) {
return ret;
}
} }
return ret;
}
void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
std::ifstream *p_ifstream_config = openFile();
if (path.empty()) if (p_ifstream_config == nullptr) {
{
std::cout << "standard config file location: " ETCDIR "/epple2/epple2.conf" << std::endl;
/*
On Windows, the default directory will be
C:\Program Files\Epple2 if they start the
program from the Start Menu; therefore
etc/epple2/epple2.conf would be
C:\Program Files\epple2\etc\epple2\epple2.conf
On Linux... the current directory could be
anything, so this probably won't find it (unless
the current directory is /).
*/
path = "etc/epple2/epple2.conf";
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
This is primarily for Linux. If configured for
a PREFIX of "/usr/local", then this would be
/usr/local/etc/epple2/epple2.conf
*/
path = ETCDIR "/epple2/epple2.conf";
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
Try a likely linux location
*/
path = "/etc/epple2/epple2.conf";
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
Try another likely linux location
*/
path = "/etc/epple2.conf";
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
Last effort to find it.
*/
path = "epple2.conf";
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl; std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl;
return; return;
} }
std::cout << "Reading configuration from file: " << path << std::endl;
std::string line; std::string line;
std::getline(*pConfig,line); std::getline(*p_ifstream_config, line);
while (!pConfig->eof()) while (!p_ifstream_config->eof()) {
{
strip_comment(line); strip_comment(line);
trim(line); trim(line);
if (!line.empty()) if (!line.empty()) {
{ // TODO "parseLine" will become Command::execute, or similar
parseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut,apple2); parseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
} }
std::getline(*pConfig,line); std::getline(*p_ifstream_config, line);
} }
pConfig->close();
delete pConfig; delete p_ifstream_config;
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card // TODO: make sure there is no more than ONE stdin and/or ONE stdout card
} }
void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2)
{ {
try try
@ -519,6 +592,12 @@ void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memo
} }
} }
unsigned char Config::disk_mask(0);
void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib) void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib)
{ {
if (drive < 1 || 2 < drive) if (drive < 1 || 2 < drive)

View File

@ -18,6 +18,7 @@
#ifndef CONFIGEP2_H #ifndef CONFIGEP2_H
#define CONFIGEP2_H #define CONFIGEP2_H
#include <wx/string.h>
#include <filesystem> #include <filesystem>
#include <string> #include <string>
class Memory; class Memory;
@ -34,11 +35,19 @@ public:
ConfigException(const std::string& msg) : msg(msg) {} ConfigException(const std::string& msg) : msg(msg) {}
}; };
// TODO split out all static things into their own class (and don't make them static)
// Remember that, besides config, also command line entry calls parseLine
// This will also help with adding menu items in place of commands
class Config { class Config {
private: private:
const std::filesystem::path file_path; const std::filesystem::path file_path;
const bool prefs_only;
static unsigned char disk_mask; static unsigned char disk_mask;
std::ifstream *openFile();
std::ifstream *openFilePref(const wxString& s_name);
std::ifstream *openFileExternal(const std::filesystem::path& path);
std::ifstream *openFileLegacy();
static void loadDisk(Slots& slts, int slot, int drive, const std::string& fnib); static void loadDisk(Slots& slts, int slot, int drive, const std::string& fnib);
static void unloadDisk(Slots& slts, int slot, int drive); static void unloadDisk(Slots& slts, int slot, int drive);
static void saveDisk(Slots& slts, int slot, int drive); static void saveDisk(Slots& slts, int slot, int drive);
@ -46,7 +55,7 @@ private:
static void tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2); static void tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);
public: public:
Config(const std::filesystem::path& f); Config(const std::filesystem::path& f, bool p);
~Config(); ~Config();
void parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2); void parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2);

View File

@ -30,7 +30,7 @@
#include <filesystem> #include <filesystem>
std::filesystem::path valid_input_file(const std::filesystem::path path, const std::filesystem::path base); std::filesystem::path valid_input_file(const std::filesystem::path path, const std::filesystem::path base = std::filesystem::path{});
std::filesystem::path path_from_string(const wxString& s); std::filesystem::path path_from_string(const wxString& s);
#endif /* E2FILESYSTEM_H */ #endif /* E2FILESYSTEM_H */

View File

@ -116,6 +116,10 @@ bool WozFile::load(const std::filesystem::path& orig_file) {
printf("Reading WOZ 2.0 file: %s\n", orig_file.c_str()); printf("Reading WOZ 2.0 file: %s\n", orig_file.c_str());
std::filesystem::path filePath = valid_input_file(orig_file, wxGetApp().GetResDir()); std::filesystem::path filePath = valid_input_file(orig_file, wxGetApp().GetResDir());
if (filePath.empty()) {
printf("Error opening WOZ file.\n");
return false;
}
std::ifstream *in = new std::ifstream(filePath, std::ios::binary|std::ios::in); std::ifstream *in = new std::ifstream(filePath, std::ios::binary|std::ios::in);
if (!in->is_open()) { if (!in->is_open()) {
printf("Error opening file: %d\n", errno); printf("Error opening file: %d\n", errno);