Skip to content

Commit

Permalink
Let plugin handle .acsm downloads instead of relying on calibre gui.e…
Browse files Browse the repository at this point in the history
…book_download. Also implement basic integration with the OverDrive Link plugin.
  • Loading branch information
ping committed Jul 9, 2023
1 parent 40e9acd commit 91e5b1c
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 111 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Unreleased
- New: Borrow and cancel holds
- New: Fave magazines to monitor for new issues
- Integration with the [OverDrive Link plugin](https://www.mobileread.com/forums/showthread.php?t=187919): If an existing book has a matching OverDrive link and no formats, the loan download will be added to the book.
- Workaround issue with converted `.acsm` downloads not having metadata
- Add option to View in OverDrive
- Sort titles with sort name instead of name
- Colored icons
Expand Down
65 changes: 34 additions & 31 deletions calibre-plugin/dialog/loans.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,30 +216,13 @@ def download_loan(self, loan: Dict):
if LibbyClient.is_downloadable_ebook_loan(loan):
show_download_info(get_media_title(loan), self)
tags = [t.strip() for t in PREFS[PreferenceKeys.TAG_EBOOKS].split(",")]
if format_id in (LibbyFormats.EBookEPubOpen, LibbyFormats.EBookPDFOpen):
# special handling required for these formats
self.download_ebook(
loan,
format_id,
filename=f'{loan["id"]}.{LibbyClient.get_file_extension(format_id)}',
tags=tags,
)
else:
endpoint_url, headers = self.client.get_loan_fulfilment_details(
loan["id"], loan["cardId"], format_id
)

def create_custom_browser():
br = browser()
for k, v in headers.items():
br.set_header(k, v)
return br

self.gui.download_ebook(
url=endpoint_url,
create_browser=create_custom_browser,
tags=tags,
)
self.download_ebook(
loan,
format_id,
filename=f'{loan["id"]}.{LibbyClient.get_file_extension(format_id)}',
tags=tags,
)

if LibbyClient.is_downloadable_magazine_loan(loan):
show_download_info(get_media_title(loan), self)
Expand All @@ -263,14 +246,32 @@ def download_ebook(
tags=[],
create_browser=None,
):
# We will handle the downloading of the files ourselves instead of depending
# on the calibre browser

# Heavily referenced from
# https://github.com/kovidgoyal/calibre/blob/58c609fa7db3a8df59981c3bf73823fa1862c392/src/calibre/gui2/ebook_download.py#L127-L152

description = _("Downloading {book}").format(
book=as_unicode(get_media_title(loan), errors="replace")
# We will handle the downloading of the files ourselves

# [OverDrive Link integration]
# If we find a book without formats and has the odid identifier matching the loan,
# add the new file as a format to the existing book record
card = self.loans_model.get_card(loan["cardId"])
library = self.loans_model.get_library(self.loans_model.get_website_id(card))
search_query = (
"format:False "
f'and identifiers:"=odid:{loan["id"]}@{library["preferredKey"]}.overdrive.com"'
)
book_ids = list(self.db.search(search_query))
book_id = book_ids[0] if book_ids else 0
mi = self.db.get_metadata(book_id) if book_id else None

description = (
_(
"Downloading {format} for {book}".format(
format=LibbyClient.get_file_extension(format_id).upper(),
book=as_unicode(get_media_title(loan), errors="replace"),
)
)
if book_id and mi
else _("Downloading {book}").format(
book=as_unicode(get_media_title(loan), errors="replace")
)
)
callback = Dispatcher(self.gui.downloaded_ebook)
job = ThreadedJob(
Expand All @@ -282,6 +283,8 @@ def download_ebook(
self.client,
loan,
format_id,
book_id,
mi,
cookie_file,
url,
filename,
Expand Down
56 changes: 56 additions & 0 deletions calibre-plugin/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import time
from pathlib import Path
from typing import List


class LibbyDownload:
def add(
self,
gui,
downloaded_file: Path,
tags: List[str] = [],
book_id: int = None,
metadata=None,
log=None,
) -> None:
db = gui.current_db.new_api
ext = downloaded_file.suffix[1:] # remove the "." suffix

if book_id and metadata:
log.info(
"Adding {ext} format to existing book {book}".format(
ext=ext.upper(), book=metadata.title
)
)
# if book_id is found, it's an OverDriveLink book, download and add the book as a format
successfully_added = db.add_format(
book_id, ext.upper(), str(downloaded_file), replace=False
)
if successfully_added:
metadata.tags.extend(tags)
db.set_metadata(book_id, metadata)
else:
# add as a new book
from calibre.ebooks.metadata.meta import get_metadata
from calibre.ebooks.metadata.worker import run_import_plugins

# we have to run_import_plugins first so that we can get
# the correct metadata for the .acsm
new_path = run_import_plugins(
(str(downloaded_file),),
time.monotonic_ns(),
str(downloaded_file.parent),
)[0]
new_ext = Path(new_path).suffix[1:]

# Reference: https://github.com/kovidgoyal/calibre/blob/58c609fa7db3a8df59981c3bf73823fa1862c392/src/calibre/gui2/ebook_download.py#L108-L116
with open(new_path, "rb") as f:
mi = get_metadata(f, new_ext, force_read_metadata=True)
mi.tags.extend(tags)

book_id = gui.library_view.model().db.create_book_entry(mi)
gui.library_view.model().db.add_format_with_hooks(
book_id, new_ext.upper(), new_path, index_is_id=True
)
gui.library_view.model().books_added(1)
gui.library_view.model().count_changed()
37 changes: 20 additions & 17 deletions calibre-plugin/ebook_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,26 @@
# information
#

import os
from typing import Dict
from pathlib import Path
from typing import Dict, Optional

from calibre.gui2.ebook_download import EbookDownload
from calibre.ptempfile import PersistentTemporaryDirectory

from .download import LibbyDownload
from .libby import LibbyClient


load_translations()

# Ref: https://github.com/kovidgoyal/calibre/blob/58c609fa7db3a8df59981c3bf73823fa1862c392/src/calibre/gui2/ebook_download.py#L77-L122
class CustomEbookDownload(EbookDownload):

class CustomEbookDownload(LibbyDownload):
def __call__(
self,
gui,
libby_client: LibbyClient,
loan: Dict,
format_id: str,
book_id=None,
metadata=None,
cookie_file=None,
url="",
filename="",
Expand All @@ -38,9 +39,9 @@ def __call__(
abort=None,
notifications=None,
):
dfilename = ""
downloaded_filepath: Optional[Path] = None
try:
dfilename = self._custom_download(
downloaded_filepath = self._custom_download(
libby_client,
loan,
format_id,
Expand All @@ -49,12 +50,12 @@ def __call__(
abort=abort,
notifications=notifications,
)
self._add(dfilename, gui, add_to_lib, tags)
self._save_as(dfilename, save_loc)
self.add(gui, downloaded_filepath, tags, book_id, metadata, log=log)

finally:
try:
if dfilename:
os.remove(dfilename)
if downloaded_filepath:
downloaded_filepath.unlink(missing_ok=True)
except:
pass

Expand All @@ -67,13 +68,15 @@ def _custom_download(
log=None,
abort=None,
notifications=None,
) -> str:
temp_path = os.path.join(PersistentTemporaryDirectory(), filename)
) -> Path:
book_folder_path = Path(PersistentTemporaryDirectory())
book_file_path = book_folder_path.joinpath(filename)

notifications.put((0.5, _("Downloading")))
res_content = libby_client.fulfill_loan_file(
loan["id"], loan["cardId"], format_id
)
with open(temp_path, "w+b") as tf:
with book_file_path.open("w+b") as tf:
tf.write(res_content)
dfilename = tf.name
return dfilename

return book_file_path
15 changes: 7 additions & 8 deletions calibre-plugin/magazine_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from urllib.parse import urlparse, urljoin

from bs4 import BeautifulSoup, Doctype, Tag, element
from calibre.gui2.ebook_download import EbookDownload
from calibre.ptempfile import PersistentTemporaryDirectory

from .libby import LibbyClient
Expand Down Expand Up @@ -56,6 +55,8 @@

load_translations()

from .download import LibbyDownload


def _sort_toc(toc: Dict) -> List:
"""
Expand Down Expand Up @@ -351,8 +352,7 @@ def _filter_content(entry: Dict, media_info: Dict, toc_pages: List[str]):
return True


# Ref: https://github.com/kovidgoyal/calibre/blob/58c609fa7db3a8df59981c3bf73823fa1862c392/src/calibre/gui2/ebook_download.py#L77-L122
class CustomMagazineDownload(EbookDownload):
class CustomMagazineDownload(LibbyDownload):
def __call__(
self,
gui,
Expand All @@ -372,11 +372,10 @@ def __call__(
):
dfilename = ""
try:
dfilename = self._custom_download(
downloaded_filepath = self._custom_download(
libby_client, loan, format_id, filename, log, abort, notifications
)
self._add(dfilename, gui, add_to_lib, tags)
self._save_as(dfilename, save_loc)
self.add(gui, downloaded_filepath, tags, None, log=log)
finally:
try:
if dfilename:
Expand All @@ -393,7 +392,7 @@ def _custom_download(
log=None,
abort=None,
notifications=None,
) -> str:
) -> Path:
logger = log
download_progress_fraction = 0.97
meta_progress_fraction = 1.0 - download_progress_fraction
Expand Down Expand Up @@ -964,4 +963,4 @@ def _custom_download(
'epub: Added "%s" as "%s"' % (zip_target_file, zip_archive_name)
)
logger.info('Saved "%s"' % epub_file_path)
return str(epub_file_path)
return epub_file_path
27 changes: 16 additions & 11 deletions calibre-plugin/translations/default.pot
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: libby-calibre-plugin 0.1.3\n"
"Report-Msgid-Bugs-To: https://github.com/ping/libby-calibre-plugin/\n"
"POT-Creation-Date: 2023-07-09 08:06+0800\n"
"POT-Creation-Date: 2023-07-09 11:08+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -95,7 +95,7 @@ msgstr ""
msgid "Invalid setup code format: {code}"
msgstr ""

#: calibre-plugin/ebook_download.py:72 calibre-plugin/magazine_download.py:676
#: calibre-plugin/ebook_download.py:75 calibre-plugin/magazine_download.py:675
msgid "Downloading"
msgstr ""

Expand All @@ -107,15 +107,15 @@ msgstr ""
msgid "Returning"
msgstr ""

#: calibre-plugin/magazine_download.py:409
#: calibre-plugin/magazine_download.py:408
msgid "Getting loan details"
msgstr ""

#: calibre-plugin/magazine_download.py:419
#: calibre-plugin/magazine_download.py:418
msgid "Downloading cover"
msgstr ""

#: calibre-plugin/magazine_download.py:448
#: calibre-plugin/magazine_download.py:447
msgid "Getting book details"
msgstr ""

Expand Down Expand Up @@ -230,7 +230,7 @@ msgid "Failed to borrow book"
msgstr ""

#: calibre-plugin/dialog/holds.py:218 calibre-plugin/dialog/holds.py:265
#: calibre-plugin/dialog/loans.py:414 calibre-plugin/dialog/magazines.py:275
#: calibre-plugin/dialog/loans.py:402 calibre-plugin/dialog/magazines.py:275
msgid "finished"
msgstr ""

Expand Down Expand Up @@ -278,28 +278,33 @@ msgstr[1] ""
msgid "Please select at least 1 loan."
msgstr ""

#: calibre-plugin/dialog/loans.py:290 calibre-plugin/dialog/loans.py:337
#: calibre-plugin/dialog/loans.py:269
#, python-brace-format
msgid "Downloading {format} for {book}"
msgstr ""

#: calibre-plugin/dialog/loans.py:275 calibre-plugin/dialog/loans.py:325
#, python-brace-format
msgid "Downloading {book}"
msgstr ""

#: calibre-plugin/dialog/loans.py:369
#: calibre-plugin/dialog/loans.py:357
#, python-brace-format
msgid "Return this loan?"
msgid_plural "Return these {n} loans?"
msgstr[0] ""
msgstr[1] ""

#: calibre-plugin/dialog/loans.py:381
#: calibre-plugin/dialog/loans.py:369
msgid "Return Loans"
msgstr ""

#: calibre-plugin/dialog/loans.py:392
#: calibre-plugin/dialog/loans.py:380
#, python-brace-format
msgid "Returning {book}"
msgstr ""

#: calibre-plugin/dialog/loans.py:411
#: calibre-plugin/dialog/loans.py:399
msgid "Failed to return loan"
msgstr ""

Expand Down
Loading

0 comments on commit 91e5b1c

Please sign in to comment.