diff --git a/firmware/src/Files/Files.h b/firmware/src/Files/Files.h index e3674f4..e1dbc1d 100644 --- a/firmware/src/Files/Files.h +++ b/firmware/src/Files/Files.h @@ -237,6 +237,7 @@ class IFiles virtual void createDirectory(const char *folder) = 0; virtual FileLetterCountVector getFileLetters(const char *folder, const std::vector &extensions) = 0; virtual FileInfoVector getFileStartingWithPrefix(const char *folder, const char *prefix, const std::vector &extensions) = 0; + virtual std::string getPath(const char *path) = 0; }; template @@ -252,13 +253,18 @@ class FilesImplementation: public IFiles bool isAvailable() { - return fileSystem->isMounted(); + return fileSystem && fileSystem->isMounted(); + } + + std::string getPath(const char *path) + { + return std::string(fileSystem->mountPoint()) + path; } void createDirectory(const char *folder) { auto bl = BusyLight(); - if (!fileSystem->isMounted()) + if (!fileSystem || !fileSystem->isMounted()) { return; } @@ -280,7 +286,7 @@ class FilesImplementation: public IFiles { auto bl = BusyLight(); FileLetterCountVector fileLetters; - if (!fileSystem->isMounted()) + if (!fileSystem || !fileSystem->isMounted()) { return fileLetters; } @@ -316,7 +322,7 @@ class FilesImplementation: public IFiles { auto bl = BusyLight(); FileInfoVector files; - if (!fileSystem->isMounted()) + if (!fileSystem || !fileSystem->isMounted()) { return files; } @@ -337,3 +343,112 @@ class FilesImplementation: public IFiles return files; } }; + +class UnifiedStorage : public IFiles { +private: + IFiles *flashFiles; + IFiles *sdFiles; + +public: + UnifiedStorage(IFiles* flash, IFiles* sd) + : flashFiles(flash), sdFiles(sd) {} + + bool isAvailable() override { + return (flashFiles->isAvailable()) || (sdFiles->isAvailable()); + } + + std::string getPath(const char *path) override { + if (sdFiles->isAvailable()) { + return sdFiles->getPath(path); + } + return flashFiles->getPath(path); + } + + void createDirectory(const char *folder) override { + if (sdFiles->isAvailable()) { + sdFiles->createDirectory(folder); + return; + } + // fallback to flash + if (flashFiles->isAvailable()) { + flashFiles->createDirectory(folder); + } + } + + FileLetterCountVector getFileLetters(const char *folder, const std::vector &extensions) override { + std::map letterCountMap; + + // Get files from both sources + if (flashFiles->isAvailable()) { + auto flashLetters = flashFiles->getFileLetters(folder, extensions); + for (const auto& letterCount : flashLetters) { + letterCountMap[letterCount->getLetter()] = letterCount->getTitle().find_first_of("0123456789"); + } + } + + if (sdFiles->isAvailable()) { + auto sdLetters = sdFiles->getFileLetters(folder, extensions); + for (const auto& letterCount : sdLetters) { + auto it = letterCountMap.find(letterCount->getLetter()); + if (it != letterCountMap.end()) { + // Extract numbers from both counts and add them + int existingCount = it->second; + int newCount = letterCount->getTitle().find_first_of("0123456789"); + letterCountMap[letterCount->getLetter()] = existingCount + newCount; + } else { + letterCountMap[letterCount->getLetter()] = letterCount->getTitle().find_first_of("0123456789"); + } + } + } + + // Convert map back to vector + FileLetterCountVector combined; + for (const auto& pair : letterCountMap) { + combined.push_back(std::make_shared(pair.first, pair.second)); + } + + // Sort alphabetically + std::sort(combined.begin(), combined.end(), + [](const FileLetterCountPtr& a, const FileLetterCountPtr& b) { + return a->getLetter() < b->getLetter(); + }); + + return combined; + } + + FileInfoVector getFileStartingWithPrefix(const char *folder, const char *prefix, const std::vector &extensions) override { + FileInfoVector combined; + std::map uniqueFiles; + + // Get files from both sources + if (flashFiles->isAvailable()) { + auto flashFiles = this->flashFiles->getFileStartingWithPrefix(folder, prefix, extensions); + for (const auto& file : flashFiles) { + uniqueFiles[file->getTitle()] = file; + } + } + + if (sdFiles->isAvailable()) { + auto sdFiles = this->sdFiles->getFileStartingWithPrefix(folder, prefix, extensions); + for (const auto& file : sdFiles) { + // Only add if not already present from flash + if (uniqueFiles.find(file->getTitle()) == uniqueFiles.end()) { + uniqueFiles[file->getTitle()] = file; + } + } + } + + // Convert map back to vector + for (const auto& pair : uniqueFiles) { + combined.push_back(pair.second); + } + + // Sort alphabetically + std::sort(combined.begin(), combined.end(), + [](const FileInfoPtr& a, const FileInfoPtr& b) { + return a->getTitle() < b->getTitle(); + }); + + return combined; + } +}; diff --git a/firmware/src/Files/Flash.h b/firmware/src/Files/Flash.h index e923af8..a3e5f3a 100644 --- a/firmware/src/Files/Flash.h +++ b/firmware/src/Files/Flash.h @@ -6,6 +6,7 @@ class Flash bool _isMounted; std::string m_mountPoint; public: + static constexpr const char* DEFAULT_MOUNT_POINT = "/flash"; Flash(const char *mountPoint); ~Flash(); bool isMounted() { diff --git a/firmware/src/Files/SDCard.h b/firmware/src/Files/SDCard.h index 21dc1bf..d14af03 100644 --- a/firmware/src/Files/SDCard.h +++ b/firmware/src/Files/SDCard.h @@ -20,6 +20,7 @@ class SDCard int m_sector_size = 0; int m_sector_count = 0; public: + static constexpr const char* DEFAULT_MOUNT_POINT = "/sdcard"; bool isMounted() { return _isMounted; } const char *mountPoint() { return m_mountPoint.c_str(); diff --git a/firmware/src/Screens/AlphabetPicker.h b/firmware/src/Screens/AlphabetPicker.h index 32b3494..e326d66 100644 --- a/firmware/src/Screens/AlphabetPicker.h +++ b/firmware/src/Screens/AlphabetPicker.h @@ -8,12 +8,11 @@ template class AlphabetPicker : public PickerScreen { private: - IFiles *m_files; std::string m_path; std::vector m_extensions; public: AlphabetPicker(std::string title, IFiles *files, Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, std::string path, std::vector extensions) - : m_files(files), PickerScreen(title, tft, hdmiDisplay, audioOutput), m_path(path), m_extensions(extensions) + : PickerScreen(title, tft, hdmiDisplay, audioOutput, files), m_path(path), m_extensions(extensions) { } void onItemSelect(FileLetterCountPtr item, int index) override diff --git a/firmware/src/Screens/EmulatorScreen.cpp b/firmware/src/Screens/EmulatorScreen.cpp index ebc58d4..b4eb2f1 100644 --- a/firmware/src/Screens/EmulatorScreen.cpp +++ b/firmware/src/Screens/EmulatorScreen.cpp @@ -31,7 +31,8 @@ void EmulatorScreen::triggerLoadTape() no_sd_card_error, m_tft, m_hdmiDisplay, - m_audioOutput); + m_audioOutput, + m_files); m_navigationStack->push(errorScreen); return; } @@ -43,7 +44,8 @@ void EmulatorScreen::triggerLoadTape() no_files_error, m_tft, m_hdmiDisplay, - m_audioOutput); + m_audioOutput, + m_files); m_navigationStack->push(errorScreen); return; } @@ -53,7 +55,7 @@ void EmulatorScreen::triggerLoadTape() } EmulatorScreen::EmulatorScreen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) - : Screen(tft, hdmiDisplay, audioOutput), m_files(files) + : Screen(tft, hdmiDisplay, audioOutput, files) { renderer = new Renderer(tft, hdmiDisplay); machine = new Machine(renderer, audioOutput, [&]() @@ -160,7 +162,7 @@ void EmulatorScreen::pressKey(SpecKeys key) else if (key == SPECKEY_2) { isShowingMenu = false; // show the save snapshot UI - m_navigationStack->push(new SaveSnapshotScreen(m_tft, m_hdmiDisplay, m_audioOutput, machine->getMachine())); + m_navigationStack->push(new SaveSnapshotScreen(m_tft, m_hdmiDisplay, m_audioOutput, machine->getMachine(), m_files)); } } } diff --git a/firmware/src/Screens/EmulatorScreen.h b/firmware/src/Screens/EmulatorScreen.h index ebb9f60..4384ecc 100644 --- a/firmware/src/Screens/EmulatorScreen.h +++ b/firmware/src/Screens/EmulatorScreen.h @@ -20,7 +20,6 @@ class EmulatorScreen : public Screen Machine *machine = nullptr; GameLoader *gameLoader = nullptr; FILE *audioFile = nullptr; - IFiles *m_files; void triggerLoadTape(); bool isLoading = false; bool isInTimeTravelMode = false; diff --git a/firmware/src/Screens/ErrorScreen.h b/firmware/src/Screens/ErrorScreen.h index ee29ffb..184b763 100644 --- a/firmware/src/Screens/ErrorScreen.h +++ b/firmware/src/Screens/ErrorScreen.h @@ -15,7 +15,8 @@ class ErrorScreen : public Screen std::vector messages, Display &tft, HDMIDisplay *hdmiDisplay, - AudioOutput *audioOutput) : m_messages(messages), Screen(tft, hdmiDisplay, audioOutput) + AudioOutput *audioOutput, + IFiles *files) : m_messages(messages), Screen(tft, hdmiDisplay, audioOutput, files) { } diff --git a/firmware/src/Screens/GameFilePickerScreen.h b/firmware/src/Screens/GameFilePickerScreen.h index 4b71af6..ddbb601 100644 --- a/firmware/src/Screens/GameFilePickerScreen.h +++ b/firmware/src/Screens/GameFilePickerScreen.h @@ -6,11 +6,9 @@ class GameFilePickerScreen : public PickerScreen { - private: - IFiles *m_files; public: GameFilePickerScreen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) - : PickerScreen("Games", tft, hdmiDisplay, audioOutput), m_files(files) {} + : PickerScreen("Games", tft, hdmiDisplay, audioOutput, files) {} void onItemSelect(FileInfoPtr item, int index) { Serial.printf("Selected %s\n", item->getPath().c_str()); // locate the emaulator screen if it's on the stack already diff --git a/firmware/src/Screens/MainMenuScreen.h b/firmware/src/Screens/MainMenuScreen.h index 9b28a25..dc50d61 100644 --- a/firmware/src/Screens/MainMenuScreen.h +++ b/firmware/src/Screens/MainMenuScreen.h @@ -29,11 +29,9 @@ using MenuItemVector = std::vector; class MainMenuScreen : public PickerScreen { -private: - IFiles *m_files; - public: - MainMenuScreen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) : m_files(files), PickerScreen("Main Menu", tft, hdmiDisplay, audioOutput) + MainMenuScreen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) + : PickerScreen("Main Menu", tft, hdmiDisplay, audioOutput, files) { // Main menu MenuItemVector menuItems = { @@ -89,7 +87,8 @@ class MainMenuScreen : public PickerScreen no_sd_card_error, m_tft, m_hdmiDisplay, - m_audioOutput); + m_audioOutput, + m_files); m_navigationStack->push(errorScreen); return; } @@ -101,7 +100,8 @@ class MainMenuScreen : public PickerScreen no_files_error, m_tft, m_hdmiDisplay, - m_audioOutput); + m_audioOutput, + m_files); m_navigationStack->push(errorScreen); return; } diff --git a/firmware/src/Screens/PickerScreen.h b/firmware/src/Screens/PickerScreen.h index b13eeb3..346d5d1 100644 --- a/firmware/src/Screens/PickerScreen.h +++ b/firmware/src/Screens/PickerScreen.h @@ -30,8 +30,9 @@ class PickerScreen : public Screen std::string title, Display &tft, HDMIDisplay *hdmiDisplay, - AudioOutput *audioOutput - ) : title(title), Screen(tft, hdmiDisplay, audioOutput) + AudioOutput *audioOutput, + IFiles *files + ) : title(title), Screen(tft, hdmiDisplay, audioOutput, files) { } diff --git a/firmware/src/Screens/SaveSnapshotScreen.h b/firmware/src/Screens/SaveSnapshotScreen.h index de348f7..a2ce68a 100644 --- a/firmware/src/Screens/SaveSnapshotScreen.h +++ b/firmware/src/Screens/SaveSnapshotScreen.h @@ -7,6 +7,8 @@ #include "../Emulator/spectrum.h" #include "../Emulator/snaps.h" +class IFiles; + class SaveSnapshotScreen : public Screen { private: @@ -17,7 +19,8 @@ class SaveSnapshotScreen : public Screen Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, - ZXSpectrum *machine) : machine(machine), Screen(tft, hdmiDisplay, audioOutput) + ZXSpectrum *machine, + IFiles *files) : machine(machine), Screen(tft, hdmiDisplay, audioOutput, files) { } @@ -35,7 +38,8 @@ class SaveSnapshotScreen : public Screen if (filename.length() > 0) { auto bl = BusyLight(); drawBusy(); - saveZ80(machine, ("/fs/snapshots/" + filename + ".Z80").c_str()); + std::string fname = m_files->getPath("/snapshots") + "/" + filename + ".Z80"; + saveZ80(machine, fname.c_str()); playSuccessBeep(); vTaskDelay(500 / portTICK_PERIOD_MS); m_navigationStack->pop(); diff --git a/firmware/src/Screens/Screen.h b/firmware/src/Screens/Screen.h index 7c0b1c5..68ccc15 100644 --- a/firmware/src/Screens/Screen.h +++ b/firmware/src/Screens/Screen.h @@ -13,17 +13,22 @@ class Display; class AudioOutput; class NavigationStack; +class IFiles; const uint8_t click[] = { 50, 50, 50, 0 }; +class IFiles; + class Screen { protected: NavigationStack *m_navigationStack = nullptr; Display &m_tft; HDMIDisplay *m_hdmiDisplay = nullptr; AudioOutput *m_audioOutput; + IFiles *m_files; public: - Screen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput) : m_tft(tft), m_hdmiDisplay(hdmiDisplay), m_audioOutput(audioOutput) {} + Screen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) + : m_tft(tft), m_hdmiDisplay(hdmiDisplay), m_audioOutput(audioOutput), m_files(files) {} // input virtual void updateKey(SpecKeys key, uint8_t state) {}; virtual void pressKey(SpecKeys key) {}; diff --git a/firmware/src/Screens/VideoFilePickerScreen.h b/firmware/src/Screens/VideoFilePickerScreen.h index b3675a1..4ab30e3 100644 --- a/firmware/src/Screens/VideoFilePickerScreen.h +++ b/firmware/src/Screens/VideoFilePickerScreen.h @@ -5,13 +5,11 @@ class VideoFilePickerScreen : public PickerScreen { - private: - IFiles *m_files; public: VideoFilePickerScreen(Display &tft, HDMIDisplay *hdmiDisplay, AudioOutput *audioOutput, IFiles *files) - : PickerScreen("Videos", tft, hdmiDisplay, audioOutput), m_files(files) {} + : PickerScreen("Videos", tft, hdmiDisplay, audioOutput, files) {} void onItemSelect(FileInfoPtr item, int index) { - VideoPlayerScreen *playerScreen = new VideoPlayerScreen(m_tft, m_hdmiDisplay, m_audioOutput); + VideoPlayerScreen *playerScreen = new VideoPlayerScreen(m_tft, m_hdmiDisplay, m_audioOutput, m_files); playerScreen->play(item->getPath().c_str()); m_navigationStack->push(playerScreen); } diff --git a/firmware/src/Screens/VideoPlayerScreen.h b/firmware/src/Screens/VideoPlayerScreen.h index b8dbfff..5db30a8 100644 --- a/firmware/src/Screens/VideoPlayerScreen.h +++ b/firmware/src/Screens/VideoPlayerScreen.h @@ -7,6 +7,7 @@ using fs::File; #include "VideoPlayer/VideoPlayerState.h" class VideoSource; +class IFiles; class VideoPlayerScreen : public Screen { @@ -44,7 +45,8 @@ class VideoPlayerScreen : public Screen VideoPlayerScreen( Display &tft, HDMIDisplay *hdmiDisplay, - AudioOutput *audioOutput) : Screen(tft, hdmiDisplay, audioOutput) + AudioOutput *audioOutput, + IFiles *files) : Screen(tft, hdmiDisplay, audioOutput, files) { } void play(const char *aviFilename); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 5cb1d64..f5f5a3e 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -42,8 +42,6 @@ #include "Input/TouchKeyboardV2.h" #endif -const char *MOUNT_POINT = "/fs"; - void setup(void) { #ifdef BOARD_POWERON @@ -87,24 +85,26 @@ void setup(void) Serial.println("SPI initialized"); #endif // Files + SDCard *sdFileSystem = nullptr; #ifdef USE_SDCARD - #ifdef USE_SDIO - SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); - IFiles *files = new FilesImplementation(fileSystem); - setupUSB(fileSystem); - #else - #ifdef SD_CARD_MISO - SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_CLK, SD_CARD_CS); + #ifdef USE_SDIO + sdFileSystem = new SDCard(SDCard::DEFAULT_MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); + // TODO setupUSB(fileSystem); #else - // SD Card shares the SPI bus with the TFT - SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CS); + #ifdef SD_CARD_MISO + sdFileSystem = new SDCard(SDCard::DEFAULT_MOUNT_POINT, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_CLK, SD_CARD_CS); + #else + // SD Card shares the SPI bus with the TFT + sdFileSystem = new SDCard(SDCard::DEFAULT_MOUNT_POINT, SD_CARD_CS); + #endif #endif - IFiles *files = new FilesImplementation(fileSystem); - #endif - #else - Flash *fileSystem = new Flash(MOUNT_POINT); - IFiles *files = new FilesImplementation(fileSystem); #endif + IFiles *sdFiles = new FilesImplementation(sdFileSystem); + Flash *flashFileSystem = new Flash(Flash::DEFAULT_MOUNT_POINT); + IFiles *spiffsFiles = new FilesImplementation(flashFileSystem); + + IFiles *files = new UnifiedStorage(spiffsFiles, sdFiles); + #ifdef TFT_ST7789 Display *tft = new ST7789(TFT_CS, TFT_DC, TFT_RST, TFT_BL, TFT_WIDTH, TFT_HEIGHT); #endif