libMeatloaf/lib/meatloaf/meat_io.cpp
2024-01-24 13:43:53 -05:00

602 lines
16 KiB
C++

#include "meat_io.h"
#include <dirent.h>
#include <sys/stat.h>
#include <algorithm>
#include <vector>
#include <sstream>
#include "../../include/debug.h"
#include "MIOException.h"
#include "string_utils.h"
#include "peoples_url_parser.h"
//#include "wrappers/directory_stream.h"
// Archive
// Cartridge
// Container
#include "container/d8b.h"
#include "container/dfi.h"
// Device
#include "device/flash.h"
#include "device/sd.h"
// Disk
#include "disk/d64.h"
#include "disk/d71.h"
#include "disk/d80.h"
#include "disk/d81.h"
#include "disk/d82.h"
#include "disk/d90.h"
#include "disk/dnp.h"
// File
#include "file/p00.h"
#include "archive/archive_ml.h"
// Network
#include "network/http.h"
#include "network/tnfs.h"
// #include "network/ipfs.h"
// #include "network/tnfs.h"
// #include "network/smb.h"
// #include "network/ws.h"
// Service
// #include "service/cs.h"
#include "service/ml.h"
// Tape
#include "tape/t64.h"
#include "tape/tcrt.h"
#include "meat_buffer.h"
/********************************************************
* MFSOwner implementations
********************************************************/
// initialize other filesystems here
FlashFileSystem defaultFS;
#ifdef SD_CARD
SDFileSystem sdFS;
#endif
// Scheme
// HttpFileSystem httpFS;
// MLFileSystem mlFS;
// TNFSFileSystem tnfsFS;
// IPFSFileSystem ipfsFS;
// TNFSFileSystem tnfsFS;
// CServerFileSystem csFS;
// TcpFileSystem tcpFS;
//WSFileSystem wsFS;
// File
P00FileSystem p00FS;
//ArchiveContainerFileSystem archiveFS;
// Disk
D64FileSystem d64FS;
D71FileSystem d71FS;
D80FileSystem d80FS;
D81FileSystem d81FS;
D82FileSystem d82FS;
D90FileSystem d90FS;
DNPFileSystem dnpFS;
D8BFileSystem d8bFS;
DFIFileSystem dfiFS;
// Tape
T64FileSystem t64FS;
TCRTFileSystem tcrtFS;
// Cartridge
// put all available filesystems in this array - first matching system gets the file!
// fist in list is default
std::vector<MFileSystem*> MFSOwner::availableFS {
&defaultFS,
#ifdef SD_CARD
&sdFS,
#endif
// &archiveFS, // extension-based FS have to be on top to be picked first, otherwise the scheme will pick them!
&p00FS,
&d64FS, &d71FS, &d80FS, &d81FS, &d82FS, &d90FS, &dnpFS,
&d8bFS, &dfiFS,
&t64FS, &tcrtFS,
// &httpFS, &mlFS, &ipfsFS,
// &tnfsFS, &tcpFS,
// &tnfsFS
};
bool MFSOwner::mount(std::string name) {
Debug_print("MFSOwner::mount fs:");
Debug_println(name.c_str());
for(auto i = availableFS.begin() + 1; i < availableFS.end() ; i ++) {
auto fs = (*i);
if(fs->handles(name)) {
Debug_printv("MFSOwner found a proper fs");
bool ok = fs->mount();
if(ok)
Debug_print("Mounted fs: ");
else
Debug_print("Couldn't mount fs: ");
Debug_println(name.c_str());
return ok;
}
}
return false;
}
bool MFSOwner::umount(std::string name) {
for(auto i = availableFS.begin() + 1; i < availableFS.end() ; i ++) {
auto fs = (*i);
if(fs->handles(name)) {
return fs->umount();
}
}
return true;
}
MFile* MFSOwner::File(MFile* file) {
return File(file->url);
}
MFile* MFSOwner::File(std::shared_ptr<MFile> file) {
return File(file->url);
}
MFile* MFSOwner::File(std::string path) {
//Debug_printv("in File\r\n");
// if(mstr::startsWith(path,"cs:", false)) {
// //Serial.printf("CServer path found!\r\n");
// return csFS.getFile(path);
// }
std::vector<std::string> paths = mstr::split(path,'/');
Debug_printv("Trying to factory path [%s]", path.c_str());
auto pathIterator = paths.end();
auto begin = paths.begin();
auto end = paths.end();
auto foundFS = testScan(begin, end, pathIterator);
if(foundFS != nullptr) {
Debug_printv("PATH: '%s' is in FS [%s]", path.c_str(), foundFS->symbol);
auto newFile = foundFS->getFile(path);
Debug_printv("newFile: '%s'", newFile->url.c_str());
pathIterator++;
newFile->pathInStream = mstr::joinToString(&pathIterator, &end, "/");
Debug_printv("newFile->pathInStream: '%s'", newFile->pathInStream.c_str());
auto endHere = pathIterator;
pathIterator--;
if(begin == pathIterator)
{
Debug_printv("** LOOK DOWN PATH NOT NEEDED path[%s]", path.c_str());
newFile->streamFile = foundFS->getFile(mstr::joinToString(&begin, &pathIterator, "/"));
}
else
{
auto upperPath = mstr::joinToString(&begin, &pathIterator, "/");
Debug_printv("** LOOK DOWN PATH: %s", upperPath.c_str());
auto upperFS = testScan(begin, end, pathIterator);
if(upperFS != nullptr) {
auto wholePath = mstr::joinToString(&begin, &endHere, "/");
//auto cp = mstr::joinToString(&begin, &pathIterator, "/");
Debug_printv("CONTAINER PATH WILL BE: '%s' ", wholePath.c_str());
newFile->streamFile = upperFS->getFile(wholePath); // skończy się na d64
Debug_printv("CONTAINER: '%s' is in FS [%s]", newFile->streamFile->url.c_str(), upperFS->symbol);
}
else {
Debug_printv("WARNING!!!! CONTAINER FAILED FOR: '%s'", upperPath.c_str());
}
}
return newFile;
}
return nullptr;
}
std::string MFSOwner::existsLocal( std::string path )
{
PeoplesUrlParser *url = PeoplesUrlParser::parseURL( path );
// Debug_printv( "path[%s] name[%s] size[%d]", path.c_str(), url.name.c_str(), url.name.size() );
if ( url->name.size() == 16 )
{
struct stat st;
int i = stat(std::string(path).c_str(), &st);
// If not found try for a wildcard match
if ( i == -1 )
{
DIR *dir;
struct dirent *ent;
std::string p = url->pathToFile();
std::string name = url->name;
// Debug_printv( "pathToFile[%s] basename[%s]", p.c_str(), name.c_str() );
if ((dir = opendir ( p.c_str() )) != NULL)
{
/* print all the files and directories within directory */
std::string e;
while ((ent = readdir (dir)) != NULL) {
// Debug_printv( "%s\r\n", ent->d_name );
e = ent->d_name;
if ( mstr::compare( name, e ) )
{
path = ( p + "/" + e );
break;
}
}
closedir (dir);
}
}
}
return path;
}
MFileSystem* MFSOwner::testScan(std::vector<std::string>::iterator &begin, std::vector<std::string>::iterator &end, std::vector<std::string>::iterator &pathIterator) {
while (pathIterator != begin) {
pathIterator--;
auto part = *pathIterator;
mstr::toLower(part);
//Debug_printv("index[%d] pathIterator[%s] size[%d]", pathIterator, pathIterator->c_str(), pathIterator->size());
auto foundIter=std::find_if(availableFS.begin() + 1, availableFS.end(), [&part](MFileSystem* fs){
//Debug_printv("symbol[%s]", fs->symbol);
return fs->handles(part);
} );
if(foundIter != availableFS.end()) {
//Debug_printv("matched part '%s'\r\n", part.c_str());
return (*foundIter);
}
};
auto fs = *availableFS.begin();
//Debug_printv("return default file system [%s]", fs->symbol);
return fs;
}
/********************************************************
* MFileSystem implementations
********************************************************/
MFileSystem::MFileSystem(const char* s)
{
symbol = s;
}
MFileSystem::~MFileSystem() {}
/********************************************************
* MFile implementations
********************************************************/
MFile::MFile(std::string path) {
// Debug_printv("path[%s]", path.c_str());
// if ( mstr::contains(path, "$") )
// {
// // Create directory stream here
// Debug_printv("Create directory stream here!");
// path = "";
// }
resetURL(path);
}
MFile::MFile(std::string path, std::string name) : MFile(path + "/" + name) {
if(mstr::startsWith(name, "xn--")) {
this->path = path + "/" + U8Char::fromPunycode(name);
}
}
MFile::MFile(MFile* path, std::string name) : MFile(path->path + "/" + name) {
if(mstr::startsWith(name, "xn--")) {
this->path = path->path + "/" + U8Char::fromPunycode(name);
}
}
bool MFile::operator!=(nullptr_t ptr) {
return m_isNull;
}
MStream* MFile::getSourceStream(std::ios_base::openmode mode) {
// has to return OPENED stream
std::shared_ptr<MStream> containerStream(streamFile->getSourceStream(mode)); // get its base stream, i.e. zip raw file contents
Debug_printv("containerStream isRandomAccess[%d] isBrowsable[%d]", containerStream->isRandomAccess(), containerStream->isBrowsable());
MStream* decodedStream(getDecodedStream(containerStream)); // wrap this stream into decoded stream, i.e. unpacked zip files
decodedStream->url = this->url;
Debug_printv("decodedStream isRandomAccess[%d] isBrowsable[%d]", decodedStream->isRandomAccess(), decodedStream->isBrowsable());
Debug_printv("pathInStream [%s]", pathInStream.c_str());
if(decodedStream->isRandomAccess() && pathInStream != "") {
bool foundIt = decodedStream->seekPath(this->pathInStream);
if(!foundIt)
{
Debug_printv("path in stream not found");
return nullptr;
}
}
else if(decodedStream->isBrowsable() && pathInStream != "") {
auto pointedFile = decodedStream->seekNextEntry();
while (!pointedFile.empty())
{
if(mstr::compare(this->pathInStream, pointedFile))
{
//Debug_printv("returning decodedStream");
return decodedStream;
}
pointedFile = decodedStream->seekNextEntry();
}
Debug_printv("path in stream not found!");
if(pointedFile.empty())
return nullptr;
}
//Debug_printv("returning decodedStream");
return decodedStream;
};
MFile* MFile::cd(std::string newDir)
{
Debug_printv("cd requested: [%s]", newDir.c_str());
if ( streamFile != nullptr)
Debug_printv("streamFile[%s]", streamFile->url.c_str());
// OK to clarify - coming here there should be ONLY path or magicSymbol-path combo!
// NO "cd:xxxxx", no "/cd:xxxxx" ALLOWED here! ******************
//
// if you want to support LOAD"CDxxxxxx" just parse/drop the CD BEFORE calling this function
// and call it ONLY with the path you want to change into!
if(newDir.find(':') != std::string::npos)
{
// I can only guess we're CDing into another url scheme, this means we're changing whole path
return MFSOwner::File(newDir);
}
else if(newDir[0]=='_') // {CBM LEFT ARROW}
{
// user entered: CD:_ or CD_
// means: go up one directory
// user entered: CD:_DIR or CD_DIR
// means: go to a directory in the same directory as this one
return cdParent(mstr::drop(newDir,1));
}
else if(newDir[0]=='.' && newDir[1]=='.')
{
if(newDir.size()==2)
{
// user entered: CD:.. or CD..
// means: go up one directory
return cdParent();
}
else
{
// user entered: CD:..DIR or CD..DIR
// meaning: Go back one directory
return cdLocalParent(mstr::drop(newDir,2));
}
}
else if(newDir[0]=='/' && newDir[1]=='/')
{
// user entered: CD:// or CD//
// means: change to the root of stream
// user entered: CD://DIR or CD//DIR
// means: change to a dir in root of stream
return cdLocalRoot(mstr::drop(newDir,2));
}
else if(newDir[0]=='/')
{
// user entered: CD:/DIR or CD/DIR
// means: go to a directory in the same directory as this one
return cdParent(mstr::drop(newDir,1));
}
else if(newDir[0]=='^') // {CBM UP ARROW}
{
// user entered: CD:^ or CD^
// means: change to flash root
return MFSOwner::File("/");
}
else
{
//newDir = mstr::toUTF8( newDir );
// Add new directory to path
if ( !mstr::endsWith(url, "/") && newDir.size() )
url.push_back('/');
Debug_printv("> url[%s] newDir[%s]", url.c_str(), newDir.c_str());
// Add new directory to path
MFile* newPath = MFSOwner::File(url + newDir);
if(mstr::endsWith(newDir, ".url", false)) {
// we need to get actual url
//auto reader = Meat::New<MFile>(newDir);
//auto istream = reader->getSourceStream();
Meat::iostream reader(newPath);
//uint8_t url[istream->size()]; // NOPE, streams have no size!
//istream->read(url, istream->size());
std::string url;
reader >> url;
Debug_printv("url[%s]", url.c_str());
//std::string ml_url((char *)url);
delete newPath;
newPath = MFSOwner::File(url);
}
return newPath;
}
};
MFile* MFile::cdParent(std::string plus)
{
Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str());
// drop last dir
// add plus
if(path.empty())
{
// from here we can go only to flash root!
return MFSOwner::File("/");
}
else
{
int lastSlash = url.find_last_of('/');
if ( lastSlash == url.size() - 1 )
{
lastSlash = url.find_last_of('/', url.size() - 2);
}
std::string newDir = mstr::dropLast(url, url.size() - lastSlash);
if(!plus.empty())
newDir+= ("/" + plus);
return MFSOwner::File(newDir);
}
};
MFile* MFile::cdLocalParent(std::string plus)
{
Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str());
// drop last dir
// check if it isn't shorter than streamFile
// add plus
int lastSlash = url.find_last_of('/');
if ( lastSlash == url.size() - 1 ) {
lastSlash = url.find_last_of('/', url.size() - 2);
}
std::string parent = mstr::dropLast(url, url.size() - lastSlash);
if(parent.length()-streamFile->url.length()>1)
parent = streamFile->url;
return MFSOwner::File( parent + "/" + plus );
};
MFile* MFile::cdRoot(std::string plus)
{
Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str());
return MFSOwner::File( "/" + plus );
};
MFile* MFile::cdLocalRoot(std::string plus)
{
Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str());
if ( path.empty() || streamFile == nullptr ) {
// from here we can go only to flash root!
return MFSOwner::File( "/" + plus );
}
return MFSOwner::File( streamFile->url + "/" + plus );
};
// bool MFile::copyTo(MFile* dst) {
// Debug_printv("in copyTo\r\n");
// Meat::iostream istream(this);
// Meat::iostream ostream(dst);
// int rc;
// Debug_printv("in copyTo, iopen=%d oopen=%d\r\n", istream.is_open(), ostream.is_open());
// if(!istream.is_open() || !ostream.is_open())
// return false;
// Debug_printv("commencing copy\r\n");
// while((rc = istream.get())!= EOF) {
// ostream.put(rc);
// if(ostream.bad() || istream.bad())
// return false;
// }
// Debug_printv("copying finished, rc=%d\r\n", rc);
// return true;
// };
uint64_t MFile::getAvailableSpace()
{
if ( mstr::startsWith(path, (char *)"/sd") )
{
#ifdef SD_CARD
FATFS* fsinfo;
DWORD fre_clust;
if (f_getfree("/", &fre_clust, &fsinfo) == 0)
{
uint64_t total = ((uint64_t)(fsinfo->csize)) * (fsinfo->n_fatent - 2) * (fsinfo->ssize);
uint64_t used = ((uint64_t)(fsinfo->csize)) * ((fsinfo->n_fatent - 2) - (fsinfo->free_clst)) * (fsinfo->ssize);
uint64_t free = total - used;
//Debug_printv("total[%llu] used[%llu free[%llu]", total, used, free);
return free;
}
#endif
}
else
{
#ifdef FLASH_SPIFFS
size_t total = 0, used = 0;
esp_spiffs_info("flash", &total, &used);
size_t free = total - used;
//Debug_printv("total[%d] used[%d] free[%d]", total, used, free);
return free;
#elif FLASH_LITTLEFS
// TODO: Implement for LITTLEFS
Debug_printv("LITTLEFS getAvailableSpace()");
#endif
}
return 65535;
}