Автоматический запуск Libre/OpenOffice в режиме прослушивания из Python

Libre/Open Office предоставляют возможность работы с офисом через UNO API. Для того, чтобы можно было обратиться к офису необходимо запустить его в режиме прослушивания.

Например:

soffice --accept="socket,host=localhost,port=2002;urp;"


Данный подход вполне логичен и понятен с точки зрения разработчиков офиса, но несет ряд неудобств. В частности, нужно самостоятельно запускать Libre/Open Office в режиме прослушивания. Лично мне не понятно, почему разработчики поленились и не предоставили функции запуска офиса. Ну да ладно, было бы все сделано, не нужны были бы программисты. Посему будем решать задачу своими силами.
Самый простой способ решить данную задачу — поместить строку запуска офиса в скриптовый файл. В нем запускать сперва офис, а потом свое приложение. Но что если это, например, библиотека и нет возможности обратиться к ней через скриптовый файл. К тому же нужно не просто дождаться запуска офиса, но еще и дождаться пока он будет в режиме прослушивания. В общем этот подход годится лишь для тестовых задач, не более.
Я остановился на следующей реализации:
1. Форк процесса, который запускает офис в режиме прослушивания.
2. С определенной периодичностью пытаться обращаться к офису, пока попытка не окажется успешной.
3. Если через определенное время попытка подключения к офису не будет успешной, то генерировать исключение com.sun.star.connection.NoConnectException.

# -*- coding: utf-8 -*-

import os
import subprocess
import sys
import time
import uno

NoConnectException = uno.getClass(
        "com.sun.star.connection.NoConnectException")

###############################################################################
def init_office():
    """
    Test Libre/Open Office to be launched in the listening mode
    """
    connection_string = "uno:socket,host=localhost,port=2002;urp;\
StarOffice.ComponentContext"
    oLocal = uno.getComponentContext()
    oResolver = \
        oLocal.ServiceManager.createInstanceWithContext(
                        "com.sun.star.bridge.UnoUrlResolver", oLocal)
    if oResolver:
        oContext = oResolver.resolve(connection_string)
        oDesktop = oContext.ServiceManager.\
            createInstanceWithContext("com.sun.star.frame.Desktop",
                                      oContext)

###############################################################################
def start_office(timeout=30, attempt_period=0.1, office='soffice --accept="socket,host=localhost,port=2002;urp;"'):
    """
    Starts Libre/Open Office with a listening socket.

    @type  timeout: int
    @param timeout: Timeout for starting Libre/Open Office in seconds

    @type  attempt_period: int
    @param attempt_period: Timeout between attempts in seconds

    @type  office: string
    @param office: Libre/Open Office startup string
    """
    ###########################################################################
    def start_office_instance(office):
        """
        Starts Libre/Open Office with a listening socket.

        @type  office: string
        @param office: Libre/Open Office startup string
        """
        # Fork to execute Office
        if os.fork():
            return

        # Start OpenOffice.org and report any errors that occur.
        try:
            retcode = subprocess.call(office, shell=True)
            if retcode < 0:
                print (sys.stderr,
                       "Office was terminated by signal",
                       -retcode)
            elif retcode > 0:
                print (sys.stderr,
                       "Office returned",
                       retcode)
        except OSError as e:
            print (sys.stderr, "Execution failed:", e)

        # Terminate this process when Office has closed.
        raise SystemExit()

    ###########################################################################
    waiting = False
    try:
        init_office()
    except NoConnectException as e:
        waiting = True
        start_office_instance(office)

    if waiting:
        steps = int(timeout/attempt_period)
        exception = None
        for i in range(steps + 1):
            try:
                init_office()
                break
            except NoConnectException as e:
                exception = e
                time.sleep(attempt_period)
        else:
            if exception:
                raise NoConnectException(exception)
            else:
                raise NoConnectException()

###############################################################################

start_office()

Данный пример будет пытаться запустить Libre/Open Office с периодичностью 0.1 секунда на протяжении 30 секунд.
В качестве теста на наличие запущенного в режиме прослушивания офиса используется функция init_office (). Вместо нее может быть использована любая другая из Вашей библиотеки.

Ранее, в статье «PyOOCalc — Библиотека для генерации отчетов, счетов Libre/Open Office Calc на Python», я описал как проще работать с Libre/OpenOffice для определенной категории задач. Но библиотека PyOOCalc на имела возможности автоматического запуска офиса в режиме прослушивания, и вышеприведенный код можно переписать следующим образом.
Вместо функции init_office () можно написать:

import pyoocalc
pyoocalc.Document()


Это так же может быть любая другая библиотека. Необходимо вызывать метод, который будет пытаться подключиться к Libre/Open Office.

Так же я добавил возможность автоматического запуска офиса в библиотеку PyOOCalc.
Пример использования:

import pyoocalc
doc = pyoocalc.Document(autostart=True)
doc.new_document()

© Habrahabr.ru