Add setting to choose between parallel or sequencial fulfillment

pull/66/head
Florian Bach 2 years ago
parent 3bd4d28936
commit 4dfc7af7b8

@ -47,10 +47,13 @@
# rename plugin from "DeACSM" to "ACSM Input". BETA build, not a normal release!! # rename plugin from "DeACSM" to "ACSM Input". BETA build, not a normal release!!
# #
# v0.1.0: Continue work on renaming from "DeACSM" to "ACSM Input". # v0.1.0: Continue work on renaming from "DeACSM" to "ACSM Input".
# The big version number jump is to make that name change clearer. # The big version number jump is to make that name change clearer,
# and to support the "migration plugin" to rename the plugin.
# Print useful warning if LicenseServiceCertificate download fails, # Print useful warning if LicenseServiceCertificate download fails,
# fix error with the loan list not being updated when importing multiple ACSMs at once, # fix error with the loan list not being updated when importing multiple ACSMs at once,
# fix bug with the GUI extension in non-English environments, # fix bug with the GUI extension in non-English environments,
# add setting to choose between simultaneous (faster) or sequencial (more ADE-like)
# import of multiple ACSM files
@ -63,7 +66,6 @@ __version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
from calibre.utils.config import config_dir # type: ignore from calibre.utils.config import config_dir # type: ignore
from calibre.constants import islinux # type: ignore
import os, shutil, traceback, sys, time, io, random import os, shutil, traceback, sys, time, io, random
import zipfile import zipfile
@ -406,146 +408,217 @@ class ACSMInput(FileTypePlugin):
print("{0} v{1}: Error: Unsupported file type ...".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Error: Unsupported file type ...".format(PLUGIN_NAME, PLUGIN_VERSION))
return None return None
def is_blocked(self):
import calibre_plugins.deacsm.prefs as prefs # type: ignore
deacsmprefs = prefs.ACSMInput_Prefs()
return deacsmprefs['fulfillment_block_token'] != 0
def unblock(self):
import calibre_plugins.deacsm.prefs as prefs # type: ignore
deacsmprefs = prefs.ACSMInput_Prefs()
my_token = deacsmprefs["fulfillment_block_token"]
deacsmprefs.refresh()
if (my_token == deacsmprefs["fulfillment_block_token"]):
# Only unlock if this is my own lock
deacsmprefs.set("fulfillment_block_token", 0)
deacsmprefs.set("fulfillment_block_time", 0)
deacsmprefs.commit()
def wait_and_block(self):
random_identifier = None
import calibre_plugins.deacsm.prefs as prefs # type: ignore
deacsmprefs = prefs.ACSMInput_Prefs()
while True:
deacsmprefs.refresh()
if deacsmprefs["fulfillment_block_token"] == 0:
random_identifier = random.getrandbits(64)
#print("setting block token to %s" % (str(random_identifier)))
deacsmprefs.set("fulfillment_block_token", random_identifier)
deacsmprefs.commit()
deacsmprefs.refresh()
if random_identifier != deacsmprefs["fulfillment_block_token"]:
# print("we broke another thread's global token")
continue
deacsmprefs.set("fulfillment_block_time", int(time.time() * 1000))
#print("Obtained lock!")
return True
else:
# Token already exists, wait for it to finish ...
current_time = int(time.time() * 1000)
saved_time = deacsmprefs["fulfillment_block_time"]
if saved_time + 60000 < current_time:
# Already locked since 60s, assume error
print("{0} v{1}: Looks like the lock was stuck, removing lock {2} ...".format(PLUGIN_NAME, PLUGIN_VERSION, deacsmprefs["fulfillment_block_token"]))
self.unblock()
time.sleep(0.02)
continue
def run(self, path_to_ebook): def run(self, path_to_ebook):
# type: (str) -> str # type: (str) -> str
try:
# This code gets called by Calibre with a path to the new book file.
# We need to check if it's an ACSM file
# This code gets called by Calibre with a path to the new book file. import calibre_plugins.deacsm.prefs as prefs # type: ignore
# We need to check if it's an ACSM file deacsmprefs = prefs.ACSMInput_Prefs()
print("{0} v{1}: Trying to parse file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))) if deacsmprefs['allow_parallel_fulfillment'] == False:
self.wait_and_block()
ext = os.path.splitext(path_to_ebook)[1].lower()
if (ext != ".acsm"): print("{0} v{1}: Trying to parse file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
print("{0} v{1}: That's not an ACSM, returning (is {2} instead)... ".format(PLUGIN_NAME, PLUGIN_VERSION, ext))
return path_to_ebook
# That's an ACSM. ext = os.path.splitext(path_to_ebook)[1].lower()
# We would fulfill this now, but first perform some sanity checks ...
if not self.ADE_sanity_check(): if (ext != ".acsm"):
print("{0} v{1}: ADE auth is missing or broken ".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: That's not an ACSM, returning (is {2} instead)... ".format(PLUGIN_NAME, PLUGIN_VERSION, ext))
return path_to_ebook self.unblock()
return path_to_ebook
# That's an ACSM.
# We would fulfill this now, but first perform some sanity checks ...
from libadobe import are_ade_version_lists_valid if not self.ADE_sanity_check():
from libadobeFulfill import fulfill print("{0} v{1}: ADE auth is missing or broken ".format(PLUGIN_NAME, PLUGIN_VERSION))
self.unblock()
return path_to_ebook
if not are_ade_version_lists_valid():
print("{0} v{1}: ADE version list mismatch, please open a bug report.".format(PLUGIN_NAME, PLUGIN_VERSION))
return path_to_ebook
print("{0} v{1}: Try to fulfill ...".format(PLUGIN_NAME, PLUGIN_VERSION)) from libadobe import are_ade_version_lists_valid
from libadobeFulfill import fulfill
success, replyData = fulfill(path_to_ebook, deacsmprefs["notify_fulfillment"]) if not are_ade_version_lists_valid():
print("{0} v{1}: ADE version list mismatch, please open a bug report.".format(PLUGIN_NAME, PLUGIN_VERSION))
self.unblock()
return path_to_ebook
if (success is False): print("{0} v{1}: Try to fulfill ...".format(PLUGIN_NAME, PLUGIN_VERSION))
print("{0} v{1}: Hey, that didn't work: \n".format(PLUGIN_NAME, PLUGIN_VERSION) + replyData)
else:
print("{0} v{1}: Downloading book ...".format(PLUGIN_NAME, PLUGIN_VERSION))
rpl = self.download(replyData)
if (rpl is not None):
# Got a file
# Because Calibre still thinks this is an ACSM file (not an EPUB) success, replyData = fulfill(path_to_ebook, deacsmprefs["notify_fulfillment"])
# it will not run other FileTypePlugins that handle EPUB (or PDF) files.
# Loop through all plugins (the list is already sorted by priority),
# then execute all of them that can handle EPUB / PDF.
# if the source file is supposed to be deleted after successful fulfillment, if (success is False):
# this is set to True print("{0} v{1}: Hey, that didn't work: \n".format(PLUGIN_NAME, PLUGIN_VERSION) + replyData)
# If there's any errors whatsoever during export / plugin execution, else:
# this will be set back to False to prevent deletion. print("{0} v{1}: Downloading book ...".format(PLUGIN_NAME, PLUGIN_VERSION))
delete_src_file = deacsmprefs["delete_acsm_after_fulfill"] rpl = self.download(replyData)
if (rpl is not None):
# Got a file
try: # Because Calibre still thinks this is an ACSM file (not an EPUB)
from calibre.customize.ui import _initialized_plugins, is_disabled # it will not run other FileTypePlugins that handle EPUB (or PDF) files.
from calibre.customize import FileTypePlugin # Loop through all plugins (the list is already sorted by priority),
# then execute all of them that can handle EPUB / PDF.
original_file_for_plugins = rpl # if the source file is supposed to be deleted after successful fulfillment,
# this is set to True
# If there's any errors whatsoever during export / plugin execution,
# this will be set back to False to prevent deletion.
delete_src_file = deacsmprefs["delete_acsm_after_fulfill"]
oo, oe = sys.stdout, sys.stderr try:
from calibre.customize.ui import _initialized_plugins, is_disabled
from calibre.customize import FileTypePlugin
for plugin in _initialized_plugins: original_file_for_plugins = rpl
#print("{0} v{1}: Plugin '{2}' has prio {3}".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, plugin.priority)) oo, oe = sys.stdout, sys.stderr
# Check if this is a FileTypePlugin for plugin in _initialized_plugins:
if not isinstance(plugin, FileTypePlugin):
#print("{0} v{1}: Plugin '{2}' is no FileTypePlugin, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
continue
# Check if it's disabled #print("{0} v{1}: Plugin '{2}' has prio {3}".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, plugin.priority))
if is_disabled(plugin):
#print("{0} v{1}: Plugin '{2}' is disabled, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
continue
if plugin.name == self.name: # Check if this is a FileTypePlugin
#print("{0} v{1}: Plugin '{2}' is me - skipping".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name)) if not isinstance(plugin, FileTypePlugin):
continue #print("{0} v{1}: Plugin '{2}' is no FileTypePlugin, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
continue
# Check if it's supposed to run on import: # Check if it's disabled
if not plugin.on_import: if is_disabled(plugin):
#print("{0} v{1}: Plugin '{2}' isn't supposed to run during import, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name)) #print("{0} v{1}: Plugin '{2}' is disabled, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
continue continue
# Check filetype if plugin.name == self.name:
# If neither the book file extension nor "*" is in the plugin, #print("{0} v{1}: Plugin '{2}' is me - skipping".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
# don't execute it. continue
my_file_type = os.path.splitext(rpl)[-1].lower().replace('.', '')
if (not my_file_type in plugin.file_types):
#print("{0} v{1}: Plugin '{2}' doesn't support {3} files, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, my_file_type))
continue
if ("acsm" in plugin.file_types or "*" in plugin.file_types): # Check if it's supposed to run on import:
#print("{0} v{1}: Plugin '{2}' would run anyways, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, my_file_type)) if not plugin.on_import:
continue #print("{0} v{1}: Plugin '{2}' isn't supposed to run during import, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
continue
print("{0} v{1}: Executing plugin {2} ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name)) # Check filetype
# If neither the book file extension nor "*" is in the plugin,
# don't execute it.
my_file_type = os.path.splitext(rpl)[-1].lower().replace('.', '')
if (not my_file_type in plugin.file_types):
#print("{0} v{1}: Plugin '{2}' doesn't support {3} files, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, my_file_type))
continue
plugin.original_path_to_file = original_file_for_plugins if ("acsm" in plugin.file_types or "*" in plugin.file_types):
#print("{0} v{1}: Plugin '{2}' would run anyways, skipping ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name, my_file_type))
continue
try: print("{0} v{1}: Executing plugin {2} ...".format(PLUGIN_NAME, PLUGIN_VERSION, plugin.name))
plugin_ret = None
plugin_ret = plugin.run(rpl)
except:
delete_src_file = False
print("{0} v{1}: Running file type plugin failed with traceback:".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc(file=oe)
# Restore stdout and stderr, in case a plugin broke them. plugin.original_path_to_file = original_file_for_plugins
sys.stdout, sys.stderr = oo, oe
try:
plugin_ret = None
plugin_ret = plugin.run(rpl)
except:
delete_src_file = False
print("{0} v{1}: Running file type plugin failed with traceback:".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc(file=oe)
if plugin_ret is not None: # Restore stdout and stderr, in case a plugin broke them.
# If the plugin returned a new path, update that. sys.stdout, sys.stderr = oo, oe
print("{0} v{1}: Plugin returned path '{2}', updating.".format(PLUGIN_NAME, PLUGIN_VERSION, plugin_ret))
rpl = plugin_ret
else:
print("{0} v{1}: Plugin returned nothing - skipping".format(PLUGIN_NAME, PLUGIN_VERSION))
except: if plugin_ret is not None:
delete_src_file = False # If the plugin returned a new path, update that.
print("{0} v{1}: Error while executing other plugins".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Plugin returned path '{2}', updating.".format(PLUGIN_NAME, PLUGIN_VERSION, plugin_ret))
traceback.print_exc() rpl = plugin_ret
pass else:
print("{0} v{1}: Plugin returned nothing - skipping".format(PLUGIN_NAME, PLUGIN_VERSION))
# If enabled, and if we didn't encounter any errors, delete the source ACSM file.
if delete_src_file:
try:
if os.path.exists(path_to_ebook):
print("{0} v{1}: Deleting existing ACSM file {2} ...".format(PLUGIN_NAME, PLUGIN_VERSION, path_to_ebook))
os.remove(path_to_ebook)
except: except:
print("{0} v{1}: Failed to delete source ACSM after fulfillment.".format(PLUGIN_NAME, PLUGIN_VERSION)) delete_src_file = False
print("{0} v{1}: Error while executing other plugins".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc()
# Return path - either the original one or the one modified by the other plugins. pass
return rpl
# If enabled, and if we didn't encounter any errors, delete the source ACSM file.
if delete_src_file:
try:
if os.path.exists(path_to_ebook):
print("{0} v{1}: Deleting existing ACSM file {2} ...".format(PLUGIN_NAME, PLUGIN_VERSION, path_to_ebook))
os.remove(path_to_ebook)
except:
print("{0} v{1}: Failed to delete source ACSM after fulfillment.".format(PLUGIN_NAME, PLUGIN_VERSION))
# Return path - either the original one or the one modified by the other plugins.
self.unblock()
return rpl
return path_to_ebook self.unblock()
return path_to_ebook
except:
self.unblock()
traceback.print_exc()
return path_to_ebook

@ -55,6 +55,7 @@ class ConfigWidget(QWidget):
self.tempdeacsmprefs['notify_fulfillment'] = self.deacsmprefs['notify_fulfillment'] self.tempdeacsmprefs['notify_fulfillment'] = self.deacsmprefs['notify_fulfillment']
self.tempdeacsmprefs['detailed_logging'] = self.deacsmprefs['detailed_logging'] self.tempdeacsmprefs['detailed_logging'] = self.deacsmprefs['detailed_logging']
self.tempdeacsmprefs['delete_acsm_after_fulfill'] = self.deacsmprefs['delete_acsm_after_fulfill'] self.tempdeacsmprefs['delete_acsm_after_fulfill'] = self.deacsmprefs['delete_acsm_after_fulfill']
self.tempdeacsmprefs['allow_parallel_fulfillment'] = self.deacsmprefs['allow_parallel_fulfillment']
self.tempdeacsmprefs['list_of_rented_books'] = self.deacsmprefs['list_of_rented_books'] self.tempdeacsmprefs['list_of_rented_books'] = self.deacsmprefs['list_of_rented_books']
@ -176,6 +177,11 @@ class ConfigWidget(QWidget):
self.chkDeleteAfterFulfill.toggled.connect(self.toggle_acsm_delete) self.chkDeleteAfterFulfill.toggled.connect(self.toggle_acsm_delete)
layout.addWidget(self.chkDeleteAfterFulfill) layout.addWidget(self.chkDeleteAfterFulfill)
self.chkParallelFulfill = QtGui.QCheckBox("Allow parallel fulfillment")
self.chkParallelFulfill.setToolTip("Default: True\n\nIf this is enabled (which was the default in previous versions), \nthe plugin will import multiple ACSM files simultaneously when you add more than one.\n\nIf this is disabled, it will add them one after another like ADE.")
self.chkParallelFulfill.setChecked(self.tempdeacsmprefs["allow_parallel_fulfillment"])
layout.addWidget(self.chkParallelFulfill)
# Key shortcut Ctrl+Shift+D / Cmd+Shift+D to remove authorization, just like in ADE. # Key shortcut Ctrl+Shift+D / Cmd+Shift+D to remove authorization, just like in ADE.
self.deauthShortcut = QShortcut(QKeySequence("Ctrl+Shift+D"), self) self.deauthShortcut = QShortcut(QKeySequence("Ctrl+Shift+D"), self)
self.deauthShortcut.activated.connect(self.delete_ade_auth) self.deauthShortcut.activated.connect(self.delete_ade_auth)
@ -1269,6 +1275,7 @@ class ConfigWidget(QWidget):
self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked()) self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked())
self.deacsmprefs.set('detailed_logging', self.chkDetailedLogging.isChecked()) self.deacsmprefs.set('detailed_logging', self.chkDetailedLogging.isChecked())
self.deacsmprefs.set('delete_acsm_after_fulfill', self.chkDeleteAfterFulfill.isChecked()) self.deacsmprefs.set('delete_acsm_after_fulfill', self.chkDeleteAfterFulfill.isChecked())
self.deacsmprefs.set('allow_parallel_fulfillment', self.chkParallelFulfill.isChecked())
self.deacsmprefs.writeprefs() self.deacsmprefs.writeprefs()
def load_resource(self, name): def load_resource(self, name):

@ -28,8 +28,12 @@ class ACSMInput_Prefs():
self.deacsmprefs.defaults['notify_fulfillment'] = True self.deacsmprefs.defaults['notify_fulfillment'] = True
self.deacsmprefs.defaults['detailed_logging'] = False self.deacsmprefs.defaults['detailed_logging'] = False
self.deacsmprefs.defaults['delete_acsm_after_fulfill'] = False self.deacsmprefs.defaults['delete_acsm_after_fulfill'] = False
self.deacsmprefs.defaults['allow_parallel_fulfillment'] = True
self.deacsmprefs.defaults['loan_identifier_token'] = 0 self.deacsmprefs.defaults['loan_identifier_token'] = 0
self.deacsmprefs.defaults['fulfillment_block_token'] = 0
self.deacsmprefs.defaults['fulfillment_block_time'] = 0
self.deacsmprefs.defaults['list_of_rented_books'] = [] self.deacsmprefs.defaults['list_of_rented_books'] = []

Loading…
Cancel
Save