View Single Post
Old 03-31-2021, 02:19 PM   #470
capink
Wizard
capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.
 
Posts: 1,207
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by chaley View Post
@capink: Random thought -- does it make sense to give Single Field Edit the option to run over something other than the selection? I was thinking three options:
  1. Selection (what it does today).
  2. All books, ignoring the selection. What to do about VLs? Either use the current VL or ignore them?
  3. Books with ID in book_vars. This one I am not sure of. The idea is that a chain variables action does some computation and sets a book var for what it wants to change. Single field edit would loop over the books with any var set, calling the edit template.
The second is good for actions that want to do multiple things on many books. The third would be convenient for actions that want to do searches then compute results. For example, the second would solve @ownedbycats problem of modifying identifiers without selections. With the third my sample "average rating" function would become two actions with no selection modifiers.

What do you think?

@chaley: Attached to this post is a beta that adds scope management to the plugin. I will leave it for few days for testing before releasing it. I'd like to hear your feedback on this.

Here is how this works:
  • By default actions work on books selected in the library view. Scopes gives the user a way to change the scope to include books different to those selected in library view.
  • For actions that support scopes, the user will click a button to set the scope for the action (similar to the conditions button). The user will first have to choose a scope manager from the a combobox, after which the settings for chosen scope manager will appear so that the user can configure them.
  • The plugin currently ships with one scope manager called "Basic Scopes". It has four options: selected books, all books, books in Book Vars dict, books from search.
  • Multiple scope managers can be added including user defined scope managers. More on this below.

Under the hood this is how scopes work:
  • Actions that support scopes should set the following attribute:
    Code:
    support_scopes = True
  • Whenever and action that have the above attribute set to True wants to get a list of book_ids to work on, it should call the following:
    Code:
    chain.scope().get_book_ids()
    The above will look for scope settings configured by the user for the action, if it finds them, it hands them to the scope manager selected by the user to take care of getting the list of book_ids. If no scope settings are found, it will use a fallback scope manager, which returns a list of books selected in library view.

The whole thing seems to be working well. The only thing that I have doubts about the is user interface for this.

Finally, Here is an example of a custom scope manager that uses a template defined by the user to return the list of book_ids (copy/paste into the module editor):

Code:
from __future__ import (unicode_literals, division, absolute_import,
                        print_function)

# python 3 compatibility
from six import text_type as unicode

from PyQt5.Qt import (QApplication, Qt, QVBoxLayout, QGroupBox, QCheckBox, QRadioButton)

from calibre import prints
from calibre.constants import DEBUG
from calibre.utils.search_query_parser import ParseException

from calibre_plugins.action_chains.scopes.base import ActionScope
from calibre_plugins.action_chains.templates import TemplateBox, check_template, get_metadata_object

class TemplateScopeConfigWidget(TemplateBox):

    def __init__(self, plugin_action):
        self.plugin_action = plugin_action
        self.gui = plugin_action.gui
        self.db = self.gui.current_db
        placeholder_text = _('The template entered here should return a comma separated list '
                             'of book_ids')
        TemplateBox.__init__(
            self,
            self.gui,
            plugin_action,
            template_text='',
            placeholder_text=placeholder_text,
            dialog_is_st_editor=True
        )
        vl_chk = self.vl_chk = QCheckBox(_('Exclude books not in current virtual libarary'))
        vl_chk.setChecked(False)
        self.user_layout_1.addWidget(vl_chk)
        
        options_groupbox = QGroupBox(_('Options'))
        self.user_layout_2.addWidget(options_groupbox)
        options_groupbox_layout = QVBoxLayout()
        options_groupbox.setLayout(options_groupbox_layout)
        ids_opt = self.ids_opt = QRadioButton(_('Template output is a list of book ids'))
        options_groupbox_layout.addWidget(ids_opt)
        ids_opt.setChecked(True)
        search_opt = self.search_opt = QRadioButton(_('Template output is a calibre search'))
        options_groupbox_layout.addWidget(search_opt)

    def load_settings(self, settings):
        if settings:
            template = settings['template']
            self.textbox.insertPlainText(template)
            self.vl_chk.setChecked(settings.get('vl'))
            if settings.get('opt') == 'search':
                self.search_opt.setChecked(True)

    def save_settings(self):
        settings = {}
        settings['template'] = unicode(self.textbox.toPlainText()).rstrip()
        settings['vl'] = self.vl_chk.isChecked()
        if self.ids_opt.isChecked():
            settings['opt'] = 'ids'
        elif self.search_opt.isChecked():
            settings['opt'] = 'search'
        return settings


class TemplateScope(ActionScope):

    name = 'Template Scopes'

    def get_book_ids(self, gui, settings, chain):
        db = gui.current_db
        template = settings.get('template', '')
        template_output = chain.evaluate_template(template, book_id=None)
        if settings.get('opt') == 'ids':
            try:
                book_ids = [int(x.strip()) for x in template_output.split(',')]
            except:
                book_ids = []
        elif settings.get('opt') == 'search':
            try:
                book_ids = db.data.search_getting_ids(template_output, '', use_virtual_library=False)
            except ParseException:
                book_ids = []
        if settings.get('vl'):
            vl_ids = self.get_current_restriction_book_ids()
            book_ids = list(set(vl_ids).intersection(set(book_ids)))
        return book_ids

    def validate(self, settings):
        gui = self.plugin_action.gui
        db = gui.current_db
        if not settings:
            return (_('Scope errors'), _('You must configure this scope before running it'))
        mi = get_metadata_object(gui)
        is_template_valid = check_template(settings['template'], self.plugin_action, print_error=False)
        if is_template_valid is not True:
            return is_template_valid
        return True

    def config_widget(self):
        return TemplateScopeConfigWidget
Edit1: Builtin actions that support scopes are:
  • Single Field Edit.
  • Search And Replace.
  • Chain Variables (partial support only for variables that have iterate check enabled).
  • Open With (partial support only for multiple selection mode).
  • Copy To Clipboard (partial support only for multiple selection mode).

Edit2: The custom single field edit action posted previously will no longer work with this new version.

Edit3: Also the third option in Basic Scopes: "Act on books in that have values in Book Vars" is not at all user friendly and not descriptive enough. If anyone have better alternative?

Last edited by capink; 04-03-2021 at 06:17 AM. Reason: remove attached zip. version released.
capink is offline   Reply With Quote