diff --git a/sources/Adapters/adv/filesystem/advFileSystem.cpp b/sources/Adapters/adv/filesystem/advFileSystem.cpp index 8179c4628..72b7e9e18 100644 --- a/sources/Adapters/adv/filesystem/advFileSystem.cpp +++ b/sources/Adapters/adv/filesystem/advFileSystem.cpp @@ -133,7 +133,10 @@ PicoFileType advFileSystem::getFileType(int index) { } void advFileSystem::list(etl::ivector *fileIndexes, const char *filter, - bool subDirOnly, bool includeHidden) { + ListFlags flags) { + bool subDirOnly = flags & LF_SUBDIRS_ONLY; + bool includeHidden = flags & LF_INCLUDE_HIDDEN; + bool sorted = flags & LF_SORTED; fileIndexes->clear(); @@ -155,6 +158,8 @@ void advFileSystem::list(etl::ivector *fileIndexes, const char *filter, return; } + uint32_t sortKeys[MAX_FILE_INDEX_SIZE]; + for (size_t i = 0; i < file_cache_.size(); ++i) { if (fileIndexes->full()) { Trace::Error("PICOFILESYSTEM: fileIndexes is full, breaking list"); @@ -187,12 +192,48 @@ void advFileSystem::list(etl::ivector *fileIndexes, const char *filter, } } else { fileIndexes->push_back(i); + + if (sorted) { + sortKeys[fileIndexes->size() - 1] = + FileSystem::getFileSortKey(fno.fname); + } } Trace::Log("FILESYSTEM", "[%d] got file: %s", index, fno.fname); } else { Trace::Log("FILESYSTEM", "skipped hidden: %s", fno.fname); } } + + for (size_t i = 1; i < fileIndexes->size(); i++) { + uint32_t key = sortKeys[i]; + int index = fileIndexes->at(i); + size_t j = i; + + while (j > 0) { + bool shouldMove = sortKeys[j - 1] > key; + + if (!shouldMove && sortKeys[j - 1] == key) { + // keys are identical, do a tiebreaker by doing a string compare to + // ensure stable sorting of files with same first 4 letters + const FILINFO &fA = file_cache_[index]; + const FILINFO &fB = file_cache_[fileIndexes->at(j - 1)]; + + shouldMove = strcasecmp(fA.fname, fB.fname) > 0; + } + + if (shouldMove) { + sortKeys[j] = sortKeys[j - 1]; + fileIndexes->at(j) = fileIndexes->at(j - 1); + --j; + } else { + break; + } + } + + sortKeys[j] = key; + fileIndexes->at(j) = index; + } + f_closedir(&dir); Trace::Log("FILESYSTEM", "added file indexes:%d", fileIndexes->size()); } diff --git a/sources/Adapters/adv/filesystem/advFileSystem.h b/sources/Adapters/adv/filesystem/advFileSystem.h index 460249371..ec3ea3597 100644 --- a/sources/Adapters/adv/filesystem/advFileSystem.h +++ b/sources/Adapters/adv/filesystem/advFileSystem.h @@ -51,7 +51,7 @@ class advFileSystem : public FileSystem { virtual FileHandle Open(const char *name, const char *mode) override; virtual bool chdir(const char *path) override; virtual void list(etl::ivector *fileIndexes, const char *filter, - bool subDirOnly, bool includeHidden = false) override; + ListFlags flags) override; virtual void getFileName(int index, char *name, int length) override; virtual PicoFileType getFileType(int index) override; virtual bool isParentRoot() override; diff --git a/sources/Adapters/adv/gui/SerialDebugUI.cpp b/sources/Adapters/adv/gui/SerialDebugUI.cpp index 113673d37..cb4b69b9b 100644 --- a/sources/Adapters/adv/gui/SerialDebugUI.cpp +++ b/sources/Adapters/adv/gui/SerialDebugUI.cpp @@ -198,8 +198,8 @@ void SerialDebugUI::listFiles(const char *path) { if (!fs->chdir(path)) { Trace::Error("failed to ls files path:%s", path); } - etl::vector fileIndexes; - fs->list(&fileIndexes, "", false); + + fs->list(&fileIndexList_, "", LF_SORTED); // No need to actually do the printing below for now as the current debug code // in PicoFileSystem class is already printing all the files fetched when the diff --git a/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.cpp b/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.cpp index fceebd5ee..4a91b38e7 100644 --- a/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.cpp +++ b/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.cpp @@ -7,6 +7,7 @@ */ #include "picoTrackerFileSystem.h" +#include "Application/Persistency/PersistencyService.h" #include "Externals/etl/include/etl/pool.h" #include "pico/multicore.h" #include @@ -120,20 +121,22 @@ PicoFileType picoTrackerFileSystem::getFileType(int index) { } void picoTrackerFileSystem::list(etl::ivector *fileIndexes, - const char *filter, bool subDirOnly, - bool includeHidden) { + const char *filter, ListFlags flags) { + bool subDirOnly = flags & LF_SUBDIRS_ONLY; + bool includeHidden = flags & LF_INCLUDE_HIDDEN; + bool sorted = flags & LF_SORTED; std::lock_guard lock(mutex); fileIndexes->clear(); File cwd; + char buffer[PFILENAME_SIZE]; + if (!cwd.openCwd()) { - char name[PFILENAME_SIZE]; - cwd.getName(name, PFILENAME_SIZE); + cwd.getName(buffer, PFILENAME_SIZE); Trace::Error("Failed to open cwd"); return; } - char buffer[PFILENAME_SIZE]; cwd.getName(buffer, PFILENAME_SIZE); Trace::Log("FILESYSTEM", "LIST DIR:%s", buffer); @@ -144,6 +147,9 @@ void picoTrackerFileSystem::list(etl::ivector *fileIndexes, File entry; uint16_t count = 0; + + uint32_t sortKeys[MAX_FILE_INDEX_SIZE]; + // ref: https://github.com/greiman/SdFat/issues/353#issuecomment-1003422848 while (entry.openNext(&cwd, O_READ) && (count < fileIndexes->capacity())) { uint32_t index = entry.dirIndex(); @@ -157,25 +163,69 @@ void picoTrackerFileSystem::list(etl::ivector *fileIndexes, // matchesFilter); } // filter out "." and files that dont match filter if a filter is given - if ((entry.isDirectory() && entry.dirIndex() != 0) || - ((includeHidden || !entry.isHidden()) && matchesFilter)) { - if (subDirOnly) { - if (entry.isDirectory()) { - fileIndexes->push_back(index); - } - } else { - fileIndexes->push_back(index); + bool validDir = entry.isDirectory() && entry.dirIndex() != 0; + bool matchVisible = (!entry.isHidden() || includeHidden) && matchesFilter; + + if ((validDir || matchVisible) && (!subDirOnly || entry.isDirectory())) { + if (sorted) { + entry.getName(buffer, PFILENAME_SIZE); + sortKeys[count] = FileSystem::getFileSortKey(buffer); } - // Trace::Log("FILESYSTEM", "[%d] got file: %s", index, buffer); + fileIndexes->push_back(index); count++; + // Trace::Log("FILESYSTEM", "[%d] got file: %s", index, buffer); } else { - // Trace::Log("FILESYSTEM", "skipped hidden: %s", buffer); + // Trace::Log("FILESYSTEM", "skipped non-matching file: %s", buffer); } entry.close(); } - cwd.close(); + Trace::Log("FILESYSTEM", "scanned: %d, added file indexes:%d", count, fileIndexes->size()); + + if (!sorted) { + return; + } + + // sort using insertion sort (with an extra step if our keys are identical) + char currentName[PFILENAME_SIZE]; + + for (size_t i = 1; i < count; i++) { + uint32_t key = sortKeys[i]; + int index = fileIndexes->at(i); + size_t j = i; + + while (j > 0) { + bool shouldMove = sortKeys[j - 1] > key; + + if (!shouldMove && sortKeys[j - 1] == key) { + // keys are identical, do a tiebreaker by doing a string compare to + // ensure stable sorting of files with same first 4 letters + entry.open(index); + entry.getName(currentName, PFILENAME_SIZE); + entry.close(); + + entry.open(fileIndexes->at(j - 1)); + entry.getName(buffer, PFILENAME_SIZE); + entry.close(); + + shouldMove = strcasecmp(buffer, currentName) > 0; + } + + if (shouldMove) { + sortKeys[j] = sortKeys[j - 1]; + fileIndexes->at(j) = fileIndexes->at(j - 1); + --j; + } else { + break; + } + } + + sortKeys[j] = key; + fileIndexes->at(j) = index; + } + + cwd.close(); } void picoTrackerFileSystem::getFileName(int index, char *name, int length) { @@ -277,18 +327,21 @@ bool picoTrackerFileSystem::CopyFile(const char *srcFilename, auto fSrc = sd.open(srcFilename, O_READ); auto fDest = sd.open(destFilename, O_WRITE | O_CREAT); + const int FILE_BUFFER_SIZE = 1024; + char fileBuffer[FILE_BUFFER_SIZE]; + int n = 0; - int bufferSize = sizeof(fileBuffer_); + while (true) { - n = fSrc.read(fileBuffer_, bufferSize); + n = fSrc.read(fileBuffer, FILE_BUFFER_SIZE); // check for read error and only write if no error if (n >= 0) { - fDest.write(fileBuffer_, n); + fDest.write(fileBuffer, n); } else { Trace::Error("Failed to read file: %s", srcFilename); return false; } - if (n < bufferSize) { + if ((size_t)n < FILE_BUFFER_SIZE) { break; } } diff --git a/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.h b/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.h index e17a02974..43b7fb7ae 100644 --- a/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.h +++ b/sources/Adapters/picoTracker/filesystem/picoTrackerFileSystem.h @@ -31,7 +31,7 @@ class picoTrackerFileSystem : public FileSystem { virtual FileHandle Open(const char *name, const char *mode) override; virtual bool chdir(const char *path) override; virtual void list(etl::ivector *fileIndexes, const char *filter, - bool subDirOnly, bool includeHidden = false) override; + ListFlags flags) override; virtual void getFileName(int index, char *name, int length) override; virtual PicoFileType getFileType(int index) override; virtual bool isParentRoot() override; @@ -50,9 +50,6 @@ class picoTrackerFileSystem : public FileSystem { private: SdFs sd; void tolowercase(char *temp); - // buffer needs to be allocated here as too big for allocation as local - // variable on the stack - uint8_t fileBuffer_[512]; }; // Concrete implementation of PI_File for picoTracker diff --git a/sources/Adapters/picoTracker/gui/SerialDebugUI.cpp b/sources/Adapters/picoTracker/gui/SerialDebugUI.cpp index ef7e869a2..009a3c36a 100644 --- a/sources/Adapters/picoTracker/gui/SerialDebugUI.cpp +++ b/sources/Adapters/picoTracker/gui/SerialDebugUI.cpp @@ -134,13 +134,14 @@ void SerialDebugUI::listFiles(const char *path) { if (!fs->chdir(path)) { Trace::Error("failed to ls files path:%s", path); } - etl::vector fileIndexes; - fs->list(&fileIndexes, "", false); + + etl::vector fileIndexList_; + fs->list(&fileIndexList_, "", LF_SORTED); char name[PFILENAME_SIZE]; - for (size_t i = 0; i < fileIndexes.size(); i++) { - fs->getFileName(fileIndexes[i], name, PFILENAME_SIZE); - if (fs->getFileType(fileIndexes[i]) == PFT_FILE) { + for (size_t i = 0; i < fileIndexList_.size(); i++) { + fs->getFileName(fileIndexList_[i], name, PFILENAME_SIZE); + if (fs->getFileType(fileIndexList_[i]) == PFT_FILE) { printf("[file] %s\n", name); } else { printf("[dir] %s\n", name); diff --git a/sources/Application/AppWindow.cpp b/sources/Application/AppWindow.cpp index 187d2c08d..7afae5b24 100644 --- a/sources/Application/AppWindow.cpp +++ b/sources/Application/AppWindow.cpp @@ -152,8 +152,6 @@ AppWindow::AppWindow(I_GUIWindowImp &imp, const char *projectName) // Init all members - _statusLine[0] = 0; - _currentView = nullptr; _closeProject = false; _lastA = 0; diff --git a/sources/Application/AppWindow.h b/sources/Application/AppWindow.h index 278575d30..788034311 100644 --- a/sources/Application/AppWindow.h +++ b/sources/Application/AppWindow.h @@ -56,6 +56,7 @@ class AppWindow : public GUIWindow, I_Observer, Status { using GUIWindow::Clear; virtual void Clear(bool all = false); virtual void ClearTextRect(GUIRect &rect); + using GUIWindow::SetColor; virtual void SetColor(ColorDefinition cd); void InvalidateTextCache(); @@ -63,6 +64,8 @@ class AppWindow : public GUIWindow, I_Observer, Status { void UpdateColorsFromConfig(); void SetSdCardPresent(bool present); + View *getCurrentView() const { return _currentView; } + char projectName_[MAX_PROJECT_NAME_LENGTH + 1]; protected: // GUIWindow implementation @@ -104,7 +107,6 @@ class AppWindow : public GUIWindow, I_Observer, Status { unsigned short _mask; unsigned long _lastA; unsigned long _lastB; - char _statusLine[80]; bool lowBatteryState_; bool lowBatteryMessageShown_; diff --git a/sources/Application/Instruments/SamplePool.cpp b/sources/Application/Instruments/SamplePool.cpp index 03b3956da..800cfc549 100644 --- a/sources/Application/Instruments/SamplePool.cpp +++ b/sources/Application/Instruments/SamplePool.cpp @@ -57,10 +57,9 @@ void SamplePool::Load(const char *projectName) { PROJECT_SAMPLES_DIR); } // First, find all wav files - etl::vector fileIndexes; - fs->list(&fileIndexes, ".wav", false); + fs->list(&fileIndexList_, ".wav", LF_SORTED); char name[PFILENAME_SIZE]; - uint totalSamples = fileIndexes.size(); + uint totalSamples = fileIndexList_.size(); // store for ui updates importCount = totalSamples; @@ -69,8 +68,8 @@ void SamplePool::Load(const char *projectName) { importIndex = i; importName = name; - fs->getFileName(fileIndexes[i], name, PFILENAME_SIZE); - if (fs->getFileType(fileIndexes[i]) == PFT_FILE) { + fs->getFileName(fileIndexList_[i], name, PFILENAME_SIZE); + if (fs->getFileType(fileIndexList_[i]) == PFT_FILE) { // Check if the filename exceeds the maximum allowed length if (strlen(name) > MAX_INSTRUMENT_FILENAME_LENGTH) { Trace::Error( diff --git a/sources/Application/Instruments/SamplePool.h b/sources/Application/Instruments/SamplePool.h index a39060e70..910c5420c 100644 --- a/sources/Application/Instruments/SamplePool.h +++ b/sources/Application/Instruments/SamplePool.h @@ -61,6 +61,7 @@ class SamplePool : public T_Factory, public Observable { private: etl::vector observers_; + etl::vector fileIndexList_; }; #endif diff --git a/sources/Application/Persistency/PersistencyService.cpp b/sources/Application/Persistency/PersistencyService.cpp index d8a46696e..311501ae3 100644 --- a/sources/Application/Persistency/PersistencyService.cpp +++ b/sources/Application/Persistency/PersistencyService.cpp @@ -10,7 +10,6 @@ #include "PersistencyService.h" #include "../Instruments/SamplePool.h" #include "Foundation/Services/ServiceRegistry.h" - #include "Foundation/Types/Types.h" #include "Persistent.h" #include "System/Console/Trace.h" @@ -78,13 +77,13 @@ bool PersistencyService::DeleteDirectoryContents_(uint8_t depth) { } while (true) { - fileIndexes_.clear(); - fs->list(&fileIndexes_, "", false, true); + fileIndexList_.clear(); + fs->list(&fileIndexList_, "", LF_INCLUDE_HIDDEN); bool foundEntry = false; bool deletedEntry = false; - for (size_t i = 0; i < fileIndexes_.size(); ++i) { - fs->getFileName(fileIndexes_[i], deleteNameBuffer_, + for (size_t i = 0; i < fileIndexList_.size(); ++i) { + fs->getFileName(fileIndexList_[i], deleteNameBuffer_, sizeof(deleteNameBuffer_)); if ((strcmp(deleteNameBuffer_, ".") == 0) || @@ -94,7 +93,7 @@ bool PersistencyService::DeleteDirectoryContents_(uint8_t depth) { foundEntry = true; - const PicoFileType type = fs->getFileType(fileIndexes_[i]); + const PicoFileType type = fs->getFileType(fileIndexList_[i]); if (type == PFT_FILE) { if (!fs->DeleteFile(deleteNameBuffer_)) { Trace::Error("PERSISTENCYSERVICE: Could not delete file: %s", @@ -194,10 +193,11 @@ PersistencyResult PersistencyService::Save(const char *projectName, Trace::Debug("get list of samples to copy from old project: %s", oldProjectName); - fs->list(&fileIndexes_, ".wav", false); + fs->list(&fileIndexList_, ".wav"); char filenameBuffer[PFILENAME_SIZE]; - for (size_t i = 0; i < fileIndexes_.size(); i++) { - fs->getFileName(fileIndexes_[i], filenameBuffer, sizeof(filenameBuffer)); + for (size_t i = 0; i < fileIndexList_.size(); i++) { + fs->getFileName(fileIndexList_[i], filenameBuffer, + sizeof(filenameBuffer)); // ignore . and .. entries as using *.wav doesnt filter them out if (strcmp(filenameBuffer, ".") == 0 || strcmp(filenameBuffer, "..") == 0) diff --git a/sources/Application/Persistency/PersistencyService.h b/sources/Application/Persistency/PersistencyService.h index 2d6647cef..f5f03835e 100644 --- a/sources/Application/Persistency/PersistencyService.h +++ b/sources/Application/Persistency/PersistencyService.h @@ -68,9 +68,9 @@ class PersistencyService : public Service, bool DeleteDirectoryTree_(const char *dirname, uint8_t depth); // need these as statically allocated buffers as too big for stack - etl::vector fileIndexes_; etl::string pathBufferA; etl::string pathBufferB; + etl::vector fileIndexList_; char deleteNameBuffer_[PFILENAME_SIZE]; }; diff --git a/sources/Application/Views/BaseClasses/UIIntField.cpp b/sources/Application/Views/BaseClasses/UIIntField.cpp index 3e90b739a..fe5ac8686 100644 --- a/sources/Application/Views/BaseClasses/UIIntField.cpp +++ b/sources/Application/Views/BaseClasses/UIIntField.cpp @@ -27,7 +27,9 @@ UIIntField::UIIntField(const GUIPoint &position, int *src, const char *format, yOffset_ = yOffset; }; -void UIIntField::Draw(GUIWindow &w) { +void UIIntField::Draw(GUIWindow &w, int offset) { + + (void)offset; GUITextProperties props; GUIPoint position = GetPosition(); diff --git a/sources/Application/Views/ImportView.cpp b/sources/Application/Views/ImportView.cpp index 4e0b8dfe8..965136308 100644 --- a/sources/Application/Views/ImportView.cpp +++ b/sources/Application/Views/ImportView.cpp @@ -8,6 +8,7 @@ */ #include "ImportView.h" + #include "Application/Audio/AudioFileStreamer.h" #include "Application/Instruments/SampleInstrument.h" #include "Application/Instruments/SamplePool.h" @@ -896,7 +897,7 @@ void ImportView::onConfirmRemoveProjectSample(View &, ModalView &dialog) { } void ImportView::refreshFileIndexList(FileSystem *fs) { - fs->list(&fileIndexList_, ".wav", false); + fs->list(&fileIndexList_, ".wav", (ListFlags)(LF_INCLUDE_HIDDEN | LF_SORTED)); if (fs->isCurrentRoot() || inProjectSampleDir_) { for (auto it = fileIndexList_.begin(); it != fileIndexList_.end(); ++it) { diff --git a/sources/Application/Views/InstrumentImportView.cpp b/sources/Application/Views/InstrumentImportView.cpp index eda74d003..989a6482f 100644 --- a/sources/Application/Views/InstrumentImportView.cpp +++ b/sources/Application/Views/InstrumentImportView.cpp @@ -344,6 +344,7 @@ void InstrumentImportView::setCurrentFolder(FileSystem *fs, const char *name) { // Update list of file indexes in this new dir fileIndexList_.clear(); // Use false for subDirOnly to include both files and directories - fs->list(&fileIndexList_, INSTRUMENT_FILE_EXTENSION, false); + fs->list(&fileIndexList_, INSTRUMENT_FILE_EXTENSION, + (ListFlags)(LF_INCLUDE_HIDDEN | LF_SORTED)); Trace::Debug("loaded %d files from %s", fileIndexList_.size(), name); } diff --git a/sources/Application/Views/InstrumentImportView.h b/sources/Application/Views/InstrumentImportView.h index ebf309156..ae0eba615 100644 --- a/sources/Application/Views/InstrumentImportView.h +++ b/sources/Application/Views/InstrumentImportView.h @@ -33,10 +33,10 @@ class InstrumentImportView : public ScreenView { private: void onImportSuccess(View &view, ModalView &dialog); + etl::vector fileIndexList_; size_t topIndex_ = 0; size_t currentIndex_ = 0; short selected_ = 0; int toInstrID_ = 0; - etl::vector fileIndexList_; }; #endif diff --git a/sources/Application/Views/SelectProjectView.cpp b/sources/Application/Views/SelectProjectView.cpp index 898bb84f0..0dda1292a 100644 --- a/sources/Application/Views/SelectProjectView.cpp +++ b/sources/Application/Views/SelectProjectView.cpp @@ -356,7 +356,7 @@ void SelectProjectView::setCurrentFolder() { fileIndexList_.clear(); // Let's read all the directory in the project dir - fs->list(&fileIndexList_, "", true); + fs->list(&fileIndexList_, "", (ListFlags)(LF_SUBDIRS_ONLY | LF_SORTED)); // Filter out "." and ".." along with the hidden default project entry for (auto it = fileIndexList_.begin(); it != fileIndexList_.end();) { diff --git a/sources/Application/Views/SelectProjectView.h b/sources/Application/Views/SelectProjectView.h index 09e8e04ac..dd2f64538 100644 --- a/sources/Application/Views/SelectProjectView.h +++ b/sources/Application/Views/SelectProjectView.h @@ -39,8 +39,8 @@ class SelectProjectView : public ScreenView { size_t topIndex_ = 0; size_t currentIndex_ = 0; char selection_[MAX_PROJECT_NAME_LENGTH + 1]; - etl::vector fileIndexList_; int selectedButton_ = 0; + etl::vector fileIndexList_; void AttemptDeletingSelectedProject(); void AttemptLoadingProject(); diff --git a/sources/Application/Views/ThemeImportView.cpp b/sources/Application/Views/ThemeImportView.cpp index 3df16f95a..3d5bdbaee 100644 --- a/sources/Application/Views/ThemeImportView.cpp +++ b/sources/Application/Views/ThemeImportView.cpp @@ -206,7 +206,7 @@ void ThemeImportView::setCurrentFolder() { isDirty_ = true; // get the directory listing - fs->list(&fileIndexList_, THEME_FILE_EXTENSION, false); + fs->list(&fileIndexList_, THEME_FILE_EXTENSION); // remove directories from listing for (int i = fileIndexList_.size() - 1; i >= 0; i--) { diff --git a/sources/System/FileSystem/FileSystem.h b/sources/System/FileSystem/FileSystem.h index 1532b19c0..09792866c 100644 --- a/sources/System/FileSystem/FileSystem.h +++ b/sources/System/FileSystem/FileSystem.h @@ -21,6 +21,13 @@ enum PicoFileType { PFT_UNKNOWN, PFT_FILE, PFT_DIR }; +enum ListFlags { + LF_NONE = 0, + LF_SUBDIRS_ONLY = 1 << 0, + LF_INCLUDE_HIDDEN = 1 << 1, + LF_SORTED = 1 << 2, +}; + // Forward declaration class I_File; @@ -37,7 +44,7 @@ class FileSystem : public T_Factory { return false; } // Default implementation virtual void list(etl::ivector *fileIndexes, const char *filter, - bool subDirOnly, bool includeHidden = false) = 0; + ListFlags flags = LF_NONE) = 0; virtual void getFileName(int index, char *name, int length) = 0; virtual PicoFileType getFileType(int index) = 0; virtual bool isParentRoot() = 0; @@ -53,6 +60,22 @@ class FileSystem : public T_Factory { virtual bool CopyFile(const char *srcFilename, const char *destFilename) = 0; virtual bool MoveFile(const char *srcFilename, const char *destFilename) = 0; virtual bool isExFat() = 0; + + // extracts a uint32 from the first four letters of a string for faster + // sorting by comparing these uint32 values instead of doing string compares + static uint32_t getFileSortKey(const char *filename) { + uint32_t sortKey = 0; + const uint8_t *value = reinterpret_cast(filename); + + for (size_t i = 0; i < 4; i++) { + char c = value[i]; + sortKey <<= 8; + if (c != '\0') { + sortKey |= (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; + } + } + return sortKey; + } }; #endif // _FILESYSTEM_H_