#include "meat_io.h" #include #include #include #include #include #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 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("%s", 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("%s", 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 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 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::iterator &begin, std::vector::iterator &end, std::vector::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 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(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; }