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)
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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
{

View File

@ -64,6 +64,8 @@ class FilesystemNodeZIP : public AbstractFSNode
size_t read(ByteBuffer& 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:
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";
FilesystemNode node(myOSystem.defaultSaveDir() + rom);
ofstream out(node.getPath(), std::ios::binary);
if(out && myConsole.cartridge().saveROM(out))
if(myConsole.cartridge().saveROM(node))
return "saved ROM as " + node.getShortPath();
else
return DebuggerParser::red("failed to save ROM");

View File

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

View File

@ -20,6 +20,7 @@
class Cartridge;
class Properties;
class FilesystemNode;
class CartDebugWidget;
class CartRamWidget;
class GuiObject;
@ -72,9 +73,9 @@ class Cartridge : public Device
/**
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

View File

@ -13,12 +13,8 @@
//
// See the file "License.txt" for information on usage and redistribution of
// 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 "FSNode.hxx"
@ -227,49 +223,48 @@ bool FilesystemNode::rename(const string& newfile)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::read(ByteBuffer& buffer) const
{
size_t size = 0;
size_t sizeRead = 0;
// File must actually exist
if (!(exists() && isReadable()))
throw runtime_error("File not found/readable");
// First let the private subclass attempt to open the file
if (_realNode && (size = _realNode->read(buffer)) > 0)
return size;
if (_realNode && (sizeRead = _realNode->read(buffer)) > 0)
return sizeRead;
// Otherwise, the default behaviour is to read from a normal C++ ifstream
buffer = make_unique<uInt8[]>(Cartridge::maxSize());
ifstream in(getPath(), std::ios::binary);
if (in)
{
in.seekg(0, std::ios::end);
std::streampos length = in.tellg();
sizeRead = static_cast<size_t>(in.tellg());
in.seekg(0, std::ios::beg);
if (length == 0)
if (sizeRead == 0)
throw runtime_error("Zero-byte file");
size = std::min<size_t>(length, Cartridge::maxSize());
in.read(reinterpret_cast<char*>(buffer.get()), size);
buffer = make_unique<uInt8[]>(sizeRead);
in.read(reinterpret_cast<char*>(buffer.get()), sizeRead);
}
else
throw runtime_error("File open/read error");
return size;
return sizeRead;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNode::read(stringstream& buffer) const
{
size_t size = 0;
size_t sizeRead = 0;
// File must actually exist
if (!(exists() && isReadable()))
throw runtime_error("File not found/readable");
// First let the private subclass attempt to open the file
if (_realNode && (size = _realNode->read(buffer)) > 0)
return size;
if (_realNode && (sizeRead = _realNode->read(buffer)) > 0)
return sizeRead;
// Otherwise, the default behaviour is to read from a normal C++ ifstream
// and convert to a stringstream
@ -277,17 +272,66 @@ size_t FilesystemNode::read(stringstream& buffer) const
if (in)
{
in.seekg(0, std::ios::end);
std::streampos length = in.tellg();
sizeRead = static_cast<size_t>(in.tellg());
in.seekg(0, std::ios::beg);
if (length == 0)
if (sizeRead == 0)
throw runtime_error("Zero-byte file");
size = std::min<size_t>(length, Cartridge::maxSize());
buffer << in.rdbuf();
}
else
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
// 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
#define FS_NODE_HXX
#include <functional>
#include "bspf.hxx"
/*
* 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:, ...).
*
* 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 :-);
* 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.
* paths (and it's left to them whether / or \ or : is the path separator :-).
*/
#include <functional>
#include "bspf.hxx"
class FilesystemNode;
class AbstractFSNode;
using AbstractFSNodePtr = shared_ptr<AbstractFSNode>;
@ -233,7 +215,7 @@ class FilesystemNode
/**
* 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)
* This method can throw exceptions, and should be used inside
@ -252,6 +234,29 @@ class FilesystemNode
*/
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
* getXXXX() methods above. Internally, they call the respective methods
@ -403,9 +408,8 @@ class AbstractFSNode
/**
* Read data (binary format) into the given buffer.
*
* @param buffer The buffer to containing the data
* This will be allocated by the method, and must be
* freed by the caller.
* @param buffer The buffer to contain the data (allocated in this method).
*
* @return The number of bytes read (0 in the case of failure)
* This method can throw exceptions, and should be used inside
* a try-catch block.
@ -413,15 +417,38 @@ class AbstractFSNode
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)
* This method can throw exceptions, and should be used inside
* a try-catch block.
*/
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

View File

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

View File

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

View File

@ -57,14 +57,14 @@ class PropertiesSet
/**
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
Failure occurs if file couldn't be opened for writing,
or if the file doesn't exist and a zero-byte file
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.