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}
BUNDLE DESTINATION .)
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)
elseif(WIN32)
install(TARGETS ${APP_NAME}

View File

@ -178,7 +178,7 @@ bool E2wxApp::OnInit() {
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->init();
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_DESC_END
};
@ -231,6 +231,8 @@ bool E2wxApp::OnCmdLineParsed(wxCmdLineParser& parser) {
return false;
}
this->opt_config_from_prefs_only = parser.Found("p");
const int n = parser.GetParamCount();
if (n <= 0) {

View File

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

View File

@ -134,13 +134,7 @@ void PreferencesDialog::BuildItemTree() {
}
void PreferencesDialog::OnInit() {
wxConfigBase *appconf = wxConfigBase::Get();
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();
}
wxConfigBase::Get()->Read("/ActivePreferences/name", &this->active, "");
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) {
// note: we don't get the first select upon dialog creation,
// nor the final de-select upon dialog destruction

View File

@ -18,7 +18,7 @@
#include "configep2.h"
#include "E2wxApp.h"
#include "e2filesystem.h"
#include "apple2.h"
#include "memory.h"
#include "memoryrandomaccess.h"
@ -36,6 +36,8 @@
#include <wx/config.h>
#include <wx/string.h>
#include <boost/log/trivial.hpp>
#include <iostream>
#include <istream>
#include <fstream>
@ -47,6 +49,10 @@
static const wxString DEFAULT_CONFIG_NAME{"epple2"};
#define K 1024u
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):
file_path(f)
{
Config::Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} {
}
Config::~Config()
{
Config::~Config() {
}
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())
{
pConfig = new std::ifstream(path);
if (!pConfig->is_open())
{
// TODO use filename only and look in standard resources
std::stringstream ss;
ss << "Cannot open config file " << this->file_path;
throw std::runtime_error(ss.str());
/*
* Searches for config file with the given name in the preferences areas.
* The name must not be a filesystem path.
* The actual config files themselves have ".conf" extension.
* The preferences areas are searched in this order:
* user area
* built-in area
*
* 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())
{
// 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()};
return ret;
}
path = wxGetApp().GetConfigDir() / user_path;
std::cout << "looking for config file: " << path << std::endl;
pConfig = new std::ifstream(path);
if (!pConfig->is_open()) {
path = wxGetApp().GetResDir() / user_path;
std::cout << "looking for config file: " << path << std::endl;
pConfig = new std::ifstream(path);
if (!pConfig->is_open()) {
path.clear();
std::ifstream *Config::openFileExternal(const std::filesystem::path& path) {
std::ifstream *ret = nullptr;
const std::filesystem::path p = valid_input_file(path);
if (!p.empty()) {
ret = new std::ifstream(p);
if (ret->is_open()) {
return ret;
}
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())
{
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())
{
if (p_ifstream_config == nullptr) {
std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl;
return;
}
std::cout << "Reading configuration from file: " << path << std::endl;
std::string line;
std::getline(*pConfig,line);
while (!pConfig->eof())
{
std::getline(*p_ifstream_config, line);
while (!p_ifstream_config->eof()) {
strip_comment(line);
trim(line);
if (!line.empty())
{
parseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut,apple2);
if (!line.empty()) {
// TODO "parseLine" will become Command::execute, or similar
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
}
void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2)
{
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)
{
if (drive < 1 || 2 < drive)

View File

@ -18,6 +18,7 @@
#ifndef CONFIGEP2_H
#define CONFIGEP2_H
#include <wx/string.h>
#include <filesystem>
#include <string>
class Memory;
@ -34,11 +35,19 @@ public:
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 {
private:
const std::filesystem::path file_path;
const bool prefs_only;
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 unloadDisk(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);
public:
Config(const std::filesystem::path& f);
Config(const std::filesystem::path& f, bool p);
~Config();
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>
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);
#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());
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);
if (!in->is_open()) {
printf("Error opening file: %d\n", errno);