feat(opds): Allow selecting the download format
When downloading a book via OPDS, the last acquisition link is chosen when there are multiple. This allows choosing which format to download when there are many. Having only one format will automatically download that.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../../src/activities/browser/OpdsFormatLabel.h"
|
||||
|
||||
static int testsPassed = 0;
|
||||
static int testsFailed = 0;
|
||||
|
||||
#define ASSERT_TRUE(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT_EQ(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s != %s\n", __FILE__, __LINE__, #a, #b); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PASS() testsPassed++
|
||||
|
||||
namespace {
|
||||
OpdsAcquisitionLink makeLink(const char* href, const char* formatKey) {
|
||||
return OpdsAcquisitionLink{href, "application/epub+zip", formatKey, ".epub"};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void testUniqueFormatUsesBaseLabel() {
|
||||
printf("testUniqueFormatUsesBaseLabel...\n");
|
||||
const auto link = makeLink("/books/example.epub", "epub");
|
||||
const std::vector<OpdsAcquisitionLink> links{link};
|
||||
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(link, links, "catalog.example.com"), "EPUB");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDuplicateAbsoluteUrlsIncludeHostname() {
|
||||
printf("testDuplicateAbsoluteUrlsIncludeHostname...\n");
|
||||
const auto primary = makeLink("https://mirror-a.example.com/books/example.epub", "epub");
|
||||
const auto secondary = makeLink("https://mirror-b.example.com/books/example.epub", "epub");
|
||||
const std::vector<OpdsAcquisitionLink> links{primary, secondary};
|
||||
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(primary, links, "catalog.example.com"), "EPUB - mirror-a.example.com");
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(secondary, links, "catalog.example.com"), "EPUB - mirror-b.example.com");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDuplicateRootRelativeUrlsUseServerHostname() {
|
||||
printf("testDuplicateRootRelativeUrlsUseServerHostname...\n");
|
||||
const auto primary = makeLink("/opds/download/1/epub", "epub");
|
||||
const auto secondary = makeLink("/opds/download/2/epub", "epub");
|
||||
const std::vector<OpdsAcquisitionLink> links{primary, secondary};
|
||||
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(primary, links, "https://catalog.example.com/opds"),
|
||||
"EPUB - catalog.example.com (1)");
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(secondary, links, "https://catalog.example.com/opds"),
|
||||
"EPUB - catalog.example.com (2)");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDuplicateRelativeUrlsUseServerHostname() {
|
||||
printf("testDuplicateRelativeUrlsUseServerHostname...\n");
|
||||
const auto primary = makeLink("download/1.epub", "epub");
|
||||
const auto secondary = makeLink("download/2.epub", "epub");
|
||||
const std::vector<OpdsAcquisitionLink> links{primary, secondary};
|
||||
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(primary, links, "catalog.example.com/opds"), "EPUB - catalog.example.com (1)");
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(secondary, links, "catalog.example.com/opds"), "EPUB - catalog.example.com (2)");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDuplicateAbsoluteUrlsSameHostnameIncludeNumbering() {
|
||||
printf("testDuplicateAbsoluteUrlsSameHostnameIncludeNumbering...\n");
|
||||
const auto primary = makeLink("https://mirror.example.com/books/example.epub", "epub");
|
||||
const auto secondary = makeLink("https://mirror.example.com/books/example-copy.epub", "epub");
|
||||
const std::vector<OpdsAcquisitionLink> links{primary, secondary};
|
||||
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(primary, links, "catalog.example.com"), "EPUB - mirror.example.com (1)");
|
||||
ASSERT_EQ(opdsFormatSelectionLabel(secondary, links, "catalog.example.com"), "EPUB - mirror.example.com (2)");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testBatchLabelBuilderMatchesPerLinkLabels() {
|
||||
printf("testBatchLabelBuilderMatchesPerLinkLabels...\n");
|
||||
const auto first = makeLink("https://mirror.example.com/books/example.epub", "epub");
|
||||
const auto second = makeLink("https://mirror.example.com/books/example-copy.epub", "epub");
|
||||
const auto third = makeLink("/books/example.txt", "txt");
|
||||
const std::vector<OpdsAcquisitionLink> links{first, second, third};
|
||||
|
||||
const auto labels = buildOpdsFormatSelectionLabels(links, "https://catalog.example.com/opds");
|
||||
ASSERT_EQ(labels.size(), static_cast<size_t>(3));
|
||||
ASSERT_EQ(labels[0], opdsFormatSelectionLabel(first, links, "https://catalog.example.com/opds"));
|
||||
ASSERT_EQ(labels[1], opdsFormatSelectionLabel(second, links, "https://catalog.example.com/opds"));
|
||||
ASSERT_EQ(labels[2], opdsFormatSelectionLabel(third, links, "https://catalog.example.com/opds"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("=== OPDS Format Label Tests ===\n\n");
|
||||
|
||||
testUniqueFormatUsesBaseLabel();
|
||||
testDuplicateAbsoluteUrlsIncludeHostname();
|
||||
testDuplicateRootRelativeUrlsUseServerHostname();
|
||||
testDuplicateRelativeUrlsUseServerHostname();
|
||||
testDuplicateAbsoluteUrlsSameHostnameIncludeNumbering();
|
||||
testBatchLabelBuilderMatchesPerLinkLabels();
|
||||
|
||||
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
|
||||
return testsFailed > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
#include <OpdsParser.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
static int testsPassed = 0;
|
||||
static int testsFailed = 0;
|
||||
|
||||
#define ASSERT_TRUE(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT_EQ(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s != %s\n", __FILE__, __LINE__, #a, #b); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT_SIZE(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s == %zu, expected %zu\n", __FILE__, __LINE__, #a, static_cast<size_t>(a), \
|
||||
static_cast<size_t>(b)); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PASS() testsPassed++
|
||||
|
||||
namespace {
|
||||
bool parseSingleBookEntry(OpdsEntry& entryOut, const char* href, const char* type = "application/epub+zip") {
|
||||
const std::string xml = std::string(R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-1</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type=")") +
|
||||
type + R"(" href=")" + href + R"("/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml.data()), xml.size());
|
||||
parser.flush();
|
||||
|
||||
if (parser.error()) {
|
||||
fprintf(stderr, " FAIL: %s:%d: parser.error()\n", __FILE__, __LINE__);
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
const auto& entries = parser.getEntries();
|
||||
if (entries.size() != 1) {
|
||||
fprintf(stderr, " FAIL: %s:%d: entries.size() == %zu, expected 1\n", __FILE__, __LINE__, entries.size());
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
entryOut = entries.front();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool assertSingleFormat(const OpdsEntry& entry, const char* formatKey, const char* fileExtension) {
|
||||
if (entry.type != OpdsEntryType::BOOK) {
|
||||
fprintf(stderr, " FAIL: %s:%d: entry.type != OpdsEntryType::BOOK\n", __FILE__, __LINE__);
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
if (entry.acquisitionLinks.size() != 1) {
|
||||
fprintf(stderr, " FAIL: %s:%d: entry.acquisitionLinks.size() == %zu, expected 1\n", __FILE__, __LINE__,
|
||||
entry.acquisitionLinks.size());
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
if (entry.acquisitionLinks[0].formatKey != formatKey) {
|
||||
fprintf(stderr, " FAIL: %s:%d: formatKey actual='%s' expected='%s'\n", __FILE__, __LINE__,
|
||||
entry.acquisitionLinks[0].formatKey.c_str(), formatKey);
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
if (entry.acquisitionLinks[0].fileExtension != fileExtension) {
|
||||
fprintf(stderr, " FAIL: %s:%d: fileExtension actual='%s' expected='%s'\n", __FILE__, __LINE__,
|
||||
entry.acquisitionLinks[0].fileExtension.c_str(), fileExtension);
|
||||
testsFailed++;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void testEpubExtension() {
|
||||
printf("testEpubExtension...\n");
|
||||
OpdsEntry entry;
|
||||
if (!parseSingleBookEntry(entry, "/books/example.epub")) return;
|
||||
ASSERT_TRUE(assertSingleFormat(entry, "epub", ".epub"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testKepubDoubleExtension() {
|
||||
printf("testKepubDoubleExtension...\n");
|
||||
OpdsEntry entry;
|
||||
if (!parseSingleBookEntry(entry, "/books/example.kepub.epub")) return;
|
||||
ASSERT_TRUE(assertSingleFormat(entry, "kepub", ".kepub.epub"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testBareKepubExtension() {
|
||||
printf("testBareKepubExtension...\n");
|
||||
OpdsEntry entry;
|
||||
if (!parseSingleBookEntry(entry, "/books/example.kepub")) return;
|
||||
ASSERT_TRUE(assertSingleFormat(entry, "kepub", ".kepub.epub"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testSlashTerminatedKepubPath() {
|
||||
printf("testSlashTerminatedKepubPath...\n");
|
||||
OpdsEntry entry;
|
||||
if (!parseSingleBookEntry(entry, "/opds/download/6516/kepub/")) return;
|
||||
ASSERT_TRUE(assertSingleFormat(entry, "kepub", ".kepub.epub"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testSlashTerminatedEpubPath() {
|
||||
printf("testSlashTerminatedEpubPath...\n");
|
||||
OpdsEntry entry;
|
||||
if (!parseSingleBookEntry(entry, "/opds/download/6516/epub/")) return;
|
||||
ASSERT_TRUE(assertSingleFormat(entry, "epub", ".epub"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDistinctAcquisitionFormatsRemainSeparate() {
|
||||
printf("testDistinctAcquisitionFormatsRemainSeparate...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-2</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.kepub.epub"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="text/plain" href="/books/example.txt"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="text/markdown" href="/books/example.md"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
const auto& entries = parser.getEntries();
|
||||
ASSERT_SIZE(entries.size(), 1);
|
||||
const auto& links = entries.front().acquisitionLinks;
|
||||
ASSERT_SIZE(links.size(), 4);
|
||||
ASSERT_EQ(links[0].formatKey, "epub");
|
||||
ASSERT_EQ(links[1].formatKey, "kepub");
|
||||
ASSERT_EQ(links[2].formatKey, "txt");
|
||||
ASSERT_EQ(links[3].formatKey, "md");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testUnsupportedMimeType() {
|
||||
printf("testUnsupportedMimeType...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-3</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/x-mobipocket-ebook" href="/books/example.mobi"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
ASSERT_SIZE(parser.getEntries().size(), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testEmptyHrefOrType() {
|
||||
printf("testEmptyHrefOrType...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Empty Href</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-4</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href=""/>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Empty Type</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-5</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="" href="/books/example.epub"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Missing Href</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-6</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Missing Type</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-7</id>
|
||||
<link rel="http://opds-spec.org/acquisition" href="/books/example.epub"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
const auto& entries = parser.getEntries();
|
||||
ASSERT_SIZE(entries.size(), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testDuplicateAcquisitionLinks() {
|
||||
printf("testDuplicateAcquisitionLinks...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-8</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example-copy.epub"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
const auto& entries = parser.getEntries();
|
||||
ASSERT_SIZE(entries.size(), 1);
|
||||
const auto& links = entries.front().acquisitionLinks;
|
||||
ASSERT_SIZE(links.size(), 2);
|
||||
ASSERT_EQ(links[0].formatKey, "epub");
|
||||
ASSERT_EQ(links[0].href, "/books/example.epub");
|
||||
ASSERT_EQ(links[1].formatKey, "epub");
|
||||
ASSERT_EQ(links[1].href, "/books/example-copy.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testIdenticalHrefAcquisitionLinksAreDeduplicated() {
|
||||
printf("testIdenticalHrefAcquisitionLinksAreDeduplicated...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-9</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
const auto& entries = parser.getEntries();
|
||||
ASSERT_SIZE(entries.size(), 1);
|
||||
const auto& links = entries.front().acquisitionLinks;
|
||||
ASSERT_SIZE(links.size(), 1);
|
||||
ASSERT_EQ(links[0].formatKey, "epub");
|
||||
ASSERT_EQ(links[0].href, "/books/example.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testSlashVariantHrefAcquisitionLinksAreDeduplicated() {
|
||||
printf("testSlashVariantHrefAcquisitionLinksAreDeduplicated...\n");
|
||||
const char* xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Example Book</title>
|
||||
<author><name>Example Author</name></author>
|
||||
<id>book-10</id>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub"/>
|
||||
<link rel="http://opds-spec.org/acquisition" type="application/epub+zip" href="/books/example.epub/"/>
|
||||
</entry>
|
||||
</feed>)";
|
||||
|
||||
OpdsParser parser;
|
||||
parser.write(reinterpret_cast<const uint8_t*>(xml), strlen(xml));
|
||||
parser.flush();
|
||||
|
||||
ASSERT_TRUE(!parser.error());
|
||||
const auto& entries = parser.getEntries();
|
||||
ASSERT_SIZE(entries.size(), 1);
|
||||
const auto& links = entries.front().acquisitionLinks;
|
||||
ASSERT_SIZE(links.size(), 1);
|
||||
ASSERT_EQ(links[0].formatKey, "epub");
|
||||
ASSERT_EQ(links[0].href, "/books/example.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("=== OPDS Parser Tests ===\n\n");
|
||||
|
||||
testEpubExtension();
|
||||
testKepubDoubleExtension();
|
||||
testBareKepubExtension();
|
||||
testSlashTerminatedKepubPath();
|
||||
testSlashTerminatedEpubPath();
|
||||
testDistinctAcquisitionFormatsRemainSeparate();
|
||||
testUnsupportedMimeType();
|
||||
testEmptyHrefOrType();
|
||||
testDuplicateAcquisitionLinks();
|
||||
testIdenticalHrefAcquisitionLinksAreDeduplicated();
|
||||
testSlashVariantHrefAcquisitionLinksAreDeduplicated();
|
||||
|
||||
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
|
||||
return testsFailed > 0 ? 1 : 0;
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build/opds_format_label"
|
||||
BINARY="$BUILD_DIR/OpdsFormatLabelTest"
|
||||
PLATFORMIO_DIR="${PLATFORMIO_CORE_DIR:-$HOME/.platformio}"
|
||||
ARDUINO_FRAMEWORK_DIR="$PLATFORMIO_DIR/packages/framework-arduinoespressif32"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
SOURCES=(
|
||||
"$ROOT_DIR/test/opds_format_label/OpdsFormatLabelTest.cpp"
|
||||
"$ROOT_DIR/src/activities/browser/OpdsFormatLabel.cpp"
|
||||
"$ROOT_DIR/src/util/UrlUtils.cpp"
|
||||
)
|
||||
|
||||
CXXFLAGS=(
|
||||
-std=c++20
|
||||
-O2
|
||||
-Wall
|
||||
-Wextra
|
||||
-pedantic
|
||||
-fno-exceptions
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DDESTRUCTOR_CLOSES_FILE=1
|
||||
-I"$ROOT_DIR/test/shims"
|
||||
-I"$ROOT_DIR"
|
||||
-I"$ROOT_DIR/lib"
|
||||
-I"$ROOT_DIR/lib/OpdsParser"
|
||||
-I"$ROOT_DIR/src"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/cores/esp32"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/variants/esp32c3"
|
||||
)
|
||||
|
||||
c++ "${CXXFLAGS[@]}" "${SOURCES[@]}" -o "$BINARY"
|
||||
|
||||
"$BINARY" "$@"
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build/opds_parser"
|
||||
BINARY="$BUILD_DIR/OpdsParserTest"
|
||||
PLATFORMIO_DIR="${PLATFORMIO_CORE_DIR:-$HOME/.platformio}"
|
||||
ARDUINO_FRAMEWORK_DIR="$PLATFORMIO_DIR/packages/framework-arduinoespressif32"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
SOURCES=(
|
||||
"$ROOT_DIR/test/opds_parser/OpdsParserTest.cpp"
|
||||
"$ROOT_DIR/lib/FsHelpers/FsHelpers.cpp"
|
||||
"$ROOT_DIR/lib/OpdsParser/OpdsParser.cpp"
|
||||
)
|
||||
|
||||
CXXFLAGS=(
|
||||
-std=c++20
|
||||
-O2
|
||||
-Wall
|
||||
-Wextra
|
||||
-pedantic
|
||||
-fno-exceptions
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DDESTRUCTOR_CLOSES_FILE=1
|
||||
-DXML_GE=0
|
||||
-DXML_CONTEXT_BYTES=1024
|
||||
-DUSE_UTF8_LONG_NAMES=1
|
||||
-I"$ROOT_DIR/test/shims"
|
||||
-I"$ROOT_DIR"
|
||||
-I"$ROOT_DIR/lib"
|
||||
-I"$ROOT_DIR/lib/FsHelpers"
|
||||
-I"$ROOT_DIR/lib/OpdsParser"
|
||||
-I"$ROOT_DIR/lib/Logging"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/cores/esp32"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/variants/esp32c3"
|
||||
)
|
||||
|
||||
c++ "${CXXFLAGS[@]}" "${SOURCES[@]}" -lexpat -o "$BINARY"
|
||||
|
||||
"$BINARY" "$@"
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Print.h"
|
||||
|
||||
class HWCDC : public Print {
|
||||
public:
|
||||
void begin(unsigned long) {}
|
||||
operator bool() const { return true; }
|
||||
|
||||
size_t write(uint8_t) override { return 1; }
|
||||
size_t write(const uint8_t*, size_t size) override { return size; }
|
||||
void flush() override {}
|
||||
};
|
||||
|
||||
inline HWCDC Serial;
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class Print {
|
||||
public:
|
||||
virtual ~Print() = default;
|
||||
|
||||
virtual size_t write(uint8_t) = 0;
|
||||
|
||||
virtual size_t write(const uint8_t* buffer, size_t size) {
|
||||
size_t written = 0;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
written += write(buffer[i]);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
virtual void flush() {}
|
||||
};
|
||||
Reference in New Issue
Block a user