Begin the process of converting all file open/close operations to be done in FSNode.

This will eventually allow ZIP files (and any other compression scheme we use in the future)
to read and write as if they were normal files.  Basically an implementation of a mini-VFS.
This commit is contained in:
Stephen Anthony 2020-07-16 21:20:50 -02:30
parent 2a6c493f6f
commit 25913b791e
10 changed files with 179 additions and 76 deletions

View File

@ -181,7 +181,7 @@ size_t FilesystemNodeZIP::read(ByteBuffer& image) const
while(myZipHandler->hasNext() && !found) while(myZipHandler->hasNext() && !found)
found = myZipHandler->next() == _virtualPath; found = myZipHandler->next() == _virtualPath;
return found ? uInt32(myZipHandler->decompress(image)) : 0; // TODO: 64bit return found ? myZipHandler->decompress(image) : 0;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -197,6 +197,20 @@ size_t FilesystemNodeZIP::read(stringstream& image) const
return size; return size;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNodeZIP::write(const ByteBuffer& buffer, size_t size) const
{
// TODO: Not yet implemented
throw runtime_error("ZIP file not writable");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNodeZIP::write(const stringstream& buffer) const
{
// TODO: Not yet implemented
throw runtime_error("ZIP file not writable");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AbstractFSNodePtr FilesystemNodeZIP::getParent() const AbstractFSNodePtr FilesystemNodeZIP::getParent() const
{ {

View File

@ -64,6 +64,8 @@ class FilesystemNodeZIP : public AbstractFSNode
size_t read(ByteBuffer& image) const override; size_t read(ByteBuffer& image) const override;
size_t read(stringstream& image) const override; size_t read(stringstream& image) const override;
size_t write(const ByteBuffer& buffer, size_t size) const override;
size_t write(const stringstream& buffer) const override;
private: private:
FilesystemNodeZIP(const string& zipfile, const string& virtualpath, FilesystemNodeZIP(const string& zipfile, const string& virtualpath,

View File

@ -1347,8 +1347,7 @@ string CartDebug::saveRom()
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26"; const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26";
FilesystemNode node(myOSystem.defaultSaveDir() + rom); FilesystemNode node(myOSystem.defaultSaveDir() + rom);
ofstream out(node.getPath(), std::ios::binary); if(myConsole.cartridge().saveROM(node))
if(out && myConsole.cartridge().saveROM(out))
return "saved ROM as " + node.getShortPath(); return "saved ROM as " + node.getShortPath();
else else
return DebuggerParser::red("failed to save ROM"); return DebuggerParser::red("failed to save ROM");

View File

@ -15,6 +15,7 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================ //============================================================================
#include "FSNode.hxx"
#include "Settings.hxx" #include "Settings.hxx"
#include "System.hxx" #include "System.hxx"
#include "MD5.hxx" #include "MD5.hxx"
@ -52,19 +53,24 @@ void Cartridge::setAbout(const string& about, const string& type,
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge::saveROM(ofstream& out) const bool Cartridge::saveROM(const FilesystemNode& out) const
{ {
size_t size = 0; try
{
const ByteBuffer& image = getImage(size); size_t size = 0;
if(size == 0) const ByteBuffer& image = getImage(size);
if(size == 0)
{
cerr << "save not supported" << endl;
return false;
}
out.write(image, size);
}
catch(...)
{ {
cerr << "save not supported" << endl;
return false; return false;
} }
out.write(reinterpret_cast<const char*>(image.get()), size);
return true; return true;
} }

View File

@ -20,6 +20,7 @@
class Cartridge; class Cartridge;
class Properties; class Properties;
class FilesystemNode;
class CartDebugWidget; class CartDebugWidget;
class CartRamWidget; class CartRamWidget;
class GuiObject; class GuiObject;
@ -72,9 +73,9 @@ class Cartridge : public Device
/** /**
Save the internal (patched) ROM image. Save the internal (patched) ROM image.
@param out The output file stream to save the image @param out The output file to save the image
*/ */
bool saveROM(ofstream& out) const; bool saveROM(const FilesystemNode& out) const;
/** /**
Lock/unlock bankswitching capability. The debugger will lock Lock/unlock bankswitching capability. The debugger will lock

View File

@ -13,12 +13,8 @@
// //
// See the file "License.txt" for information on usage and redistribution of // See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// Based on code from ScummVM - Scumm Interpreter
// Copyright (C) 2002-2004 The ScummVM project
//============================================================================ //============================================================================
#include "Cart.hxx"
#include "FSNodeFactory.hxx" #include "FSNodeFactory.hxx"
#include "FSNode.hxx" #include "FSNode.hxx"
@ -227,49 +223,48 @@ bool FilesystemNode::rename(const string& newfile)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::read(ByteBuffer& buffer) const size_t FilesystemNode::read(ByteBuffer& buffer) const
{ {
size_t size = 0; size_t sizeRead = 0;
// File must actually exist // File must actually exist
if (!(exists() && isReadable())) if (!(exists() && isReadable()))
throw runtime_error("File not found/readable"); throw runtime_error("File not found/readable");
// First let the private subclass attempt to open the file // First let the private subclass attempt to open the file
if (_realNode && (size = _realNode->read(buffer)) > 0) if (_realNode && (sizeRead = _realNode->read(buffer)) > 0)
return size; return sizeRead;
// Otherwise, the default behaviour is to read from a normal C++ ifstream // Otherwise, the default behaviour is to read from a normal C++ ifstream
buffer = make_unique<uInt8[]>(Cartridge::maxSize());
ifstream in(getPath(), std::ios::binary); ifstream in(getPath(), std::ios::binary);
if (in) if (in)
{ {
in.seekg(0, std::ios::end); in.seekg(0, std::ios::end);
std::streampos length = in.tellg(); sizeRead = static_cast<size_t>(in.tellg());
in.seekg(0, std::ios::beg); in.seekg(0, std::ios::beg);
if (length == 0) if (sizeRead == 0)
throw runtime_error("Zero-byte file"); throw runtime_error("Zero-byte file");
size = std::min<size_t>(length, Cartridge::maxSize()); buffer = make_unique<uInt8[]>(sizeRead);
in.read(reinterpret_cast<char*>(buffer.get()), size); in.read(reinterpret_cast<char*>(buffer.get()), sizeRead);
} }
else else
throw runtime_error("File open/read error"); throw runtime_error("File open/read error");
return size; return sizeRead;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::read(stringstream& buffer) const size_t FilesystemNode::read(stringstream& buffer) const
{ {
size_t size = 0; size_t sizeRead = 0;
// File must actually exist // File must actually exist
if (!(exists() && isReadable())) if (!(exists() && isReadable()))
throw runtime_error("File not found/readable"); throw runtime_error("File not found/readable");
// First let the private subclass attempt to open the file // First let the private subclass attempt to open the file
if (_realNode && (size = _realNode->read(buffer)) > 0) if (_realNode && (sizeRead = _realNode->read(buffer)) > 0)
return size; return sizeRead;
// Otherwise, the default behaviour is to read from a normal C++ ifstream // Otherwise, the default behaviour is to read from a normal C++ ifstream
// and convert to a stringstream // and convert to a stringstream
@ -277,17 +272,66 @@ size_t FilesystemNode::read(stringstream& buffer) const
if (in) if (in)
{ {
in.seekg(0, std::ios::end); in.seekg(0, std::ios::end);
std::streampos length = in.tellg(); sizeRead = static_cast<size_t>(in.tellg());
in.seekg(0, std::ios::beg); in.seekg(0, std::ios::beg);
if (length == 0) if (sizeRead == 0)
throw runtime_error("Zero-byte file"); throw runtime_error("Zero-byte file");
size = std::min<size_t>(length, Cartridge::maxSize());
buffer << in.rdbuf(); buffer << in.rdbuf();
} }
else else
throw runtime_error("File open/read error"); throw runtime_error("File open/read error");
return size; return sizeRead;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::write(const ByteBuffer& buffer, size_t size) const
{
size_t sizeWritten = 0;
// First let the private subclass attempt to open the file
if (_realNode && (sizeWritten = _realNode->write(buffer, size)) > 0)
return sizeWritten;
// Otherwise, the default behaviour is to write to a normal C++ ofstream
ofstream out(getPath(), std::ios::binary);
if (out)
{
out.write(reinterpret_cast<const char*>(buffer.get()), size);
out.seekp(0, std::ios::end);
sizeWritten = static_cast<size_t>(out.tellp());
out.seekp(0, std::ios::beg);
}
else
throw runtime_error("File open/write error");
return sizeWritten;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::write(const stringstream& buffer) const
{
size_t sizeWritten = 0;
// First let the private subclass attempt to open the file
if (_realNode && (sizeWritten = _realNode->write(buffer)) > 0)
return sizeWritten;
// Otherwise, the default behaviour is to write to a normal C++ ofstream
ofstream out(getPath(), std::ios::binary);
if (out)
{
out << buffer.rdbuf();
out.seekp(0, std::ios::end);
sizeWritten = static_cast<size_t>(out.tellp());
out.seekp(0, std::ios::beg);
}
else
throw runtime_error("File open/write error");
return sizeWritten;
} }

View File

@ -13,42 +13,24 @@
// //
// See the file "License.txt" for information on usage and redistribution of // See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// Based on code from ScummVM - Scumm Interpreter
// Copyright (C) 2002-2004 The ScummVM project
//============================================================================ //============================================================================
#ifndef FS_NODE_HXX #ifndef FS_NODE_HXX
#define FS_NODE_HXX #define FS_NODE_HXX
#include <functional>
#include "bspf.hxx" #include "bspf.hxx"
/* /*
* The API described in this header is meant to allow for file system browsing in a * The API described in this header is meant to allow for file system browsing in a
* portable fashions. To this ends, multiple or single roots have to be supported * portable fashion. To this end, multiple or single roots have to be supported
* (compare Unix with a single root, Windows with multiple roots C:, D:, ...). * (compare Unix with a single root, Windows with multiple roots C:, D:, ...).
* *
* To this end, we abstract away from paths; implementations can be based on * To this end, we abstract away from paths; implementations can be based on
* paths (and it's left to them whether / or \ or : is the path separator :-); * paths (and it's left to them whether / or \ or : is the path separator :-).
* but it is also possible to use inodes or vrefs (MacOS 9) or anything else.
*
* You may ask now: "isn't this cheating? Why do we go through all this when we use
* a path in the end anyway?!?".
* Well, for once as long as we don't provide our own file open/read/write API, we
* still have to use fopen(). Since all our targets already support fopen(), it should
* be possible to get a fopen() compatible string for any file system node.
*
* Secondly, with this abstraction layer, we still avoid a lot of complications based on
* differences in FS roots, different path separators, or even systems with no real
* paths (MacOS 9 doesn't even have the notion of a "current directory").
* And if we ever want to support devices with no FS in the classical sense (Palm...),
* we can build upon this.
*/ */
#include <functional>
#include "bspf.hxx"
class FilesystemNode; class FilesystemNode;
class AbstractFSNode; class AbstractFSNode;
using AbstractFSNodePtr = shared_ptr<AbstractFSNode>; using AbstractFSNodePtr = shared_ptr<AbstractFSNode>;
@ -233,7 +215,7 @@ class FilesystemNode
/** /**
* Read data (binary format) into the given buffer. * Read data (binary format) into the given buffer.
* *
* @param buffer The buffer to contain the data. * @param buffer The buffer to contain the data (allocated in this method).
* *
* @return The number of bytes read (0 in the case of failure) * @return The number of bytes read (0 in the case of failure)
* This method can throw exceptions, and should be used inside * This method can throw exceptions, and should be used inside
@ -252,6 +234,29 @@ class FilesystemNode
*/ */
size_t read(stringstream& buffer) const; size_t read(stringstream& buffer) const;
/**
* Write data (binary format) from the given buffer.
*
* @param buffer The buffer that contains the data.
* @param size The size of the buffer.
*
* @return The number of bytes written (0 in the case of failure)
* This method can throw exceptions, and should be used inside
* a try-catch block.
*/
size_t write(const ByteBuffer& buffer, size_t size) const;
/**
* Write data (text format) from the given stream.
*
* @param buffer The buffer stream that contains the data.
*
* @return The number of bytes written (0 in the case of failure)
* This method can throw exceptions, and should be used inside
* a try-catch block.
*/
size_t write(const stringstream& buffer) const;
/** /**
* The following methods are almost exactly the same as the various * The following methods are almost exactly the same as the various
* getXXXX() methods above. Internally, they call the respective methods * getXXXX() methods above. Internally, they call the respective methods
@ -403,9 +408,8 @@ class AbstractFSNode
/** /**
* Read data (binary format) into the given buffer. * Read data (binary format) into the given buffer.
* *
* @param buffer The buffer to containing the data * @param buffer The buffer to contain the data (allocated in this method).
* This will be allocated by the method, and must be *
* freed by the caller.
* @return The number of bytes read (0 in the case of failure) * @return The number of bytes read (0 in the case of failure)
* This method can throw exceptions, and should be used inside * This method can throw exceptions, and should be used inside
* a try-catch block. * a try-catch block.
@ -413,15 +417,38 @@ class AbstractFSNode
virtual size_t read(ByteBuffer& buffer) const { return 0; } virtual size_t read(ByteBuffer& buffer) const { return 0; }
/** /**
* Read data (text format) into the given steam. * Read data (text format) into the given stream.
* *
* @param buffer The buffer stream to containing the data * @param buffer The buffer stream to contain the data.
* *
* @return The number of bytes read (0 in the case of failure) * @return The number of bytes read (0 in the case of failure)
* This method can throw exceptions, and should be used inside * This method can throw exceptions, and should be used inside
* a try-catch block. * a try-catch block.
*/ */
virtual size_t read(stringstream& buffer) const { return 0; } virtual size_t read(stringstream& buffer) const { return 0; }
/**
* Write data (binary format) from the given buffer.
*
* @param buffer The buffer that contains the data.
* @param size The size of the buffer.
*
* @return The number of bytes written (0 in the case of failure)
* This method can throw exceptions, and should be used inside
* a try-catch block.
*/
virtual size_t write(const ByteBuffer& buffer, size_t size) const { return 0; }
/**
* Write data (text format) from the given stream.
*
* @param buffer The buffer stream that contains the data.
*
* @return The number of bytes written (0 in the case of failure)
* This method can throw exceptions, and should be used inside
* a try-catch block.
*/
virtual size_t write(const stringstream& buffer) const { return 0; }
}; };
#endif #endif

View File

@ -251,7 +251,7 @@ void OSystem::saveConfig()
Logger::debug("Saving config options ..."); Logger::debug("Saving config options ...");
mySettings->save(); mySettings->save();
if(myPropSet && myPropSet->save(myPropertiesFile.getPath())) if(myPropSet && myPropSet->save(myPropertiesFile))
Logger::debug("Saving properties set ..."); Logger::debug("Saving properties set ...");
} }

View File

@ -19,6 +19,7 @@
#include "bspf.hxx" #include "bspf.hxx"
#include "FSNode.hxx" #include "FSNode.hxx"
#include "Logger.hxx"
#include "DefProps.hxx" #include "DefProps.hxx"
#include "Props.hxx" #include "Props.hxx"
#include "PropsSet.hxx" #include "PropsSet.hxx"
@ -42,22 +43,31 @@ void PropertiesSet::load(const FilesystemNode& file, bool save)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PropertiesSet::save(const string& filename) const bool PropertiesSet::save(const FilesystemNode& file) const
{ {
// Only save properties when it won't create an empty file try
FilesystemNode props(filename); {
if(!props.exists() && myExternalProps.size() == 0) // Only save properties when it won't create an empty file
return false; if(!file.exists() && myExternalProps.size() == 0)
return false;
ofstream out(filename); // Only save those entries in the external list
if(!out) stringstream out;
return false; for(const auto& i: myExternalProps)
out << i.second;
// Only save those entries in the external list file.write(out);
for(const auto& i: myExternalProps) return true;
out << i.second; }
catch(const runtime_error& e)
{
Logger::error(e.what());
}
catch(...)
{
}
return true; return false;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -57,14 +57,14 @@ class PropertiesSet
/** /**
Save properties to the specified file. Save properties to the specified file.
@param filename Full pathname of output file to use @param file The node representing the output file to use
@return True on success, false on failure or save not needed @return True on success, false on failure or save not needed
Failure occurs if file couldn't be opened for writing, Failure occurs if file couldn't be opened for writing,
or if the file doesn't exist and a zero-byte file or if the file doesn't exist and a zero-byte file
would be created would be created
*/ */
bool save(const string& filename) const; bool save(const FilesystemNode& file) const;
/** /**
Get the property from the set with the given MD5. Get the property from the set with the given MD5.