View Single Post
Old 04-07-2023, 03:22 PM   #1065
ownedbycats
Custom User Title
ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.
 
ownedbycats's Avatar
 
Posts: 11,149
Karma: 77213683
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
Quote:
Originally Posted by capink View Post
Here is a modified version of the Chain Caller action (called Modified Chain Caller) that can call a target chain multiple times to process chunks of books for each iteration.

Code:
#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~

from qt.core import (QApplication, Qt, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout,
                      QGroupBox, QComboBox, QCheckBox, QSpinBox)

from calibre import prints
from calibre.constants import DEBUG

from calibre_plugins.action_chains.chains import Chain
import calibre_plugins.action_chains.config as cfg


from calibre_plugins.action_chains.actions.chain_caller import ChainCallerAction


tooltip = '''
This option splits the books into multiple chunks and run the target chain multiple times,
each time processing a chunk of books. The chunk size can be selected in the spin box.
The book ids in each chunk is passed to the target chain in a variabe called _book_ids.
You must adjust the scope of the actions in the target chains using a template scope to
act on this variable.
'''

class ConfigWidget(QWidget):
    def __init__(self, plugin_action, chain_name, chains_config, *args, **kwargs):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        self.gui = plugin_action.gui
        self.db = self.gui.current_db
        self.chain_name = chain_name
        self.chains_config = chains_config
        self._init_controls()

    def _init_controls(self):

        l = self.l = QVBoxLayout()
        self.setLayout(l)

        chain_groupbox = QGroupBox(_('Select chain'))
        l.addWidget(chain_groupbox)
        chain_groupbox_l = QVBoxLayout()
        chain_groupbox.setLayout(chain_groupbox_l)
        self.chain_combo = QComboBox()
        chain_groupbox_l.addWidget(self.chain_combo)

        opts_groupbox = QGroupBox(_('Options'))
        opts_groupbox_l = QVBoxLayout()
        opts_groupbox.setLayout(opts_groupbox_l)
        l.addWidget(opts_groupbox)

        vars_chk = self.vars_chk = QCheckBox(_('Pass variables from calling chain'))
        vars_chk.setChecked(False)
        opts_groupbox_l.addWidget(vars_chk)

        #all_chain_names = [chain_config['menuText'] for chain_config in cfg.get_chains_config()]
        all_chain_names = [chain_config['menuText'] for chain_config in self.chains_config if chain_config['menuText']]
        try:
            all_chain_names.remove(self.chain_name)
        except:
            pass
        self.chain_combo.addItems(all_chain_names)
        self.chain_combo.setCurrentIndex(-1)

        spin_group_box = self.spin_group_box = QGroupBox('Split books into chunks')
        spin_group_box.setCheckable(True)
        spin_group_box.setChecked(False)
        spin_group_box_l = QVBoxLayout()
        spin_group_box.setLayout(spin_group_box_l)
        l.addWidget(spin_group_box)
        spin_group_box.setToolTip(tooltip)
        self.spinbox = QSpinBox()
        self.spinbox.setMaximum(100000)
        self.spinbox.setMinimum(1)
        self.spinbox.setSingleStep(1)
        self.spinbox.setValue(10)
        spin_group_box_l.addWidget(self.spinbox)

        l.addStretch(1)

        self.setMinimumSize(300,300)

    def load_settings(self, settings):
        if settings:
            idx = self.chain_combo.findText(settings['chain_name'])
            self.chain_combo.setCurrentIndex(idx)
            self.vars_chk.setChecked(settings.get('pass_vars', False))
            self.spin_group_box.setChecked(settings.get('split_to_chunks', False))
            self.spinbox.setValue(settings.get('chunk_size', 10))

    def save_settings(self):
        settings = {}
        settings['chain_name'] = self.chain_combo.currentText()
        settings['pass_vars'] = self.vars_chk.isChecked()
        settings['split_to_chunks'] = self.spin_group_box.isChecked()
        settings['chunk_size'] = self.spinbox.value()
        return settings

class ModifiedChainCallerAction(ChainCallerAction):

    name = 'Modified Chain Caller'
    is_experimental = False

    def run(self, gui, settings, chain):
        # Chain variables {
        add_to_vars = {}
        if settings.get('pass_vars'):
            add_to_vars = self.sanitize_chain_vars(chain)
        add_to_vars['_caller'] = chain.chain_name
        # }
        book_ids = chain.scope().get_book_ids()
        if settings.get('split_to_chunks', False):
            chunk_size = settings['chunk_size']
            chunks = [book_ids[x:x+chunk_size] for x in range(0, len(book_ids), chunk_size)]
        else:
            chunks = [book_ids]
        chunks_no = len(chunks)
        for i, chunk in enumerate(chunks):
            prints('Action Chains: Chain Caller: running chunk no {} of {}'.format(i, chunks_no))
            add_to_vars['_book_ids'] = ','.join([str(x) for x in chunk])
            target_chain_config = cfg.get_chain_config(settings['chain_name'])
            target_chain = Chain(self.plugin_action, target_chain_config, show_progress=False,
                                add_to_vars=add_to_vars, chain_caller=True)
            target_chain.run()
            del target_chain

    def config_widget(self):
        return ConfigWidget
You should use this Modified Chain Caller to call your target chain as follows:
  1. Add the Modified Chain Caller as a custom action using the module editor (Action Chains > manage modules > create module > copy/paste the code above)
  2. Create a new chain that consists of a single action > Modified Chain Caller.
  3. In the settings of the Modified Chain Caller, choose your target chain from the dropdown menu.
  4. In the settings of the Modified Chain Caller, tick the box for splitting books into chunks, and choose the chunk size (default is 10 books)
  5. In your target chain, all the actions must have a template scope with the following template:
    Code:
    program:
        globals(_book_ids)
  6. Now select the books you want to process, and run the Modified Chain Caller chain.

I am not sure whether this can help you with the out of memory problem, give it a try and see.
Question: If I set this to run on 1 book, can it get around problems when it's running on a selection rather than individual books?

Use-case 1: Running a single-field MDE with ask-at-runtime.
Use-case 2: Using an action with a conditional that checks something from the current book.
Use-case 3: This.

EDIT: Use-case 1 didn't work. I ran it on two books. Book selection didn't change (thought it would) and I got the information I entered for the second book on both.

I wonder now if this is something I can fudge with selection modifiers.

Last edited by ownedbycats; 04-07-2023 at 04:26 PM. Reason: spellcheck is misbehaving
ownedbycats is offline   Reply With Quote