Skip to content

Создание собственных плагинов

Dmitrii edited this page Dec 17, 2022 · 15 revisions

Общие сведения

Вы можете расширять возможности Libro2 с помощью собственных плагинов, написанных на языке Python. Плагины дают возможность пакетной обработки метаданных файлов в форматах fb2 и epub (версии 2 и 3), а также самих файлов (копирование, переименование, архивация и проч.). Вам не нужно дополнительно устанавливать Python - Libro2 уже содержит встроенный интерпретатор, а также предоставляет ряд модулей для написания плагинов.

Файл плагина с расширением .py должен размещаться в папке %USERPROFILE%\libro2\plugins для Microsoft Windows или ~/.libro2/plugins для MacOS и Linux. Libro2 при запуске загружает файлы, расположенные в этой папке и успешно загруженные плагины помещает в меню "Инструменты".

Для запуска плагина загрузите в программу файлы fb2 или epub, выделите в списке необходимые файлы и запустите нужный плагин из меню "Инструменты" - отобразится форма запуска плагина. Если плагин имеет дополнительные параметры, на этой форме отразятся соответствующие поля для ввода дополнительных параметров. После нажатия кнопки "OK" Libro2 запустит выбранный плагин для каждого файла из выбранного списка. В момент обработки на экране будет отображаться ее прогресс, по окончании будет выведен перечень ошибок или сообщений плагина для выбранного списка файлов.

Руководство по написанию плагинов

Как выше уже было сказано, файл плагина должен размещаться в соответствующей папке и иметь расширение .py. Программный код плагина пишется на языке Python с использованием системных и специализированных модулей, поставляемых в составе Libro2. Полный перечень поддерживаемых модулей см. в разделе "Список поддерживаемых модулей".

Плагин должен содержать один класс типа MetaPlugin или FilePlugin

from plugin_collection import MetaPlugin

class MyPlugin(MetaPlugin):
    ...

В классе должны быть определены два метода:

  • init - метод инициализации системных параметров, дополнительных параметров и атрибутов плагина
  • perform_operation - метод, содержащий реализацию непосредственной фунциональности плагина.

Также дополнительно класс может содержать реализацию метода validate.

Порядок вызова методов следующий. Метод init вызывается каждый раз при вызове плагина, непосредственно перед отображением формы плагина. Метод validate вызывается после изменения значения любого специального атрибута и нажатия кнопки OK на форме плагина (детали см. ниже). Метод perform_operation вызывается последовательно для каждого файла из выделенных в списке файлов.

Метод init

В методе init должны быть установлены значения следующих системных параметров плагина:

  • _title - название плагина, которое помещается в меню "Инструменты",
  • _description - необязательное описание плагина,
  • _hotkey - "горячая" клавиша, с помощью которой можно запустить плагин (необязательный атрибут),
  • _is_context_menu - помещать ли плагин в контекстное меню списка файлов (необязательный атрибут).

Также здесь могут быть определены дополнительные атрибуты класса плагина (см. ниже).

from plugin_collection import MetaPlugin

class MyPlugin(MetaPlugin):
    def init(self):
        self._title = 'My plugin title'
        self._description = 'My plugin description'
        self._hotkey = 'Ctrl-J'
        self._is_context_menu = True
    ...

Метод perform_operation

Метод perform_operation является входной точкой для запуска плагина. Входные и выходные параметры определяются типом базового класса плагина.

Для класса типа MetaPlugin perform_operation получает на вход экземпляр класса ebookmeta.Metadata, содержащий метаданные обрабатываемого файла. Возвращаемое значение - также экземпляр класса ebookmeta.Metadata, содержащий измененные метаданные обрабатываемого файла. Полученные метаданные Libro2 самостоятельно записывает в обрабатываемый файл. Если в результате обработки не требуется сохранять изменение, метод должен вернуть None.

from plugin_collection import MetaPlugin

class MyPlugin(MetaPlugin):
    def init(self):
        self._title = 'My plugin title'

    def perform_operation(self, meta):
        meta.title = 'New book title'
        return meta
    ...

Для класса типа FilePlugin метод preform_operation получает на вход полное имя обрабатываемого файла. Возвращаемое значение - новое имя файла или None, если не требуется обработка. После завершения работы плагина Libro2 заменит в списке обрабатываемый файл на полученный и перечитает метаданные.

from plugin_collection import MetaPlugin
import shutil

class MyPlugin(MetaPlugin):
    def init(self):
        self._title= 'My plugin title'
    
    def perform_operation(self, filename):
        new_filename = 'file1.fb2'
        shutil.copy(filename, new_filename)
        return new_filename
    ...

Дополнительные атрибуты плагина

В методе init класса, описывающего плагин вы можете определять собственные атрибуты. Поддерживаются все типы данных Python. Например:

from plugin_collection import FilePlugin
 def init(self):
        self._title = 'My plugin title'
        self.my_str_attr = 'Some value'
        self.my_int_attr = 1

Также вы можете определить специальные атрибуты, значения которых можно вводить на форме запуска плагина. Каждому типу параметра соответствует свой элемент ввода.

Поддерживаются следующие типы специальных атрибутов:

  • ТextField - на форме отражается поле ввода,
  • BoolField - на форме отражается checkbox,
  • ChoiceField - на форме отражается выпадающий список (combobox),
  • FolderField - на форме отражается элемент для выбора каталога,
  • FileField - на форме отражается элемент для выбора файла.

Инициализация дополнительных параметров производится в методе init. Также из модуля plugin_collection необходимо импортировать соответствующие типы для описания специальных атрибутов. Введенные на форме значения внутри методов preform_operation и validate можно получить, обратившись к свойству value соответствующего атрибута:

from plugin_collection import FilePlugin, TextField, BoolField

class MyPlugin(FilePlugin):
    def init(self):
        self._title = 'My plugin title'

        self.bool_attr = BoolField(label='Bool attribute label')
        self.bool_attr.value = False
     
        self.text_attr = TextField(label='Text attribute label')
        self.text_attr.value = 'Some text'
    def preform_operation(self, file):
        if self.bool_attr.value:
            new_file_name = self.text_attr.value 
   ...

Все типы специальных атрибутов имеют следующие свойства (все свойства доступны для записи и чтения):

  • value - значение атрибута.
  • label - метка поля ввода, связанного с данным атрибутом.
  • enabled - данное свойство позволяет управлять доступностью связанного с атрибутом поля ввода формы.
  • visible - данное свойство позволяет управлять видимостью связанного с атрибутом поля ввода формы.

Кроме того, тип ChoiceField имеет свойство items, содержащий список доступных для выбора элементов.

Метод validate

Данный метод позволяет реализовать проверку введенных на форме плагина значений специальных атрибутов, а также позволяет управлять поведением элементов формы в зависимости от свойств связанных атрибутов.

Метод validate вызывается после изменения значения в поле ввода формы, а также после того, как пользователь нажал кнопку "OK".

Метод имеет один параметр source, содержащий ссылку на атрибут, значение которого было изменено на форме, либо None, если была нажата кнопка "OK".

from plugin_collection import FilePlugin, BoolField, FolderField, DebugException

class MyPlugin(FilePlugin):
    def init(self):
        self._title = 'Тестовый плагин'

        # Объявляем специальные атрибуты, которые будут отражаться на форме 
        self.is_copy_to_folder = BoolField(label='Копировать файлы в папку')
        self.folder_to_copy = FolderField(label='Папка для копирования')
 
        # Инициализируем начальное значение
        # (на форме связанный с атрибутом чекбокс будет выключен 
        self.is_copy_to_folder.value = False
        # На форме связанное поле будет недоступно для ввода
        self.folder_to_copy.enabled = False
        ...

    def validate(self, source):
        if source == self.is_copy_to_folder:
            # Пользователь поставил отметку в чекбоксе - 
            # открываем folder_to_copy на редактирование 
            self.folder_to_copy.enabled = True
        elif source is None:
             # Нажали кнопку OK - самое время проверить введенные на форме значения
             if not self.folder_to_copy.value and self.is_copy_to_folder.value:  
                 raise DebugException('Вы не указали папку для копирования!')

    def preform_operation(self, file):
        ...

Сохранение и восстановление значений атрибутов плагина в системных настройках

Вы можете сохранять и в последующем восстанавливать введенные значения дополнительных параметров в системных настройка Libro2. Для этого существуют соответствующие методы save_settings и load_settings:

from plugin_collection import FilePlugin, TextField, DebugException

class MyPlugin(FilePlugin):
    def init(self):
        self._title = 'My plugin title'

        self.param1 = TextField(label='Label for field')
        # Чтение значения из настроек
        self.param1.value = self.load_settings('MyPlugin.Param1', default_value='Param1 value')
        ...

    def validate(self, source):
        ...
        if source is None:
           # Запись значения в настройки. Запись обязательно делать в секции source is None!
           self.save_settings('MyPlugin1.Param1', value=self.param1.value)

    def preform_operation(self, file):
        ...

Примеры плагинов

Пример 1. MetaPlugin

Плагин удаляет отчества у всех авторов книги

from plugin_collection import MetaPlugin
from config import locale


class RemoveAuthorMiddleName(MetaPlugin):
    def init(self):
        if locale == 'ru_RU':
            self._title = 'Удалить отчество у автора'
            self._description = ('Некотрые предпочитают в библиотеке видеть '
                                 'автора в формате "Имя Фамилия" вместо '
                                 '"Иия Отчество Фамилия". Например, Виктор '
                                 'Пелевин вместо Виктор Олегович Пелевин. '
                                 'Данный плагин удаляет отчество у автора, '
                                 'если оно присутствует.')
        else:
            self._title = 'Remove author\'s middle name'
            self._description = ('Remove middle name for each author'
                                 'in authors list')

    def perform_operation(self, meta):
        new_authors = []

        for author in meta.author_list:
            author_part = author.split()
            if len(author_part) == 3: # Author have middle name
                new_author = author_part[0] + ' ' + author_part[2]
                new_authors.append(new_author)
            else:
                new_authors.append(author)

        meta.set_author_list_from_string(','.join(new_authors))

        return meta

Пример 2. MetaPlugin

Плагин автоматически нумерует серию для выбранных книг. Нумерация выполняется в соответствии с установленным порядком сортировки в программе

from plugin_collection import MetaPlugin, DebugException, TextField
from config import locale


class SeriesIndexer(MetaPlugin):
    def init(self):
        default_title = self.load_settings('SeriesIdx.Title', default_value='')
        start_index = self.load_settings('SeriesIdx.StartValue',
                                         default_value='1')

        self.series_title = TextField()
        self.series_index_start = TextField()

        self.series_title.value = default_title
        self.series_index_start.value = int(start_index)

        if locale == 'ru_RU':
            self._title = 'Автоматическая нумерация серии'
            self._description = ('Плагин нумерует серию в выбранных книгах '
                                 'согласно сортировке в списке')
            self.series_title.label = 'Название серии (опционально)'
            self.series_index_start.label = 'Начинать индексирование с'
        else:
            self._title = 'Series index generator'
            self._description = 'Generate series index in book sort order'

            self.series_title.label = 'Series title (optional)'
            self.series_index_start.label = 'Series index start value'

    def validate(self, source):
        if source is None:
            try:
                self.start_index = int(self.series_index_start.value)
                self.save_settings('SeriesIdx.Title',
                                   value=self.series_title.value)
                self.save_settings('SeriesIdx.StartValue',
                                   value=self.start_index)
            except ValueError:
                if locale == 'ru_RU':
                    raise DebugException(
                        'Неверное значение начального индекса!')
                else:
                    raise DebugException('Wrong series index start value!')

    def perform_operation(self, meta):
        if self.series_title.value:
            meta.series = self.series_title.value
        meta.series_index = self.start_index
        self.start_index += 1

        return meta

Пример 3. FilePlugin

Плагин архивирует файлы .fb2 в .fb2.zip

from plugin_collection import FilePlugin, DebugException, BoolField
from ebookmeta.myzipfile import ZipFile, ZIP_DEFLATED
import os
from config import locale


class ZipFb2(FilePlugin):
    def init(self):
        self._hotkey = 'Ctrl+Z'
        self._is_context_menu = True

        self.delete_source = BoolField()
        self.delete_source.value = False

        if locale == 'ru_RU':
            self._title = 'Упаковать fb2 в fb2.zip'
            self._description = ('Преобразовывает файлы fb2 в формат fb2.zip. '
                                 'Если указано, после преобразования исходный '
                                 'fb2 удаляется.')
            self.delete_source.label = 'Удалить исходные файлы после архивации'
        else:
            self._title = 'Zip fb2 into fb2.zip'
            self._description = ('Zip fb2 files into fb2.zip, '
                                 'then delete original fb2, if specified.')
            self.delete_source.label = 'Delete original fb2 after zipping'

    def perform_operation(self, file):
        if file.lower().endswith('.fb2'):
            file_path = os.path.dirname(file)
            file_name = os.path.basename(file)
            zip_name = file + '.zip'

            if not os.path.exists(zip_name):
                os.chdir(file_path)
                zip = ZipFile(zip_name, mode='w', compression=ZIP_DEFLATED)
                zip.write(file_name)
                zip.close()

                if self.delete_source.value:
                    os.remove(file)

                return zip_name
            else:
                if locale == 'ru_RU':
                    raise DebugException(f'Файл "{zip_name}" уже существует!')
                else:
                    raise DebugException(f'File "{zip_name}" already exist!')
        else:
            if file.lower().endswith('.fb2.zip'):
                if locale == 'ru_RU':
                    raise DebugException(f'"{file}" уже упакован в fb2.zip.')
                else:
                    raise DebugException(f'"{file}" zipped already.')
            else:
                if locale == 'ru_RU':
                    raise DebugException(f'"{file}" не является файлом fb2!')
                else:
                    raise DebugException(f'"{file}" not fb2 file!')

Список поддерживаемых модулей

Системные модули Python

  • os - модуль содержит интерфейсы для различных функций операционной системы. Наиболее употребительным является подмодуль os.path, содержащий функции по работе с файлами.
  • sys - модуль содержит системные параметры и функции.
  • shutil - набор высокоуровневых файловых операций - копирование, перемещение, каскадное удаление и др.
  • lxml - модуль для работы с xml и html.
  • json - модуль для работы с json
  • codecs - модуль содержит набор классов для работы с различными кодировками. В основном используется для чтения/записи файлов в кодировках с поддержкой кириллицы.
  • traceback - модуль для трассировки исключений. Используется для получения отладочной информации.

Модули Libro2

  • plugin_collection - модуль для поддержки создания плагинов
  • ebookmeta- модуль, содержащий классы и функции для работы с метаданными файлов в формате fb2 и epub.
  • ebookmeta.myzipfile - модуль для работы с zip-архивами. Спецификация модуля соответствует системному модулю ZipFile.