![]() |
#1111 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
|
|
![]() |
![]() |
![]() |
#1112 |
Zealot
![]() Posts: 124
Karma: 10
Join Date: Dec 2008
Location: France
Device: None
|
Windows 11 / calibre 6.20 / Action Chains 1.18.7
I have the same problem as Philantrop. I can't add a calibre action. Here is the json file from export. Spoiler:
Thanks |
![]() |
![]() |
![]() |
#1113 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
Anyway, here is modified version of the plugin that has print some debug statements. Please run this modified version in debug mode, and post your debug log. Last edited by capink; 06-14-2023 at 01:43 PM. Reason: remove debug version |
|
![]() |
![]() |
![]() |
#1114 |
Zealot
![]() Posts: 124
Karma: 10
Join Date: Dec 2008
Location: France
Device: None
|
Thank you.
Here is the log file (not sure if it is what you want) and a new export of the action chain, just in case. To be clear, I get the error as soon as I click the icon settings - If I want to modify a calibre action (for example, Extract ISBN in the screenshot) - If I want to add a calibre action (I also tried on a portable version of calibre, and I get the same thing) |
![]() |
![]() |
![]() |
#1115 |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
From what I can see from the log the problem is one of the multiple SortAction is returning an int value of 0 instead of returning a text value (when calling QAction.text() method). I don't why this is happening and why it is not replicated on my system.
Anyway try this new version, see whether it helps with the problem. Last edited by capink; 06-14-2023 at 01:42 PM. Reason: zip removed. New version with modifications releases |
![]() |
![]() |
![]() |
#1116 |
Zealot
![]() Posts: 124
Karma: 10
Join Date: Dec 2008
Location: France
Device: None
|
It works.
Thanks a lot ! |
![]() |
![]() |
![]() |
#1117 | |
Addict
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 296
Karma: 32153
Join Date: Dec 2008
Device: Kindles (e-ink)
|
Quote:
|
|
![]() |
![]() |
![]() |
#1118 |
want to learn what I want
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,614
Karma: 7891011
Join Date: Sep 2020
Device: none
|
Hi, do we have a chain for Prince PDF plugin? I could only set up the first step, that runs the existing Calibre Action and brings up the plugin dialog. Then we would need some script to send alt+P, then maybe wait a second, then send alt + V.
|
![]() |
![]() |
![]() |
#1119 |
Member
![]() Posts: 13
Karma: 10
Join Date: Apr 2013
Device: lots of, frequently changing
|
Persistant vars?
Trying to use an old chain (which has worked AFAIK), I get "EXCEPTION: Formatter: Unknown function set_persistent_vars"?
Problem in front of the screen? |
![]() |
![]() |
![]() |
#1120 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
I thought no one used them. But if you want to get them back, copy the code below into your module editor: Code:
from calibre.utils.config import JSONConfig from calibre_plugins.action_chains.templates.functions.base import TemplateFunction ########################################### # Persistent Vars ########################################### persistent_storage = JSONConfig('plugins/action_chains/action-chains-persistent-storage') # initialize root namespace if not present if not persistent_storage.get('root_namespace'): persistent_storage['root_namespace'] = {} class VarStorage(object): def __init__(self): self.root_namespace = persistent_storage['root_namespace'] # prefixes are used to avoid vars overwriting namespaces self.var_type = 'VAR' self.ns_type = 'NS' # Naming rules : {{{{ def mangle_name(self, name, typ): if not self.validate_name(name): raise ValueError('Invalid {} name: {}'.format(typ, name)) prefix = '_{}_'.format(typ) name = name if name.startswith(prefix) else prefix + name return name def unmangle_name(self, name, typ): prefix = '_{}_'.format(typ) if name.startswith(prefix): name = name.replace(prefix, '') return name def is_var(self, name): prefix = '_{}_'.format(self.var_type) if name.startswith(prefix): return True return False def is_namespace(self, name): prefix = '_{}_'.format(self.ns_type) if name.startswith(prefix): return True return False def validate_namespace_path(self, namespace_path): for node in namespace_path: if not self.validate_name(node): return False return True def validate_name(self, name): if not name.replace('_', '').isalnum(): return False return True def mangle_namespace_path(self, namespace_path): if not self.validate_namespace_path(namespace_path): raise ValueError('Invalid namespace path: {}'.format(namespace_path)) mangled_path = [] for node in namespace_path: mangled_node = self.mangle_name(node, self.ns_type) mangled_path.append(mangled_node) return mangled_path # }}}} def commit(self): persistent_storage.commit() def get_namespace(self, namespace_path=[]): mangled_path = self.mangle_namespace_path(namespace_path) ns = self.root_namespace for node in mangled_path: try: ns = ns[node] except KeyError as e: raise ValueError('Namespace cannot be found: {}'.format(namespace_path)) return ns def create_namespace_if_not_present(self, namespace_path=[]): mangled_path = self.mangle_namespace_path(namespace_path) ns = self.root_namespace for node in mangled_path: if not ns.get(node): ns[node] = {} ns = ns[node] def get_var(self, var_name, namespace_path=[]): try: namespace = self.get_namespace(namespace_path) except: return '' name = self.mangle_name(var_name, self.var_type) return namespace.get(name, '') def set_var(self, var_name, var_value, namespace_path=[]): if not isinstance(var_value, str): raise ValueError('Persistent variable values can only of string type') self.create_namespace_if_not_present(namespace_path) namespace = self.get_namespace(namespace_path) name = self.mangle_name(var_name, self.var_type) namespace[name] = var_value self.commit() def list_vars(self, namespace_path=[]): namespace = self.get_namespace(namespace_path) all_vars = [] for name in namespace.keys(): if self.is_var(name): var = self.unmangle_name(name, self.var_type) all_vars.append(var) return all_vars def list_namespaces(self, namespace_path=[]): if not namespace_path: namespace = self.root_namespace else: namespace = self.get_namespace(namespace_path) all_ns = [] for name in namespace.keys(): if self.is_namespace(name): ns = self.unmangle_name(name, self.ns_type) all_ns.append(ns) return all_ns def delete_namespace(self, namespace_path=[]): if not namespace_path: self.root_namespace.clear() else: if len(namespace_path) == 1: parent = self.root_namespace elif len(namespace_path) > 1: parent_path = namespace_path[:-1] parent = self.get_namespace(parent_path) mangled_name = self.mangle_name(namespace_path[-1], self.ns_type) del parent[mangled_name] self.commit() def delete_var(self, var_name, namespace_path=[]): namespace = self.get_namespace(namespace_path) mangled_name = self.mangle_name(var_name, self.var_type) del namespace[mangled_name] self.commit() persistent_vars = VarStorage() ########################################### # Persistent Vars Template Functions ########################################### class PersistentVars(TemplateFunction): doc = _('Retrieve value for variable stored in persistent memory using set_persistent_vars()\n' 'Usage: persistent_vars(var_name, [namespace])\n' '`var_name`: name of the variable\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, the default namespace will be used.\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'persistent_vars' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [1, 2]: raise ValueError('Incorrect number of arguments') var_name = args[0] if len(args) == 2: namespace_path = args[1].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') try: return persistent_vars.get_var(var_name, namespace_path) except: # if namespace cannot be found return '' class SetPersistentVars(TemplateFunction): doc = _('Set variables to presistent memory\n' 'Usage: set_persistent_vars(var_name, var_value, [namespace])\n' '`var_name`: name of the variable\n' '`var_value`: value of the variable\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, the default namespace will be used.\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'set_persistent_vars' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [2, 3]: raise ValueError('Incorrect number of arguments') var_name = args[0] var_value = args[1] if not persistent_vars.validate_name(var_name): raise ValueError('Invalid variable name: {}'.format(var_name)) if len(args) == 3: namespace_path = args[2].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') # We don't want to write vars when the user is typing in the template if not self.context == 'template_dialog_mode': persistent_vars.set_var(var_name, var_value, namespace_path) return '' class DeletePersistentVars(TemplateFunction): doc = _('Delete variable from presistent memory\n' 'Usage: set_persistent_vars(var_name, var_value, [namespace])\n' '`var_name`: name of the variable\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, the default namespace will be used.\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'delete_persistent_vars' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [1, 2]: raise ValueError('Incorrect number of arguments') var_name = args[0] if not persistent_vars.validate_name(var_name): raise ValueError('Invalid variable name: {}'.format(var_name)) if len(args) == 2: namespace_path = args[1].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') # We don't want to delete vars when the user is typing in the template if not self.context == 'template_dialog_mode': try: persistent_vars.delete_var(var_name, namespace_path) except Exception as e: import traceback print(traceback.format_exc()) return '' class ListPersistentVars(TemplateFunction): doc = _('List variables stored in presistent memory using set_persistent_vars()\n' 'Usage: list_persistent_vars([namespace])\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, the default namespace will be used.\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'list_persistent_vars' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [0, 1]: raise ValueError('Incorrect number of arguments') if len(args) == 1: namespace_path = args[0].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') all_vars = persistent_vars.list_vars(namespace_path) return ','.join(all_vars) class ListPersistentNamespaces(TemplateFunction): doc = _('List namespaces in presistent memory used by set_persistent_vars()\n' 'Usage: list_persistent_namespaces([namespace])\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, all top level namespaces will be listed.\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'list_persistent_namespaces' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [0, 1]: raise ValueError('Incorrect number of arguments') if len(args) == 1: namespace_path = args[0].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') all_ns = persistent_vars.list_namespaces(namespace_path) return ','.join(all_ns) class DeletePersistentNamespaces(TemplateFunction): doc = _('Delete a namespace in presistent memory used by set_persistent_vars()\n' 'Usage: delete_persistent_namespaces([namespace])\n' '`namespace`: (optional) namespace where the variable is stored. Nested namespaces ' 'are possible and are represented by dot notation e.g. namespace1.namspace2 ' 'If no namespace is provided, the top level namespace including all namespaces and ' 'vars will be deleted\n' 'This function is part of the action chain plugin and will not work elsewhere.') name = 'delete_persistent_namespaces' arg_count = -1 def evaluate(self, formatter, kwargs, mi, locals, *args): if len(args) not in [0, 1]: raise ValueError('Incorrect number of arguments') if len(args) == 1: namespace_path = args[0].split('.') else: namespace_path = [] if not persistent_vars.validate_namespace_path(namespace_path): raise ValueError('Namespaces must contain only letter, numbers and underscores') # We don't want to delete namespaces when the user is typing in the template if not self.context == 'template_dialog_mode': try: persistent_vars.delete_namespace(namespace_path) except Exception as e: import traceback print(traceback.format_exc()) return '' |
|
![]() |
![]() |
![]() |
#1121 |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
No. It it is not working through Calibre Actions, the only way is for someone to write a custom action that calls the plugin's code directly.
|
![]() |
![]() |
![]() |
#1122 |
want to learn what I want
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,614
Karma: 7891011
Join Date: Sep 2020
Device: none
|
|
![]() |
![]() |
![]() |
#1123 |
Member
![]() Posts: 13
Karma: 10
Join Date: Apr 2013
Device: lots of, frequently changing
|
Thank you, code works, I'll switch then to python templates.
|
![]() |
![]() |
![]() |
#1124 |
Custom User Title
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 11,007
Karma: 75555555
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
|
Question: Is there a method to add a file to the extra data folder similar to SFE on 'formats'?
Context: I have a chain that copies the cover path to clipboard then follows it with the 'add data file' Calibre action where I paste it in. It's a little clunky though. |
![]() |
![]() |
![]() |
#1125 | |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
Code:
python: def evaluate(book, context): import os from shutil import copy from calibre.db.constants import DATA_DIR_NAME db = context.db cache = db.new_api # Get the normalized library path library_path = cache.backend.library_path # Construct the full path to the book folder path = os.path.join(library_path, cache.field_for('path', book.id).replace('/', os.sep)) # Ensure the data directory exists data_dir = os.path.join(path, DATA_DIR_NAME) if not os.path.exists(data_dir): os.mkdir(data_dir) cover_file = os.path.join(path, 'cover.jpg') if os.path.exists(cover_file): # It does. Copy it to the data directory. The 'copy' method takes a # directory as a target. copy(cover_file, data_dir) return 'copied' return 'not copied' |
|
![]() |
![]() |
![]() |
|
![]() |
||||
Thread | Thread Starter | Forum | Replies | Last Post |
[Editor Plugin] Editor Chains | capink | Plugins | 106 | 06-17-2025 05:36 PM |
Action Chains Resources | capink | Plugins | 77 | 06-16-2025 12:45 PM |
[GUI Plugin] Noosfere_util, a companion plugin to noosfere DB | lrpirlet | Plugins | 2 | 08-18-2022 03:15 PM |
[GUI Plugin] Save Virtual Libraries To Column (GUI) | chaley | Plugins | 14 | 04-04-2021 05:25 AM |