tdf#116156 tdf#157162 tdf#159528 Fix glitches in officehelper.py
- MacOs & GNU/Linux distributions are supported, next to Windows
- Connection attempts are customisable
- Reporting to console can be configured
- Python source doc. added
- service memory cleanup suggestion examples intended for QA as well as inclusion in wiki pages
officehelper documentation:
https://wiki.documentfoundation.org/Documentation/DevGuide/First_Steps#First_Contact
Change-Id: I6a7c19429e78a1736e4a1479c4bbc1950228d93f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164118
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
diff --git a/pyuno/source/officehelper.py b/pyuno/source/officehelper.py
index 53bd594..5c89ef7 100644
--- a/pyuno/source/officehelper.py
+++ b/pyuno/source/officehelper.py
@@ -17,14 +17,56 @@
# the License at http://www.apache.org/licenses/LICENSE-2.0 .
#
#
# Translated to python from "Bootstrap.java" by Kim Kulak
#
""" Bootstrap OOo and PyUNO Runtime.
import os
import random
from sys import platform
from time import sleep
The soffice process is started opening a named pipe of random name, then
the local context is used to access the pipe. This function directly
returns the remote component context, from whereon you can get the
ServiceManager by calling getServiceManager() on the returned object.
This module supports the following environments:
- Windows
- GNU/Linux derivatives
- MacOS X
A configurable time-out allows to wait for LibO process to be completed.
Multiple attempts can be set in order to connect to LibO as a service.
Specific versions of the office suite can be started.
Instructions:
1. Include one of the below examples in your Python macro
2. Run your LibreOffice script from your preferred IDE
Imports:
os, random, subprocess, sys - `bootstrap`
itertools, time - `retry` decorator
Exceptions:
BootstrapException - in `bootstrap`
NoConnectException - in `bootstrap`
Functions:
`bootstrap`
`retry` decorator
Acknowledgments:
- Kim Kulak for original officehelper.py Python translation from bootstrap.java
- ActiveState, for `retry` python decorator
warning:: Tested platforms are Linux, MacOS X & Windows
AppImage, Flatpak, Snap and the like have not be validated
:References:
.. _ActiveState retry Python decorator: http://code.activestate.com/recipes/580745-retry-decorator-in-python/
"""
import os, random, subprocess # in bootstrap()
from sys import platform # in bootstrap()
import itertools, time # in retry() decorator
import uno
from com.sun.star.connection import NoConnectException
@@ -34,54 +76,146 @@ from com.sun.star.uno import Exception as UnoException
class BootstrapException(UnoException):
pass
def bootstrap():
def retry(delays=(0, 1, 5, 30, 180, 600, 3600),
exception=Exception,
report=lambda *args: None):
"""retry Python decorator
Credit:
http://code.activestate.com/recipes/580745-retry-decorator-in-python/
"""
def wrapper(function):
def wrapped(*args, **kwargs):
problems = []
for delay in itertools.chain(delays, [None]):
try:
return function(*args, **kwargs)
except exception as problem:
problems.append(problem)
if delay is None:
report("retryable failed definitely:", problems)
# raise
else:
report("retryable failed:", problem,
"-- delaying for %ds" % delay)
time.sleep(delay)
return None
return wrapped
return wrapper
def bootstrap(soffice=None,delays=(1, 3, 5, 7), report=lambda *args: None):
# 4 connection attempts; sleeping 1, 3, 5 and 7 seconds
# no report to console
"""Bootstrap OOo and PyUNO Runtime.
The soffice process is started opening a named pipe of random name, then the local context is used
to access the pipe. This function directly returns the remote component context, from whereon you can
get the ServiceManager by calling getServiceManager() on the returned object.
"""
try:
# soffice script used on *ix, Mac; soffice.exe used on Win
The soffice process is started opening a named pipe of random name,
then the local context is used to access the pipe. This function
directly returns the remote component context, from whereon you can
get the ServiceManager by calling getServiceManager() on the
returned object.
Examples:
i. Start LO as a service, get its remote component context
import officehelper
ctx = officehelper.bootstrap()
# your code goes here
ii. Wait longer for LO to start, request context multiples times
+ Report processing in console
import officehelper as oh
ctx = oh.bootstrap(delays=(5,10,15,20),report=print) # every 5 sec.
# your code goes here
iii. Use a specific LibreOffice copy
from officehelper import bootstrap
ctx = bootstrap(soffice='USB:\PortableApps\libO-7.6\App\libreoffice\program\soffice.exe'))
# your code goes here
"""
if soffice: # soffice fully qualified path as parm
sOffice = soffice
else: # soffice script used on *ix, Mac; soffice.exe used on Win
if "UNO_PATH" in os.environ:
sOffice = os.environ["UNO_PATH"]
else:
sOffice = "" # lets hope for the best
sOffice = "" # let's hope for the best
sOffice = os.path.join(sOffice, "soffice")
if platform.startswith("win"):
sOffice += ".exe"
sOffice = '"' + sOffice + '"' # accommodate ' ' spaces in filename
elif platform=="darwin": # any other un-hardcoded suggestion?
sOffice = "/Applications/LibreOffice.App/Contents/MacOS/soffice"
#print(sOffice)
# Generate a random pipe name.
random.seed()
sPipeName = "uno" + str(random.random())[2:]
# Generate a random pipe name.
random.seed()
sPipeName = "uno" + str(random.random())[2:]
# Start the office process, don't check for exit status since an exception is caught anyway if the office terminates unexpectedly.
cmdArray = (sOffice, "--nologo", "--nodefault", "".join(["--accept=pipe,name=", sPipeName, ";urp;"]))
os.spawnv(os.P_NOWAIT, sOffice, cmdArray)
# Start the office process
# ---------
connect_string = ''.join(['pipe,name=', sPipeName, ';urp;'])
command = ' '.join([sOffice, '--nologo', '--nodefault', '--accept="' + connect_string + '"'])
#print(command)
subprocess.Popen(command, shell=True)
xLocalContext = uno.getComponentContext()
resolver = xLocalContext.ServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", xLocalContext)
sConnect = "".join(["uno:pipe,name=", sPipeName, ";urp;StarOffice.ComponentContext"])
# Connect to a started office instance
# Wait until an office is started, but loop only nLoop times (can we do this better???)
nLoop = 20
while True:
try:
xContext = resolver.resolve(sConnect)
break
except NoConnectException:
nLoop -= 1
if nLoop <= 0:
raise BootstrapException("Cannot connect to soffice server.", None)
sleep(0.5) # Sleep 1/2 second.
xLocalContext = uno.getComponentContext()
resolver = xLocalContext.ServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", xLocalContext)
sConnect = "".join(['uno:', connect_string, 'StarOffice.ComponentContext'])
except BootstrapException:
raise
except Exception as e: # Any other exception
raise BootstrapException("Caught exception " + str(e), None)
@retry(delays=delays,
exception=NoConnectException,
report=report)
def resolve():
return resolver.resolve(sConnect) # may raise NoConnectException
return xContext
ctx = resolve()
if not ctx:
raise BootstrapException
return ctx
# vim: set shiftwidth=4 softtabstop=4 expandtab:
# ============
# Unit Testing
# ============
if __name__ == "__main__":
# ~ dir(__name__)
# ~ help(__name__)
# ~ help(bootstrap)
# ~ exit()
#from officehelper import bootstrap, BootstrapException
#from sys import platform
#import subprocess, time
try:
ctx = bootstrap()
# use delays=[0,] to raise BootstrapException
except BootstrapException: # stop soffice as a service
if platform.startswith("win"):
subprocess.Popen(['taskkill', '/f', '/t', '/im', 'soffice.exe'])
elif platform == "linux":
time.sleep(5)
subprocess.Popen(['killall', "soffice.bin"])
elif platform == "darwin":
time.sleep(15)
subprocess.Popen(['pkill', '-f', 'LibreOffice'])
raise BootstrapException()
# your code goes here
time.sleep(10)
if ctx: # stop soffice as a service
smgr = ctx.getServiceManager()
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
desktop.terminate()
# vim: set shiftwidth=4 softtabstop=4 expandtab
\ No newline at end of file