BETA: Migration code from DeACSM to ACSM Input

Do not use this version yet unless you have a backup of your account data
This commit is contained in:
Florian Bach 2022-10-08 17:51:15 +02:00
parent 2bb0d0334f
commit 79be3e3d87
15 changed files with 226 additions and 67 deletions

View File

@ -19,13 +19,15 @@ jobs:
- name: Compile
run: |
./bundle_calibre_plugin.sh
./bundle_migration_plugin.sh
- name: Upload
uses: actions/upload-artifact@v2
with:
name: linux
name: calibre-plugins
path: |
calibre-plugin.zip
migration-plugin.zip
test-ubuntu-2004:
runs-on: ubuntu-20.04
@ -152,4 +154,4 @@ jobs:
- name: Run tests (Python 2)
run: |
cd tests && PYTHONWARNINGS=ignore python2 ./main.py && cd ..
cd tests && PYTHONWARNINGS=ignore python2 ./main.py && cd ..

16
bundle_migration_plugin.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
rm -rf calibre-plugin-tmp || /bin/true
mkdir calibre-plugin-tmp
cp migration_plugin/* calibre-plugin-tmp/
cp LICENSE calibre-plugin-tmp/
pushd calibre-plugin-tmp
# Create ZIP file from calibre-plugin folder.
zip -r ../calibre-migration-plugin.zip *
popd
rm -rf calibre-plugin-tmp

View File

@ -15,7 +15,9 @@ if "calibre" in sys.modules:
# Bugfix for Calibre < 5:
if sys.version_info[0] == 2:
from calibre.utils.config import config_dir
if os.path.join(config_dir, "plugins", "DeACSM.zip") not in sys.path:
sys.path.insert(0, os.path.join(config_dir, "plugins", "DeACSM.zip"))
for filename in ["ACSM Input.zip", "DeACSM.zip"]:
__zip_path = os.path.join(config_dir, "plugins", filename)
if __zip_path not in sys.path and os.path.exists(__zip_path):
sys.path.insert(0, __zip_path)
#@@CALIBRE_COMPAT_CODE_END@@

View File

@ -45,10 +45,14 @@
# Fix bug that would sometimes return the wrong book (or none at all) if you had
# multiple active loans from the same distributor, add experimental GUI button,
# 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".
# The big version number jump is to make that name change clearer.
PLUGIN_NAME = "ACSM Input"
PLUGIN_VERSION_TUPLE = (0, 0, 17)
PLUGIN_VERSION_TUPLE = (0, 1, 0)
from calibre.customize import FileTypePlugin # type: ignore
__version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
@ -61,7 +65,6 @@ from calibre.constants import isosx, iswindows, islinux # type:
import os, shutil, traceback, sys, time, io, random
import zipfile
from lxml import etree
from calibre.gui2 import error_dialog
#@@CALIBRE_COMPAT_CODE@@
@ -80,12 +83,12 @@ class ACSMInput(FileTypePlugin):
def init_embedded_plugins(self):
"""
A Calibre plugin can normally only contain one Plugin class.
In our case, this would be the DeACSM class.
In our case, this would be the file type class.
However, we want to load the GUI plugin, too, so we have to trick
Calibre into believing that there's actually a 2nd plugin.
"""
from calibre.customize.ui import _initialized_plugins
from calibre_plugins.deacsm.gui_main_wrapper import DeACSMGUIExtension
from calibre_plugins.deacsm.gui_main_wrapper import ACSMInputGUIExtension
def init_plg(plg_type):
for plugin in _initialized_plugins:
@ -100,7 +103,7 @@ class ACSMInput(FileTypePlugin):
return plugin
init_plg(DeACSMGUIExtension)
init_plg(ACSMInputGUIExtension)
@ -129,51 +132,24 @@ class ACSMInput(FileTypePlugin):
if not os.path.exists(self.pluginsdir):
os.mkdir(self.pluginsdir)
# Okay, "I" am now the new version. If I'm running under the old name,
# move "me" to the new one.
if os.path.exists(os.path.join(self.pluginsdir, "DeACSM.zip")):
from calibre.customize.ui import _config
shutil.copyfile(os.path.join(self.pluginsdir, "DeACSM.zip"), os.path.join(self.pluginsdir, "ACSM Input.zip"))
# Delete the old plugin.
os.remove(os.path.join(self.pluginsdir, "DeACSM.zip"))
# Forcibly add the new plugin, circumventing the Calibre code.
ui_plg_config = _config()
plugins = ui_plg_config['plugins']
plugins["ACSM Input"] = os.path.join(self.pluginsdir, "ACSM Input.zip")
ui_plg_config['plugins'] = plugins
print("Need another restart due to plugin update ...")
# "Rude" exit, but otherwise it won't work ...
try:
os._exit(42)
except TypeError:
os._exit()
# If the old DeACSM plugin still exists, rename it to BAK or something so it doesn't load.
os.rename(os.path.join(self.pluginsdir, "DeACSM.zip"), os.path.join(self.pluginsdir, "DeACSM.BAK"))
# Make sure the GUI extension is loaded:
self.init_embedded_plugins()
self.maindir_old = os.path.join(self.pluginsdir,"DeACSM")
self.maindir = os.path.join(self.pluginsdir,"ACSMInput")
if os.path.exists(self.maindir_old) and not os.path.exists(self.maindir):
# Migrate config to new folder
os.rename(self.maindir_old, self.maindir)
if not iswindows:
# Linux and Mac support symlinks, so create one so the old paths
# still work and people can downgrade the plugin again.
# Windows ... doesn't, so downgrading will be tricky.
try:
os.symlink(self.maindir_old, self.maindir)
except:
pass
# Do NOT try to migrate data, that just screws everything up.
# If this is a fresh install of the plugin and there's no old data,
# use the new path. Otherwise, if there's already data at the old location,
# continue to use that.
if os.path.exists(self.maindir_old):
# We have the old folder, continue to use that
self.maindir = self.maindir_old
if not os.path.exists(self.maindir):

View File

@ -1317,7 +1317,7 @@ class RentedBooksDialog(QDialog):
QDialog.__init__(self,parent)
self.parent = parent
self.setWindowTitle("DeACSM: Manage loaned Books")
self.setWindowTitle("ACSM Input: Manage loaned Books")
self.deacsmprefs = prefs.ACSMInput_Prefs()

View File

@ -51,9 +51,15 @@ def GetMasterKey(wineprefix):
try:
from calibre.utils.config import config_dir
pluginsdir = os.path.join(config_dir,"plugins")
maindir = os.path.join(pluginsdir,"DeACSM")
moddir = os.path.join(maindir,"modules")
from calibre_plugins.deacsm.__init__ import maindir as plg_maindir
if plg_maindir is not None:
print("FOUND MOD DIR!")
moddir = os.path.join(plg_maindir,"modules")
else:
pluginsdir = os.path.join(config_dir,"plugins")
maindir = os.path.join(pluginsdir,"ACSMInput")
moddir = os.path.join(maindir,"modules")
except:
import os
moddir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyextract")

View File

@ -52,7 +52,7 @@ def create_menu_action_unique(ia, parent_menu, menu_text, image=None, tooltip=No
return ac
class ActualDeACSMGUIExtension(InterfaceAction):
class ActualACSMInputGUIExtension(InterfaceAction):
name = "ACSM Input Plugin GUI Extension"
popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
@ -106,7 +106,7 @@ class ActualDeACSMGUIExtension(InterfaceAction):
break
if plg is None:
msg = "Tried to open the ACSM Input plugin (DeACSM) settings, but I couldn't find the ACSM Input plugin. "
msg = "Tried to open the ACSM Input plugin settings, but I couldn't find the ACSM Input plugin. "
msg += "This is most likely a bug in the plugin. Try restarting Calibre, and if you still get this error, "
msg += "please open a bug report. "
return error_dialog(None, "Plugin not found", msg, show=True)

View File

@ -11,9 +11,9 @@ from calibre.customize import PluginInstallationType
#@@CALIBRE_COMPAT_CODE@@
class DeACSMGUIExtension(InterfaceActionBase):
class ACSMInputGUIExtension(InterfaceActionBase):
name = "ACSM Input Plugin GUI Extension"
description = "GUI code for ACSM Input Plugin (DeACSM). This is automatically installed and updated with the ACSM plugin."
description = "GUI code for ACSM Input Plugin. This is automatically installed and updated with the ACSM plugin."
supported_platforms = ['linux', 'osx', 'windows']
author = "Leseratte10"
minimum_calibre_version = (4, 0, 0)
@ -28,7 +28,7 @@ class DeACSMGUIExtension(InterfaceActionBase):
installation_type = PluginInstallationType.EXTERNAL
# Mark this as user-installed so it shows up in the plugin list by default.
actual_plugin = "calibre_plugins.deacsm.gui_main:ActualDeACSMGUIExtension"
actual_plugin = "calibre_plugins.deacsm.gui_main:ActualACSMInputGUIExtension"
def is_customizable(self):
return False

View File

@ -101,7 +101,7 @@ def are_ade_version_lists_valid():
fail = True
if fail:
print("Internal error in DeACSM: Mismatched version list lenghts.")
print("Internal error in ACSM Input: Mismatched version list lenghts.")
print("This should never happen, please open a bug report.")
return False

View File

@ -572,6 +572,9 @@ def updateLoanReturnData(fulfillmentResultToken, forceTestBehaviour=False):
deacsmprefs["list_of_rented_books"].append(new_loan_record)
print("DEBUG, list of books:")
print(deacsmprefs["list_of_rented_books"])
deacsmprefs.writeprefs()
return True

View File

@ -18,9 +18,8 @@ class ACSMInput_Prefs():
JSON_PATH = os.path.join("plugins", "ACSMInput", "ACSMInput.json")
if os.path.exists(JSON_PATH_OLD) and not os.path.exists(JSON_PATH):
os.rename(JSON_PATH_OLD, JSON_PATH)
if not iswindows:
os.symlink(JSON_PATH_OLD, JSON_PATH)
# If the file exists in the old location, use that.
JSON_PATH = JSON_PATH_OLD
self.deacsmprefs = JSONConfig(JSON_PATH)
@ -37,14 +36,21 @@ class ACSMInput_Prefs():
self.pluginsdir = os.path.join(config_dir,"plugins")
self.maindir = os.path.join(self.pluginsdir,"ACSMInput")
self.accountdir = os.path.join(self.maindir,"account")
if not os.path.exists(self.accountdir):
raise Exception("Why does the account folder not exist?")
self.__pluginsdir = os.path.join(config_dir,"plugins")
success = False
for f in ["ACSMInput", "DeACSM"]:
self.__maindir = os.path.join(self.__pluginsdir, f)
self.__accountdir = os.path.join(self.__maindir,"account")
if os.path.exists(self.__accountdir):
self.deacsmprefs.defaults['path_to_account_data'] = self.__accountdir
success = True
break
# Default to the builtin account path
self.deacsmprefs.defaults['path_to_account_data'] = self.accountdir
if not success:
raise Exception("Why does the account folder not exist?")
def __getitem__(self,kind = None):

View File

@ -0,0 +1,40 @@
# What is this?
The original name of the plugin, when I first introduced it in 2021, was "DeACSM" - similar to how the popular DRM removal plugin is called "DeDRM". However, later I realized that this is a terrible name, and I'd rather have the plugin be named "ACSM Input", similar to other file type plugins like "LCPL Input", "KFX Input", "DOC Input" and "KePub Input".
However, the Calibre plugin updater doesn't support replacing an existing plugin A with a differently-named plugin B.
This is where this helper plugin comes in. It's a dummy plugin that doesn't have any actual functionality, and will be released under the old name ("DeACSM") with a higher version number (0.0.18 or something like that).
Then, all this plugin is going to be doing is, without using the Calibre updater, automatically download the renamed plugin ("ACSM Input") from my Github page, installs it into Calibre, the uninstalls itself. This will have no impact on the plugin data, that will still be stored at the same location and will be read from the same location. But it means that the plugin will get auto-renamed to its new name and can still continue to receive plugin updates through the Calibre plugin updater.
# Process details
I have asked on MobileRead whether it is possible [to rename a Calibre Plugin](https://www.mobileread.com/forums/showthread.php?t=348941), but I was told that that's not easily possible. Kovid suggested just making a new MobileRead thread for the "new" plugin, mark the old one as "deprecated", and then manually tell users of the old plugin somehow that they need to re-download the new one - but I didn't want to have to do that.
The "old" plugin's versions go up to 0.0.16 for the latest release, and 0.0.17 for the latest beta.
The migration plugin you can find in this folder will have the same name "DeACSM" as the old plugin, but a higher version number (0.0.20).
Then, I will create a new dummy thread at MobileRead and upload this migration plugin to that thread.
What this migration plugin does on first start is it downloads the "new" "ACSM Input" plugin from the MobileRead forum thread which is named "ACSM Input" with version number 0.1.0. This circumvents the Calibre Plugin updater which would refuse to update a plugin if that were to change a plugin's name.
Once that's happened, future updates can go through the plugin updater again.
So, the chain of versions is going to be the following:
DeACSM 0.0.16 -> "DeACSM" migration plugin 0.0.20 -> Replaces itself with "ACSM Input 0.1.0" -> more updates in the future to ACSM Input 0.1.1 and so on.
# MobileRead forum plugin index
Right now, the thread for my ACSM Input plugin contains the "old" DeACSM 0.0.16 plugin.
Once I release ACSM Input 0.1.0, I will put that version into the MobileRead thread, and I will put the migration build into another dummy thread.
Then I will have a moderator update the Calibre plugin index with two entries:
- "DeACSM", pointing to [the dummy thread](https://www.mobileread.com/forums/showthread.php?t=348941) with the migration build. The comment will mention not to install this manually.
- "ACSM Input", pointing to the well-known [ACSM Input plugin thread](https://www.mobileread.com/forums/showthread.php?t=341975).
This means that users are not going to see any change in behaviour in the forum thread - they're not going to notice the dummy thread, and the well-known thread that's linked everywhere will continue to point to the newest plugin updates.
After a couple months once everyone has updated to the newest versions, the dummy thread and the old "DeACSM" entry in the plugin index can then be removed.

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Migration plugin from "DeACSM" to "ACSM Input"
# See README.md for details.
# Revision history:
# v0.0.20: First version of the migration plugin, released under the old name.
from calibre.customize import InterfaceActionBase # type: ignore
from calibre.customize import PluginInstallationType
class DeACSMMigrationPlugin(InterfaceActionBase):
name = "DeACSM"
description = "Extension for the ACSM Input plugin to migrate to a new plugin name"
supported_platforms = ['linux', 'osx', 'windows']
author = "Leseratte10"
minimum_calibre_version = (4, 0, 0)
version = (0, 0, 20)
can_be_disabled = False
# This plugin will be auto-loaded from the ACSM Input plugin. It doesn't make sense for the user
# to disable it. If necessary, the menu bar button can be removed through the Calibre settings.
type = "File type"
# Just so that the GUI extension shows up at the same place as the actual ACSM Input plugin.
installation_type = PluginInstallationType.EXTERNAL
# Mark this as user-installed so it shows up in the plugin list by default.
actual_plugin = "calibre_plugins.deacsm.migration:ActualMigrationPlugin"
def is_customizable(self):
return False

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Migration plugin from "DeACSM" to "ACSM Input"
# See README.md for details.
import os, sys
from calibre.gui2.actions import InterfaceAction
class ActualMigrationPlugin(InterfaceAction):
name = "DeACSM"
def genesis(self):
print("DeACSM -> ACSM Input migration started ...")
DOWNLOAD_URL = "https://github.com/Leseratte10/acsm-calibre-plugin/releases/download/config/TEST_calibre_plugin_acsminput_new_0_0_30.zip"
# Okay, now download the new version and uninstall myself:
from calibre.utils.config import config_dir
self.pluginsdir = os.path.join(config_dir,"plugins")
if not os.path.exists(self.pluginsdir):
os.mkdir(self.pluginsdir)
new_path = os.path.join(self.pluginsdir, "ACSM Input.zip")
if os.path.exists(new_path):
# If so, delete ourselves and exit
print("Already done ...")
return
if sys.version_info[0] == 2:
import urllib
urllib.urlretrieve(DOWNLOAD_URL, new_path)
else:
import urllib.request
urllib.request.urlretrieve(DOWNLOAD_URL, new_path)
# Check if the download was successful and the new file exists:
if os.path.exists(new_path):
# Delete myself
os.remove(os.path.join(self.pluginsdir, "DeACSM.zip"))
# Forcibly add the new plugin
from calibre.customize.ui import _config
ui_plg_config = _config()
plugins = ui_plg_config['plugins']
plugins["ACSM Input"] = new_path
ui_plg_config['plugins'] = plugins
# Force-kill Calibre and have the user manually restart it:
print("Force-exit, please restart")
try:
os._exit(42)
except TypeError:
os._exit()
else:
print("Download / Update failed, trying again later ...")
print("Please open a bug report for the ACSM Input plugin")