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 - name: Compile
run: | run: |
./bundle_calibre_plugin.sh ./bundle_calibre_plugin.sh
./bundle_migration_plugin.sh
- name: Upload - name: Upload
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: linux name: calibre-plugins
path: | path: |
calibre-plugin.zip calibre-plugin.zip
migration-plugin.zip
test-ubuntu-2004: test-ubuntu-2004:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

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: # Bugfix for Calibre < 5:
if sys.version_info[0] == 2: if sys.version_info[0] == 2:
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
if os.path.join(config_dir, "plugins", "DeACSM.zip") not in sys.path: for filename in ["ACSM Input.zip", "DeACSM.zip"]:
sys.path.insert(0, os.path.join(config_dir, "plugins", "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@@ #@@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 # 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, # multiple active loans from the same distributor, add experimental GUI button,
# 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".
# The big version number jump is to make that name change clearer.
PLUGIN_NAME = "ACSM Input" PLUGIN_NAME = "ACSM Input"
PLUGIN_VERSION_TUPLE = (0, 0, 17) PLUGIN_VERSION_TUPLE = (0, 1, 0)
from calibre.customize import FileTypePlugin # type: ignore from calibre.customize import FileTypePlugin # type: ignore
__version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE]) __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 os, shutil, traceback, sys, time, io, random
import zipfile import zipfile
from lxml import etree from lxml import etree
from calibre.gui2 import error_dialog
#@@CALIBRE_COMPAT_CODE@@ #@@CALIBRE_COMPAT_CODE@@
@ -80,12 +83,12 @@ class ACSMInput(FileTypePlugin):
def init_embedded_plugins(self): def init_embedded_plugins(self):
""" """
A Calibre plugin can normally only contain one Plugin class. 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 However, we want to load the GUI plugin, too, so we have to trick
Calibre into believing that there's actually a 2nd plugin. Calibre into believing that there's actually a 2nd plugin.
""" """
from calibre.customize.ui import _initialized_plugins 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): def init_plg(plg_type):
for plugin in _initialized_plugins: for plugin in _initialized_plugins:
@ -100,7 +103,7 @@ class ACSMInput(FileTypePlugin):
return plugin return plugin
init_plg(DeACSMGUIExtension) init_plg(ACSMInputGUIExtension)
@ -129,51 +132,24 @@ class ACSMInput(FileTypePlugin):
if not os.path.exists(self.pluginsdir): if not os.path.exists(self.pluginsdir):
os.mkdir(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: # Make sure the GUI extension is loaded:
self.init_embedded_plugins() self.init_embedded_plugins()
self.maindir_old = os.path.join(self.pluginsdir,"DeACSM") self.maindir_old = os.path.join(self.pluginsdir,"DeACSM")
self.maindir = os.path.join(self.pluginsdir,"ACSMInput") self.maindir = os.path.join(self.pluginsdir,"ACSMInput")
if os.path.exists(self.maindir_old) and not os.path.exists(self.maindir): # Do NOT try to migrate data, that just screws everything up.
# Migrate config to new folder # If this is a fresh install of the plugin and there's no old data,
os.rename(self.maindir_old, self.maindir) # use the new path. Otherwise, if there's already data at the old location,
if not iswindows: # continue to use that.
# Linux and Mac support symlinks, so create one so the old paths
# still work and people can downgrade the plugin again. if os.path.exists(self.maindir_old):
# Windows ... doesn't, so downgrading will be tricky. # We have the old folder, continue to use that
try: self.maindir = self.maindir_old
os.symlink(self.maindir_old, self.maindir)
except:
pass
if not os.path.exists(self.maindir): if not os.path.exists(self.maindir):

View File

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

View File

@ -51,8 +51,14 @@ def GetMasterKey(wineprefix):
try: try:
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
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") pluginsdir = os.path.join(config_dir,"plugins")
maindir = os.path.join(pluginsdir,"DeACSM") maindir = os.path.join(pluginsdir,"ACSMInput")
moddir = os.path.join(maindir,"modules") moddir = os.path.join(maindir,"modules")
except: except:
import os import os

View File

@ -52,7 +52,7 @@ def create_menu_action_unique(ia, parent_menu, menu_text, image=None, tooltip=No
return ac return ac
class ActualDeACSMGUIExtension(InterfaceAction): class ActualACSMInputGUIExtension(InterfaceAction):
name = "ACSM Input Plugin GUI Extension" name = "ACSM Input Plugin GUI Extension"
popup_type = QToolButton.ToolButtonPopupMode.InstantPopup popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
@ -106,7 +106,7 @@ class ActualDeACSMGUIExtension(InterfaceAction):
break break
if plg is None: 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 += "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. " msg += "please open a bug report. "
return error_dialog(None, "Plugin not found", msg, show=True) return error_dialog(None, "Plugin not found", msg, show=True)

View File

@ -11,9 +11,9 @@ from calibre.customize import PluginInstallationType
#@@CALIBRE_COMPAT_CODE@@ #@@CALIBRE_COMPAT_CODE@@
class DeACSMGUIExtension(InterfaceActionBase): class ACSMInputGUIExtension(InterfaceActionBase):
name = "ACSM Input Plugin GUI Extension" 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'] supported_platforms = ['linux', 'osx', 'windows']
author = "Leseratte10" author = "Leseratte10"
minimum_calibre_version = (4, 0, 0) minimum_calibre_version = (4, 0, 0)
@ -28,7 +28,7 @@ class DeACSMGUIExtension(InterfaceActionBase):
installation_type = PluginInstallationType.EXTERNAL installation_type = PluginInstallationType.EXTERNAL
# Mark this as user-installed so it shows up in the plugin list by default. # 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): def is_customizable(self):
return False return False

View File

@ -101,7 +101,7 @@ def are_ade_version_lists_valid():
fail = True fail = True
if fail: 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.") print("This should never happen, please open a bug report.")
return False return False

View File

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

View File

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