Skip to content

Writing to OPDS library file - #1321

Open
hamazaspavetisyan wants to merge 14 commits into
mainfrom
issue-1309-write-opds
Open

Writing to OPDS library file#1321
hamazaspavetisyan wants to merge 14 commits into
mainfrom
issue-1309-write-opds

Conversation

@hamazaspavetisyan

Copy link
Copy Markdown
Collaborator

Writing an OPDS library file

Why

Continues #1319 toward #1309's goal of dropping library.xml in favor of OPDS. #1319 added OPDS reading; this PR adds writing, so a library can round-trip through OPDS alone.

What

  • Added LibOPDSDumper (mirrors LibXMLDumper) to dump a Library to an OPDS document for local/offline use, and Library::writeToOPDSFile(path) to write it. writeToFile() is now a deprecated alias for a new writeToXMLFile().
  • fullEntryOpds() gains an optional selfPath param, rendered as a rel="self" link to the book's local path — used only by LibOPDSDumper, never by the live HTTP OPDSDumper, to avoid leaking server filesystem paths.
  • The rel="search" link is now opt-in (include_search_link): still emitted for the live catalog (kiwix-serve), dropped for offline file dumps since there's no search endpoint to point at.
  • Dropped a dead friend class libXMLDumper; (case typo, never matched the real class).
  • Tests parameterized to cover both .xml and .opds round-trips.

Impact on libkiwix users/devs

No breaking API changes; writeToFile() still works as before. New writeToOPDSFile() lets callers persist a library as OPDS, readable back via Manager::readFile() (#1319) with no extra handling. kiwix-tools needs no changes.

Fundamental change

Library is no longer XML-only for serialization — OPDS output now covers both the live catalog and local file dumps through the same rendering path, differing only in what's safe to expose (paths, search links) per use case.

Notes

This PR builds on top of #1319 and should be reviewed/merged after it (this branch currently includes #1319's commits since it hasn't landed on main yet). Follow-up in kiwix-tools is expected — kiwix-manage and kiwix-serve.

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.90909% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.61%. Comparing base (335eb12) to head (e8a4765).

Files with missing lines Patch % Lines
src/library.cpp 0.00% 15 Missing ⚠️
src/libopds_dumper.cpp 52.17% 3 Missing and 8 partials ⚠️
src/book.cpp 33.33% 0 Missing and 6 partials ⚠️
src/manager.cpp 60.00% 0 Missing and 6 partials ⚠️
src/library_dumper.cpp 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1321      +/-   ##
==========================================
+ Coverage   43.54%   43.61%   +0.06%     
==========================================
  Files          60       62       +2     
  Lines        4848     4907      +59     
  Branches     2547     2580      +33     
==========================================
+ Hits         2111     2140      +29     
- Misses       1085     1098      +13     
- Partials     1652     1669      +17     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kelson42

Copy link
Copy Markdown
Collaborator

@hamazaspavetisyan AFAIK witting library.xml capacity is not fully dropped, right?

@hamazaspavetisyan

Copy link
Copy Markdown
Collaborator Author

@hamazaspavetisyan AFAIK witting library.xml capacity is not fully dropped, right?

Yes, as we discussed under previous PR, first we will have symmetric behavoiur for XML and ODPS, then will drop the XML.

@veloman-yunkan veloman-yunkan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a quick superficial review. Take it with a grain of salt.

Comment thread src/library.cpp
Comment on lines +461 to +468
std::string opds;
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
std::ostringstream oss;
dumper.dumpOPDSContent(allBookIds, oss);
opds = oss.str();
};
return writeTextFile(path, opds);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you write directly to a file via std::ofstream?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@veloman-yunkan
writeTextFile() isn't a plain ofstream wrapper — it opens the file via _wopen(Utf8ToWide(path), ...) on Windows so that UTF-8 library paths with non-ASCII characters work correctly (see pathTools.cpp:397). A raw std::ofstream(path) would go through the narrow-string/local-codepage path on Windows and break on those paths.

Comment thread test/book.cpp Outdated
Comment thread src/libopds_dumper.h
* @param bookIds the ids of the books to include.
* @param os the output stream to write the OPDS content into.
*/
void dumpOPDSContent(const std::vector<std::string>& bookIds, std::ostream& os);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we need to filter the library when saving it.

@hamazaspavetisyan hamazaspavetisyan Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@veloman-yunkan I mirrored this from dumpLibXMLContent, if we need symmetric behaviour, then having such argument is good idea

Comment thread src/libopds_dumper.h
Comment on lines +51 to +56
/**
* Set the library to dump.
*
* @param library The library to dump.
*/
void setLibrary(const Library* library) { this->library = library; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this method. In fact I don't even think that we need a user-visible class for dumping a library. It looks like a free-function void dumpLibraryOpds(const Library& lib, const std::string& outputPath) is all we need as user facing API (where baseDir is derived from outputPath). Wait! Library::writeToOPDSFile() is that function. Why can't the OPDS export be implemented there directly? I guess, because we want that functionality to be unit testable. Then we can have void dumpLibraryOpds(const Library& lib, const std::string& baseDir, std::ostream& out) as a testable workhorse and the two-parameter version as a simple wrapper around it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@veloman-yunkan I agree with you, we can remove LibOPDSDumper entirely and keep only writer method that will be called from Library::writeToOPDSFile. But again I prefer to have symmetric structure for XML and OPDS, then clean up codes in scope of separate issue. What you think ?

Comment thread src/library.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants