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:
@@ -103,14 +103,11 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
||||
pos += strlen(pattern);
|
||||
const auto endPos = coverPageHtml.find('"', pos);
|
||||
if (endPos != std::string::npos) {
|
||||
const auto ref = coverPageHtml.substr(pos, endPos - pos);
|
||||
const auto ref = std::string_view{coverPageHtml}.substr(pos, endPos - pos);
|
||||
// Check if it's an image file
|
||||
if (ref.length() >= 4) {
|
||||
const auto ext = ref.substr(ref.length() - 4);
|
||||
if (ext == ".png" || ext == ".jpg" || ext == "jpeg" || ext == ".gif") {
|
||||
imageRef = ref;
|
||||
break;
|
||||
}
|
||||
if (FsHelpers::hasPngExtension(ref) || FsHelpers::hasJpgExtension(ref) || FsHelpers::hasGifExtension(ref)) {
|
||||
imageRef = ref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = coverPageHtml.find(pattern, pos);
|
||||
@@ -541,8 +538,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||
if (FsHelpers::hasJpgExtension(coverImageHref)) {
|
||||
LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit");
|
||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||
|
||||
@@ -575,7 +571,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
return success;
|
||||
}
|
||||
|
||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".png") {
|
||||
if (FsHelpers::hasPngExtension(coverImageHref)) {
|
||||
LOG_DBG("EBP", "Generating BMP from PNG cover image (%s mode)", cropped ? "cropped" : "fit");
|
||||
const auto coverPngTempPath = getCachePath() + "/.cover.png";
|
||||
|
||||
@@ -629,8 +625,7 @@ bool Epub::generateThumbBmp(int height) const {
|
||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
||||
if (coverImageHref.empty()) {
|
||||
LOG_DBG("EBP", "No known cover image for thumbnail");
|
||||
} else if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||
} else if (FsHelpers::hasJpgExtension(coverImageHref)) {
|
||||
LOG_DBG("EBP", "Generating thumb BMP from JPG cover image");
|
||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||
|
||||
@@ -666,7 +661,7 @@ bool Epub::generateThumbBmp(int height) const {
|
||||
}
|
||||
LOG_DBG("EBP", "Generated thumb BMP from JPG cover image, success: %s", success ? "yes" : "no");
|
||||
return success;
|
||||
} else if (coverImageHref.substr(coverImageHref.length() - 4) == ".png") {
|
||||
} else if (FsHelpers::hasPngExtension(coverImageHref)) {
|
||||
LOG_DBG("EBP", "Generating thumb BMP from PNG cover image");
|
||||
const auto coverPngTempPath = getCachePath() + "/.cover.png";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "JpegToFramebufferConverter.h"
|
||||
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <JPEGDEC.h>
|
||||
@@ -486,9 +487,5 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat
|
||||
}
|
||||
|
||||
bool JpegToFramebufferConverter::supportsFormat(const std::string& extension) {
|
||||
std::string ext = extension;
|
||||
for (auto& c : ext) {
|
||||
c = tolower(c);
|
||||
}
|
||||
return (ext == ".jpg" || ext == ".jpeg");
|
||||
return FsHelpers::hasJpgExtension(extension);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "PngToFramebufferConverter.h"
|
||||
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
@@ -391,9 +392,5 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
}
|
||||
|
||||
bool PngToFramebufferConverter::supportsFormat(const std::string& extension) {
|
||||
std::string ext = extension;
|
||||
for (auto& c : ext) {
|
||||
c = tolower(c);
|
||||
}
|
||||
return (ext == ".png");
|
||||
return FsHelpers::hasPngExtension(extension);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include "FsHelpers.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
std::string FsHelpers::normalisePath(const std::string& path) {
|
||||
namespace FsHelpers {
|
||||
|
||||
std::string normalisePath(const std::string& path) {
|
||||
std::vector<std::string> components;
|
||||
std::string component;
|
||||
|
||||
@@ -37,3 +41,41 @@ std::string FsHelpers::normalisePath(const std::string& path) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool checkFileExtension(std::string_view fileName, const char* extension) {
|
||||
const size_t extLen = strlen(extension);
|
||||
if (fileName.length() < extLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t offset = fileName.length() - extLen;
|
||||
for (size_t i = 0; i < extLen; i++) {
|
||||
if (tolower(static_cast<unsigned char>(fileName[offset + i])) !=
|
||||
tolower(static_cast<unsigned char>(extension[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasJpgExtension(std::string_view fileName) {
|
||||
return checkFileExtension(fileName, ".jpg") || checkFileExtension(fileName, ".jpeg");
|
||||
}
|
||||
|
||||
bool hasPngExtension(std::string_view fileName) { return checkFileExtension(fileName, ".png"); }
|
||||
|
||||
bool hasBmpExtension(std::string_view fileName) { return checkFileExtension(fileName, ".bmp"); }
|
||||
|
||||
bool hasGifExtension(std::string_view fileName) { return checkFileExtension(fileName, ".gif"); }
|
||||
|
||||
bool hasEpubExtension(std::string_view fileName) { return checkFileExtension(fileName, ".epub"); }
|
||||
|
||||
bool hasXtcExtension(std::string_view fileName) {
|
||||
return checkFileExtension(fileName, ".xtc") || checkFileExtension(fileName, ".xtch");
|
||||
}
|
||||
|
||||
bool hasTxtExtension(std::string_view fileName) { return checkFileExtension(fileName, ".txt"); }
|
||||
|
||||
bool hasMarkdownExtension(std::string_view fileName) { return checkFileExtension(fileName, ".md"); }
|
||||
|
||||
} // namespace FsHelpers
|
||||
|
||||
@@ -1,7 +1,58 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <WString.h>
|
||||
|
||||
class FsHelpers {
|
||||
public:
|
||||
static std::string normalisePath(const std::string& path);
|
||||
};
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace FsHelpers {
|
||||
|
||||
std::string normalisePath(const std::string& path);
|
||||
|
||||
/**
|
||||
* Check if the given filename ends with the specified extension (case-insensitive).
|
||||
*/
|
||||
bool checkFileExtension(std::string_view fileName, const char* extension);
|
||||
inline bool checkFileExtension(const String& fileName, const char* extension) {
|
||||
return checkFileExtension(std::string_view{fileName.c_str(), fileName.length()}, extension);
|
||||
}
|
||||
|
||||
// Check for either .jpg or .jpeg extension (case-insensitive)
|
||||
bool hasJpgExtension(std::string_view fileName);
|
||||
inline bool hasJpgExtension(const String& fileName) {
|
||||
return hasJpgExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
|
||||
// Check for .png extension (case-insensitive)
|
||||
bool hasPngExtension(std::string_view fileName);
|
||||
inline bool hasPngExtension(const String& fileName) {
|
||||
return hasPngExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
|
||||
// Check for .bmp extension (case-insensitive)
|
||||
bool hasBmpExtension(std::string_view fileName);
|
||||
|
||||
// Check for .gif extension (case-insensitive)
|
||||
bool hasGifExtension(std::string_view fileName);
|
||||
inline bool hasGifExtension(const String& fileName) {
|
||||
return hasGifExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
|
||||
// Check for .epub extension (case-insensitive)
|
||||
bool hasEpubExtension(std::string_view fileName);
|
||||
inline bool hasEpubExtension(const String& fileName) {
|
||||
return hasEpubExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
|
||||
// Check for either .xtc or .xtch extension (case-insensitive)
|
||||
bool hasXtcExtension(std::string_view fileName);
|
||||
|
||||
// Check for .txt extension (case-insensitive)
|
||||
bool hasTxtExtension(std::string_view fileName);
|
||||
inline bool hasTxtExtension(const String& fileName) {
|
||||
return hasTxtExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
|
||||
// Check for .md extension (case-insensitive)
|
||||
bool hasMarkdownExtension(std::string_view fileName);
|
||||
|
||||
} // namespace FsHelpers
|
||||
|
||||
@@ -41,7 +41,7 @@ std::string Txt::getTitle() const {
|
||||
std::string filename = (lastSlash != std::string::npos) ? filepath.substr(lastSlash + 1) : filepath;
|
||||
|
||||
// Remove .txt extension
|
||||
if (filename.length() >= 4 && filename.substr(filename.length() - 4) == ".txt") {
|
||||
if (FsHelpers::hasTxtExtension(filename)) {
|
||||
filename = filename.substr(0, filename.length() - 4);
|
||||
}
|
||||
|
||||
@@ -112,14 +112,7 @@ bool Txt::generateCoverBmp() const {
|
||||
// Setup cache directory
|
||||
setupCacheDir();
|
||||
|
||||
// Get file extension
|
||||
const size_t len = coverImagePath.length();
|
||||
const bool isJpg =
|
||||
(len >= 4 && (coverImagePath.substr(len - 4) == ".jpg" || coverImagePath.substr(len - 4) == ".JPG")) ||
|
||||
(len >= 5 && (coverImagePath.substr(len - 5) == ".jpeg" || coverImagePath.substr(len - 5) == ".JPEG"));
|
||||
const bool isBmp = len >= 4 && (coverImagePath.substr(len - 4) == ".bmp" || coverImagePath.substr(len - 4) == ".BMP");
|
||||
|
||||
if (isBmp) {
|
||||
if (FsHelpers::hasBmpExtension(coverImagePath)) {
|
||||
// Copy BMP file to cache
|
||||
LOG_DBG("TXT", "Copying BMP cover image to cache");
|
||||
FsFile src, dst;
|
||||
@@ -139,9 +132,7 @@ bool Txt::generateCoverBmp() const {
|
||||
dst.close();
|
||||
LOG_DBG("TXT", "Copied BMP cover to cache");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isJpg) {
|
||||
} else if (FsHelpers::hasJpgExtension(coverImagePath)) {
|
||||
// Convert JPG/JPEG to BMP (same approach as Epub)
|
||||
LOG_DBG("TXT", "Generating BMP from JPG cover image");
|
||||
FsFile coverJpg, coverBmp;
|
||||
|
||||
@@ -144,14 +144,4 @@ inline const char* errorToString(XtcError err) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if filename has XTC/XTCH extension
|
||||
*/
|
||||
inline bool isXtcExtension(const char* filename) {
|
||||
if (!filename) return false;
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (!ext) return false;
|
||||
return (strcasecmp(ext, ".xtc") == 0 || strcasecmp(ext, ".xtch") == 0);
|
||||
}
|
||||
|
||||
} // namespace xtc
|
||||
|
||||
Reference in New Issue
Block a user