-
-
Notifications
You must be signed in to change notification settings - Fork 341
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
Koreader progress sync interface #3025
Koreader progress sync interface #3025
Conversation
This PR is not ready for a merge (if that isn't already apparent.) I wanted to show you the basic interface that I'd like to implement and get some feedback on it before diving in. Here are some relevant details that will help you understand the scope of the proposed change. As it currently stands, you can read books on Koreader by using OPDS and stream the book from Kavita. Unfortunately, I have two issues with this. First, I haven't even gotten it to work yet. I've only tried a handful of times, but it always produces just the Koreader logo. It might simply be user error, but if you like, I can investigate further and open an issue if necessary. Second, what if I'm away from my LAN and want to sync my books? What if the power goes out? Well too bad. This feature should solve that issue. Koreader has a plugin that ships with the install called 'progress sync'. It is on by default. It allows a user to connect to a Koreader sync server, to sync progress across Koreader devices. We can use this functionality within Koreader to sync progress between Koreader devices and Kavita. A user would only need to us OPDS to download the book onto their Koreader device, then authenticate to the server in the progress sync page, and poof! The Koreader device and Kavita can sync progress! The user will have to enter the following url into their Koreader device: 'http://my-kavita-server.com/api/koreader/api-key/'. Then they will login using their username, and the password can be anything, as the authentication happens through the api-key. (I don't think it makes sense to use the user's actual password, as Koreader sends the MD5 hash of the password to the server. Since Kavita doesn't store passwords as an MD5, it would make implementing this much more difficult.) Alternatively we could require the user to use the api-key as the password. Your advice would be appreciated. Koreader allows you to register as a user. I think it's in our best interest to leave that functionality out. There are Put and Get endpoints that allow for syncing between the device and the server. Koreader uses the MD5 hash of the book (or filename as a secondary option) to identify what book is being synced. I'm not sure how to handle the device id stuff yet, but that seems like an implementation concern. Let me know what you think. I'd love to implement this functionality. |
First off, I would welcome a PR for this (and any needed PR for OPDS and Koreader).
I agree. I don't want to use passwords with external applications, hence why we have the api key in the first place.
Agreed. External user provisioning is not something I'm a fan of for Kavita. My main points of concern are this:
Let me know if you need anything either via Github or via discord. |
Wonderful! I'll be working on this over the next couple of weeks. I've looked through code, and I think I have a general idea of how to accomplish it. Thanks for your help! |
Not sure why this says it was closed by me. I had no intention of closing it yet. |
Awesome. I think AutoMapper can help with that as much as possible. It's okay if the PR gets closed, as long as you re-open once you're ready for some more review. |
@majora2007 I've made some more changes and have it working locally. When reading in koreader, the progress is updated on kavita, and vice versa. However, I don't feel confident that I've accounted for every potential "bookScrollId". I wanted to know what you thought about releasing this feature in beta to start off with, with the expectation that it might break here and there. I'll get this branch caught up on the develop branch in the next couple of days, improve the logging, and start preparing the Wiki entry. Could you perform a quick once over to give me any feedback on it? |
Yeah, ill go over it and give you a review. I think we can probably get this into the current nightly, since we still have some good amount of time before the next stable goes out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of style comments and the need for documentation to get the full gist.
I def would recommend adding some links to places where you learned to reverse engineer, which will not only help me, but any other project that wants to add this sort of support.
API/Services/KoreaderService.cs
Outdated
Device_id = "kavita", | ||
Device = "kavita", | ||
Progress = koreaderProgress, | ||
Percentage = 0.5f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why we are sending a percentage if it's not correct...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By what I've seen, Koreader doesn't appear to use this value. I think it could be used by the Koreader progress sync server to display statistics or something. I'll see if I can track it down a little better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I've been looking at this even more, and it appears to be only for message boxes.
A little earlier in the method it compares against a previous percentage and a new percentage to see if if is in the same position. If so, it isn't supposed to update the progress in Koreader. However, in testing it doesn't behave that way. Maybe it has to do with direct comparison of a floating point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it's a user friendly percentage. Can we not use the Progress mapped to a 0-1 float?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Someone from Stump did some research on this:
stumpapp/stump#239 (comment)
@@ -818,6 +818,7 @@ private void AddOrUpdateFileForChapter(Chapter chapter, ParserInfo info, bool fo | |||
var file = new MangaFileBuilder(info.FullFilePath, info.Format, _readingItemService.GetNumberOfPages(info.FullFilePath, info.Format)) | |||
.WithExtension(fileInfo.Extension) | |||
.WithBytes(fileInfo.Length) | |||
.WithHash() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to know the overhead of hashing files, perhaps a benchmark can help us understand. (See the Benchmark project)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I set up the benchmark and will be committing it soon. I set up the benchmark to build a MangaFile using the MangaFileBuilder. I went ahead and ran the benchmark on hashing a 300 KB epub. Here are the results:
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Allocated | Alloc Ratio |
|------------------------ |----------:|----------:|----------:|----------:|------:|--------:|-----:|----------:|------------:|
| TestBuildManga_baseline | 40.18 us | 1.631 us | 4.731 us | 38.49 us | 1.00 | 0.00 | 1 | 922 B | 1.00 |
| TestBuildManga_withHash | 230.09 us | 10.654 us | 31.415 us | 229.86 us | 5.81 | 0.99 | 2 | 7151 B | 7.76 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing to keep in mind about the hashing algorithm is that a 300KB file will only have 4KB of its contents hashed. At most, the algorithm will hash 10 KB of the file. That means that even though there are files much larger than 300KB, they shouldn't take that much longer to hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Koreader, is this only for epub files? Because if so, I think we could swallow this type of overhead. If it's for archives as well, then we might want to look into offloading this into a separate task that performs the hashing. This will offload the extra spend on the scan loop and shortly make it available after scan for syncing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched it to only hash EPUBs. I don't think there is any sense in hashing the other files as I don't believe Koreader supports Archives. It supports PDFs though. We can add support for that later.
@majora2007, I've merged the most recent changes into my branch and updated the PR. I've made a few small changes since the last time I ran the project. however, I noticed that I can't run the project anymore. I launch the API project and it directs me to localhost, but it returns an HTTP 500.30. I even pulled straight from the Kavita develop branch, and am getting the same result. I'm not sure if it is my local setup, or if there is something wrong in develop. Regardless, I'd like to run a few quick tests, then I'm ready for an in depth review. If we can resolve the HTTP 500 error, then I'll run those tests and get it to you to review. |
I just pulled down your code and the webapp is working just fine, I'm not able to reproduce any 500 errors. Are you also launching the webui server as well with angular? You should be going to localhost:4200/ |
API/Entities/MangaFile.cs
Outdated
@@ -21,6 +21,10 @@ public class MangaFile : IEntityDate | |||
/// </summary> | |||
public required string FilePath { get; set; } | |||
/// <summary> | |||
/// A hash of the document using Koreader's unique hashing algorithm | |||
/// </summary> | |||
public string KoreaderHash { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be string?
as we aren't using for all file types. I would also update <remarks>
to include information about the supported file types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a code review, I will pull this down soon and start testing the heck out of it.
@DieselTech if you want to also start testing.
We missed our opportunity for the v0.8.3 release, but v0.8.4 development is right around the corner and I know many people will want this feature.
{ | ||
_mangaFile.KoreaderHash = KoreaderHelper.HashContents(_mangaFile.FilePath); | ||
} | ||
return this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newlines above returns
API/Helpers/KoreaderHelper.cs
Outdated
/// <param name="filePath">The path to the file to hash</param> | ||
public static string HashContents(string filePath) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove empty line
API/Helpers/KoreaderHelper.cs
Outdated
|
||
using var file = File.OpenRead(filePath); | ||
|
||
int step = 1024; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use var whenever possible
var fileName = Path.GetFileName(filePath); | ||
var fileNameBytes = Encoding.ASCII.GetBytes(fileName); | ||
var bytes = MD5.HashData(fileNameBytes); | ||
return BitConverter.ToString(bytes).Replace("-", string.Empty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline above returns
API/Services/KoreaderService.cs
Outdated
Device_id = "kavita", | ||
Device = "kavita", | ||
Progress = koreaderProgress, | ||
Percentage = 0.5f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it's a user friendly percentage. Can we not use the Progress mapped to a 0-1 float?
@majora2007 I fixed the stylistic concerns you had. I'm not sure how exactly to map a progress to a float though. It seems like it could be difficult, because of the way Kavita tracks a user's progress. I could use currentPage/totalPages, but that isn't the same way that Koreader tracks progress, and would lead to discrepancies. Do you have any other ideas? |
Well Kavita does technically use currentPage/totalPage. It just stores an additional xpath to resume scroll position, but the underlying progress would be currentPage/totalPage. This would ensure that the modal on Koreader side shows something correct. I'll try and pull this down this weekend and give it a round of review. I don't have Koreader, but we can deploy this to a test instance and get some testers before we merge it into develop. |
@DieselTech Can we get this deployed to the dev instance so we can ask users to test it? I don't want to merge into nightly yet without more validation testing from people with actual devices. We can lock the users down to dedicated testers. |
Just to update you, I'm planning to put this in a canary and asking for some testing before I merge it into develop. Canary is currently being used for a large refactor of the scan loop, to ensure it's not breaking anything before moving the code into nightly/develop. Once that wraps, this is up next. |
This feature is great, I’m looking forward to it very much. |
Just an update, canary should be coming to an end today/tomorrow. It's been nearly a week of just working on rewriting the scanner but we are at a good merge point. Once that occurs, I will be creating a new canary with this for testing (which should be quick, I already have a user to perform the extended testing). Thank you for your patience. With bigger PRs, it's critical to ensure things work well before merging in and taking ownership. |
If you could resolve the conflicts then I'll get you merged into canary and start the testing. Discussion for testing: |
Hi @majora2007, apologies for the delay. I've never worked with synchronizing a fork. I finally understand what having multiple remotes is for now, so I think I'll be a little better at merging these things. Anyways, I've brought the branch up to date with the feature/koreader-sync branch. |
I'm going to do some cleanup for you and once done, I encourage you to read over the upcoming PR into canary. Some items I need from you:
|
@MFDeAngelo A few more things that I'll need implementation help on. In the KoreaderService, there are a lot of cases of data coming back null that aren't being handled. I'm going to augment the code with TODOs and push it up. Please branch off my branch and get everything in order. You wont have to deal with any merges anymore since the code is in my own branch. I just need help since I don't have Koreader setup. You can see the TODOs on the PR: |
Added