Skip to content

Custom metadata #10861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 1, 2023
Merged

Custom metadata #10861

merged 17 commits into from
Sep 1, 2023

Conversation

hius07
Copy link
Member

@hius07 hius07 commented Aug 30, 2023

Long-press on a property in the Book information window to customize it.
custom_props and original doc_props are saved to custom_metadata.lua in sdr folder.

01

02

04

05

Maybe it's better to reorder the items, to put non-prop "Pages" to the end, closer to other numbers.

06


This change is Reviewable

@hius07
Copy link
Member Author

hius07 commented Aug 30, 2023

TODO:
(1) Exporter plugin
(2) Reset button in the "long-press on a file" dialog: show 3 checkboxes for resetting separately document settings, custom cover, custom metadata.

Copy link
Contributor

@poire-z poire-z left a comment

Choose a reason for hiding this comment

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

(Just some early comments, have to run, will review more later today.)

@@ -16,6 +16,7 @@ local DocSettings = LuaSettings:extend{}

local HISTORY_DIR = DataStorage:getHistoryDir()
local DOCSETTINGS_DIR = DataStorage:getDocSettingsDir()
local custom_metadata_filename = "/custom_metadata.lua"
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads odd, as meaning that the file with be in the root folder /custom_metadata.lua.
I guess you want to avoid an additional concatenation .. "/" .. custom_metadata_filename, but still...

Copy link
Member Author

Choose a reason for hiding this comment

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

to avoid an additional concatenation

Yes, that's the only purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still "reads" odd :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 351 to 341
function DocSettings:removeSidecarDir(doc_path, sidecar_dir)
if sidecar_dir == self:getSidecarDir(doc_path, "doc") then
function DocSettings.removeSidecarDir(doc_path, sidecar_dir)
if sidecar_dir == DocSettings:getSidecarDir(doc_path, "doc") then
Copy link
Contributor

Choose a reason for hiding this comment

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

There and below (and maybe elsewhere).
DocSettings:getSidecarDir with a : reads odd (and feels like a typo, and have me wondering what will happen if the DocSettings module table is used as the object self.)
It should be either DocSettings.getSidecarDir() with a . when it's a pure function, and no self object is needed.
Or self:getSidecarDir() with a : (as it was), if it looks better as a method and having an object self, even if self is not used.

I see that there, defining a function DocSettings.removeSidecarDir(), you need to use the method DocSettings:getSidecarDir() (probably because it's often used as a method), so it's indeed complicated.
Dunno how to make that clearer. Should if be made as a method DocSettings:removeSidecarDir() because it uses other methods ?
Not sure what's the best/cleaner. @Frenzie @NiLuJe ?
(Or may be it's just me on an early morning seeing evil where there is not :/)

Copy link
Member Author

@hius07 hius07 Aug 30, 2023

Choose a reason for hiding this comment

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

I've started here and would like to continue transforming methods that do not require self, to functions.
It is a code optimization, and is clearer to read.
In DocSettings only flush, purge and getCoverFile require self.
EDIT: and new flushCustomMetadata.

Copy link
Member Author

Choose a reason for hiding this comment

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

you need to use the method DocSettings:getSidecarDir() (probably because it's often used as a method)

Yes, I tried to avoid massive refactoring not related to custom metadata.

Copy link
Member

@NiLuJe NiLuJe Aug 30, 2023

Choose a reason for hiding this comment

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

and is clearer to read.

Disagree, for pretty much the reasons @poire-z pointed out.

I'd be fine with it if the whole module was pure, but this isn't the case here.

The only way I would see that viable is to fork those off to a dedicated LuaSettingsUtil module, but, again, I very strongly would suggest not bothering with that, especially if it involves a lot of code churn in existing code.

Copy link
Member

Choose a reason for hiding this comment

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

To my mind using self the way it was is clearer too (in fact I'm not sure I didn't write it in the first place 😅).

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

original_props = original_props or {}

local props = {}
for _i, prop_key in ipairs(BookInfo.props) do
for _, prop_key in ipairs(BookInfo.props) do
props[prop_key] = custom_props[prop_key] or original_props[prop_key]
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Still feeling setting display_title should be the job of this customizeProps() ?
If it really is the place where we "extend" the plain book metadata (with pages, display_title and custom metadata), may be the its name should be changed to extendProps() ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines +236 to +238
-- but custom metadata exists, it has a copy of original doc_props
if not book_props then
local custom_metadata_file = DocSettings:getCustomMetadataFile(file)
Copy link
Contributor

Choose a reason for hiding this comment

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

(I have not yet met the custom_metadata code while reading) but should custom_metadata really contain a copy of the orignal book_props, even if not modified ?
I would think it should contain only key/value of modified props.

edit: ok, seems you save both original doc_props and custom ones (see my other comment below).

Comment on lines -281 to -283
-- Description may (often in EPUB, but not always) or may not (rarely
-- in PDF) be HTML.
description = util.htmlToPlainTextIfHtml(description)
Copy link
Contributor

Choose a reason for hiding this comment

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

(Was a bit sad that you removed my wildlife observation notes. But it is duplicated elsewhere, so not yet lost :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry for that, but there is another one:

-- Description may (often in EPUB, but not always) or may not (rarely in PDF) be HTML

function BookInfo:showCustomEditDialog(file, book_props, metadata_updated_caller_callback, prop_key)
local input_dialog
input_dialog = InputDialog:new{
title = _("Edit book property: ") .. self.prop_text[prop_key]:sub(1, -2),
Copy link
Contributor

Choose a reason for hiding this comment

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

_("Title:"), when translated in French (haven't checked) may be _("Titre :") with a space or thin-space before the :.
I guess no issue if there's a trailing space in the text of the title you make for that widget, but dunno about other languages and how they would translate these (and if we should include the : in the translatable strings or not (and have the code append it, but then French won't get that space).
@Frenzie : thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Looks to me like it should be Edit book property: %1 instead of trying to figure out how to concatenate things, but iff template isn't appropriate for some reason the space should be concatenated without bothering the translator with it, as in Edit book property: (colon at the end, no space at the end).

Copy link
Contributor

@poire-z poire-z Aug 31, 2023

Choose a reason for hiding this comment

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

My comment was more about your %1, from https://github.com/koreader/koreader/pull/10861/files#diff-10106ca66f5be0b0a2554fc344220689d85fa930768116d5650e655c558f465bR32
the list of the keys of the Book info KVPage, which includes their :, and is reused there to make the title "Edit this property", without the there uneeded : .

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, there's a good chance that's being too clever yeah, although your French example is probably unproblematic (a tiny bit of extra space). But if Chinese uses some utf8 full width colon for example does it still work out...

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed (replace ":" to "").

Comment on lines +467 to +468
if original_prop and prop_key == "description" then
original_prop = util.htmlToPlainTextIfHtml(original_prop)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, if editing Description, and it was HTML, we'll start editing the TEXT version of it?
I guess that's ok and the most sane, to avoid getting bad/unbalanced HTML in the custom Description.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, currently BookInfo:showCustomEditDialog() takes unstripped html description.
Should be fixed.

I see that we do not use original HTML'ed description anywhere.
Maybe clean it at the source, in Document:getProps()?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see that we do not use original HTML'ed description anywhere.
Maybe clean it at the source, in Document:getProps()?

My preference in general is to save the orignal stuff as it is, original :)
If we later fix/tweak the cleaning code, it will apply immediately, without the need to refresh book metadata.
And maybe one day we'll want to display it as HTML (I think there was an issue about it - the resulting text being bad, that we solved another way).

But then I don't know how this plays with "custom description" :/ I guess the custom description should be plain text - dunno if where the htmlToPlainText stuff happens is too late to know about that.

Comment on lines +462 to +463
original_prop = self.custom_doc_settings:readSetting("doc_props")[prop_key]
custom_prop = self.custom_doc_settings:readSetting("custom_props")[prop_key]
Copy link
Contributor

Choose a reason for hiding this comment

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

There are both the original doc_props and the customized (only those modified?) in the custom_metadata.lua ?
Can you paste some sample of this custom_metadata.lua content?

Copy link
Member Author

Choose a reason for hiding this comment

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

-- we can read Lua syntax here!
return {
    ["custom_props"] = {
        ["authors"] = "Dariusz Terefenko",
        ["series"] = "Jazz",
        ["series_index"] = 3,
    },
    ["doc_props"] = {
        ["authors"] = "Terefenko, Dariusz;",
        ["language"] = "En",
        ["pages"] = 824,
        ["title"] = "Jazz Theory",
    },
}

@poire-z
Copy link
Contributor

poire-z commented Aug 31, 2023

Maybe it's better to reorder the items, to put non-prop "Pages" to the end, closer to other numbers.

Yes, might looks better.
(Dunno about people so used to find "Cover image: tap to display" at the bottom, but it's not something we use everyday.)

@@ -270,7 +269,7 @@ function BookInfo.getDocProps(file, book_props, no_open_document, no_customize)
end
end

return no_customize and (book_props or {}) or BookInfo.customizeProps(book_props, file)
return no_customize and (book_props or {}) or BookInfo.extendProps(book_props, file)
Copy link
Contributor

Choose a reason for hiding this comment

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

Guess no_customize could be renamed to not_extended or plain or raw or something else like that.

@poire-z
Copy link
Contributor

poire-z commented Aug 31, 2023

remove unused no_customize

Good :)
What happened to the place that needed that ?

@hius07
Copy link
Member Author

hius07 commented Aug 31, 2023

What happened to the place that needed that ?

Eventually, it never existed.

@hius07 hius07 merged commit ed2ea68 into koreader:master Sep 1, 2023
@hius07 hius07 deleted the custom-metadata branch September 1, 2023 05:07
@hius07 hius07 added this to the 2023.09 milestone Sep 1, 2023
@poire-z
Copy link
Contributor

poire-z commented Sep 1, 2023

Some issue with this and the Reading statistics.
If I've read a book and swap its author and title, the statistics are gone, and new ones are created. If I read a few pages and fix the author or title again, a third book appear in the statistics and my Daily view.
Dunno which metadata affects other modules, and which metadata distinguish a book in the Statistics (authors and title probably, language and series I don't know).
May be it's worth to broadcast an event to inform other modules of some metadata changes, with previous and new values, so they can react on their side as needed.
For the statistics plugin, I don't know what should be done (don't really want to find out :/)

NiLuJe added a commit to NiLuJe/koreader that referenced this pull request Sep 1, 2023
Allows me to fix an old pet-peeve of mine re: the terrible variable
names used in there ;o).
@Frenzie
Copy link
Member

Frenzie commented Sep 1, 2023

I thought the stats were based on some kind of md5 and/or file location, but I guess that's the sync?

@poire-z
Copy link
Contributor

poire-z commented Sep 1, 2023

Looks like it is the 3 items (title, authors, md5) that are used to find the book ID (ReaderStatistics:getIdBookDB()).
Not using the filepath makes it easy for us when moving files around, not having to update anything :)

@Frenzie
Copy link
Member

Frenzie commented Sep 1, 2023

Yes, agreed on not using the path. Using the title and authors seems a bit redundant given the md5.

@poire-z
Copy link
Contributor

poire-z commented Sep 1, 2023

Using the title and authors seems a bit redundant given the md5.

Indeed :)
I remember of one request for having multi-users KOReader (so one and his partner can read the same book on a shared single device and have different reading progress, highlights...) that I suggested it was just simpler to copy/duplicate the book :) But then, with (title, authors, md5) and no filepath, the stats would me merged anyway.
But now that we can customize metadata, that solution may work again: rename book title to "Moby Dick (John)" and "Moby Dick (Jane)" for the copy, and it's fine.

@hius07 hius07 mentioned this pull request Sep 2, 2023
NiLuJe added a commit that referenced this pull request Sep 6, 2023
Allows me to fix an old pet-peeve of mine re: the terrible variable
names used in there ;o).
@poire-z
Copy link
Contributor

poire-z commented Sep 10, 2023

Looks like that in customization, I can't "empty" a field (to remove trash, ie. from keywords, if we later have "browse by metadata/keyword").
Not sure it's a real use case and if we should care about that - just mentionning it.
(One can always set it to a single space to have the appearance it's empty.)

@hius07
Copy link
Member Author

hius07 commented Sep 10, 2023

I thought about that when coded the input dialog.
We cannot make a custom field nil.
We can make it false but it would complicate all the checks.
So I decided that a single space would work.

@hius07
Copy link
Member Author

hius07 commented Sep 10, 2023

It also cannot be "" because there are some checks that replace it with "N/A".

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.

4 participants