Skip to content

Exporter: XMNote support exporting read time and mark book source#14144

Merged
Frenzie merged 6 commits into
koreader:masterfrom
Merpyzf:upgrade-xmnote-exporter
Oct 15, 2025
Merged

Exporter: XMNote support exporting read time and mark book source#14144
Frenzie merged 6 commits into
koreader:masterfrom
Merpyzf:upgrade-xmnote-exporter

Conversation

@Merpyzf

@Merpyzf Merpyzf commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

XMNote users have long requested the ability to export daily reading time records from KOReader. This PR adds support for exporting reading time within the existing XMNote exporter plugin.

Additionally, to help XMNote App better organize imported books, each exported book is now marked with its source as “KOReader”.


This change is Reviewable

@Merpyzf

Merpyzf commented Sep 14, 2025

Copy link
Copy Markdown
Contributor Author

Hi, sorry to bother you. Perhaps pazos is busy and hasn’t had time to review my code. Would it be possible to assign this review to someone else? Thanks! @Frenzie

@Frenzie

Frenzie commented Sep 14, 2025

Copy link
Copy Markdown
Member

I'm not really sure if anybody else knows the Exporter plugin nearly as well but I'll take a quick look.

Comment on lines +77 to +78
function XMNoteExporter:getBookReadingDurations(id_book)
local conn = SQ3.open(db_location)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see two presumably fairly minor issues with this:

  1. It assumes the DB exists, but that's predicated on the statistics plugin being enabled. You'd have to include something like if not self.ui.statistics then return end.
  2. This seems to look sufficiently generic that most of it should probably be located somewhere else, like ReaderStatistics itself or Exporter base. I'd go for ReaderStatistics itself unless there's a good reason not to.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pinging @poire-z regarding ReaderStatistics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The purpose of this query is to align with the field requirements defined by the export App’s API, which makes it fairly specific to the export logic. From a modular perspective, I agree that reading statistics generally belongs in ReaderStatistics. However, since this query is strongly coupled to the API field definitions, I decided to keep it in xmnote.lua to avoid introducing API-specific logic into a generic module.

Would you agree with this reasoning, or do you think it would still be better to move the core query into ReaderStatistics and leave only the API adaptation in xmnote.lua?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No strong opinion.
It indeed feels strange to have in this "remote" module some SQL query with internal knowledge about how the stats db is set up.
But I'm fine with not having in the statistics plugin tons of functions with SQL queries that are not used in it or by core :)

I wondered what this SQL query returns. I see that it gives results like:

dates       last_page  durations  min_start_time
----------  ---------  ---------  --------------
2025-09-10  9          78         1757538978
2025-08-31  11         14         1756656993
2025-08-30  11         20         1756560788
2025-08-28  11         46         1756367532
2025-08-25  12         126        1756125006
2025-08-09  14         417        1754694878

So, dunno. If put in the stats plugin, it should have a more explicite name, maybe getBookReadingDurationsByDay(id_book).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mainly I was thinking that if this is useful for one export implementation maybe it also is for others. For example for Markdown/HTML the output from the same query could be written as a table at the bottom. But for now let's keep it in here, just with that early return right at the start.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If multiple export implementations need something from the stats db, may be it could just be an other specific module inside the exporter plugin (with a not in the statistics plugin that there are additional SQL queries in the exporter plugin).

@Frenzie Frenzie Sep 14, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I did write "or Exporter base." ;-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No strong opinion. It indeed feels strange to have in this "remote" module some SQL query with internal knowledge about how the stats db is set up. But I'm fine with not having in the statistics plugin tons of functions with SQL queries that are not used in it or by core :)

I wondered what this SQL query returns. I see that it gives results like:

dates       last_page  durations  min_start_time
----------  ---------  ---------  --------------
2025-09-10  9          78         1757538978
2025-08-31  11         14         1756656993
2025-08-30  11         20         1756560788
2025-08-28  11         46         1756367532
2025-08-25  12         126        1756125006
2025-08-09  14         417        1754694878

So, dunno. If put in the stats plugin, it should have a more explicite name, maybe getBookReadingDurationsByDay(id_book).

I’m querying a book’s daily reading stats across all dates, capturing each day’s first reading time, the last page read, and the total reading duration. I’ll also adjust the SQL aliases to make them clearer and easier to understand.

Comment on lines +111 to +117
SELECT
id
FROM
book
WHERE
title = "%s"
LIMIT 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not important but for such a simple query this seems a bit silly. :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I’ll change it to a single line. Thanks!

@Frenzie Frenzie added this to the 2025.10 milestone Sep 14, 2025
return reading_durations
end

function XMNoteExporter:getBookIdByTitle(title)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Rare case, but book title may be not unique.

function ReaderStatistics:getIdBookDB()
local conn = SQ3.open(db_location)
local id_book
local sql_stmt = [[
SELECT count(id)
FROM book
WHERE title = ?
AND authors = ?
AND md5 = ?;
]]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If needed we can fetch the md5 from doc_settings along with title and author

clippings[title] = {
file = doc_path,
title = title,
author = author,
number_of_pages = doc_settings:readSetting("doc_pages"),
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I’ll work on optimizing it.

Comment on lines +9 to +12
local util = require("util")
local ffiUtil = require("ffi/util")
local datetime = require("datetime")
local T = ffiUtil.template

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you sort the new additions as per the coding style? https://github.com/koreader/koreader-base/wiki/Coding-style#order-of-requires

datetime
ffiUtil
logger
util
_
T

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I haven’t paid attention to that document before. If I had known, I would have done it. I’ll make the changes now!

@Merpyzf

Merpyzf commented Oct 12, 2025

Copy link
Copy Markdown
Contributor Author

Hello, the CI check for this PR did not pass. After reviewing the logs, the error doesn't appear to be related to my code changes, which only involved adjusting the order of require statements. Would it be possible to manually re-trigger the pipeline? Thank you for your time.

end

function XMNoteExporter:getBookReadingDurationsByDay(id_book)
if util.fileExists(db_location) then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to check again, already done in findBookIdByTitleAndMD5().

@Frenzie Frenzie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@hius07 Anything else?

@hius07

hius07 commented Oct 14, 2025

Copy link
Copy Markdown
Member

Everything looks correct, just a small note: do we need a separate findBookIdByTitleAndMD5()?
Can it be just a part of getBookReadingDurationsByDay()?
Currently the db is opened and closed twice.

Of course, if there are plans to get other info from the db in the future, it's okay.

@Merpyzf

Merpyzf commented Oct 15, 2025

Copy link
Copy Markdown
Contributor Author

In this case, there’s no need to split it.

@Frenzie Frenzie merged commit 158463f into koreader:master Oct 15, 2025
4 checks passed
0xstillb pushed a commit to 0xstillb/koreader-thai that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants