perf: Avoid creating strings for file extension checks (#1303)

## Summary

**What is the goal of this PR?**

This change avoids the pattern of creating a `std::string` using
`.substr` in order to compare against a file extension literal.
```c++
std::string path;
if (path.length() >= 4 && path.substr(path.length() - 4) == ".ext")
```

The `checkFileExtension` utility has moved from StringUtils to
FsHelpers, to be available to code in lib/. The signature now accepts a
`std::string_view` instead of `std::string`, which makes the single
implementation reusable for Arduino `String`.

Added utility functions for commonly repeated extensions.

These changes **save about 2 KB of flash (5,999,427 to 5,997,343)**.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
This commit is contained in:
Zach Nelson
2026-03-05 10:12:22 -06:00
committed by GitHub
parent ea88797c8e
commit c3f1dbfa09
19 changed files with 160 additions and 151 deletions
+4 -7
View File
@@ -1,6 +1,7 @@
#include "RecentBooksStore.h"
#include <Epub.h>
#include <FsHelpers.h>
#include <HalStorage.h>
#include <JsonSettingsIO.h>
#include <Logging.h>
@@ -9,8 +10,6 @@
#include <algorithm>
#include "util/StringUtils.h"
namespace {
constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 3;
constexpr char RECENT_BOOKS_FILE_BIN[] = "/.crosspoint/recent.bin";
@@ -71,19 +70,17 @@ RecentBook RecentBooksStore::getDataFromBook(std::string path) const {
// If epub, try to load the metadata for title/author and cover.
// Use buildIfMissing=false to avoid heavy epub loading on boot; getTitle()/getAuthor() may be
// blank until the book is opened, and entries with missing title are omitted from recent list.
if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) {
if (FsHelpers::hasEpubExtension(lastBookFileName)) {
Epub epub(path, "/.crosspoint");
epub.load(false, true);
return RecentBook{path, epub.getTitle(), epub.getAuthor(), epub.getThumbBmpPath()};
} else if (StringUtils::checkFileExtension(lastBookFileName, ".xtch") ||
StringUtils::checkFileExtension(lastBookFileName, ".xtc")) {
} else if (FsHelpers::hasXtcExtension(lastBookFileName)) {
// Handle XTC file
Xtc xtc(path, "/.crosspoint");
if (xtc.load()) {
return RecentBook{path, xtc.getTitle(), xtc.getAuthor(), xtc.getThumbBmpPath()};
}
} else if (StringUtils::checkFileExtension(lastBookFileName, ".txt") ||
StringUtils::checkFileExtension(lastBookFileName, ".md")) {
} else if (FsHelpers::hasTxtExtension(lastBookFileName) || FsHelpers::hasMarkdownExtension(lastBookFileName)) {
return RecentBook{path, lastBookFileName, "", ""};
}
return RecentBook{path, "", "", ""};