summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlain Romedenne <alain.romedenne@libreoffice.org>2024-02-29 10:23:55 +0100
committerNoel Grandin <noel.grandin@collabora.co.uk>2024-03-02 13:00:12 +0100
commitd8978a8c4ffabd6b36a691fd3e2df68563808234 (patch)
treecfb2b2d77c6bbc6796ec642a50dbc4875fafbf2f
parentf7d3b846ccdc979ebb3dfbff21eabc9d17eb204a (diff)
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>
-rw-r--r--pyuno/source/officehelper.py234
1 files changed, 184 insertions, 50 deletions
diff --git a/pyuno/source/officehelper.py b/pyuno/source/officehelper.py
index 53bd5943e3c0..5c89ef7ea0bf 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.
+
+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`
-import os
-import random
-from sys import platform
-from time import sleep
+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:]
+
+ # 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)
+
+ # Connect to a started office instance
+
+ xLocalContext = uno.getComponentContext()
+ resolver = xLocalContext.ServiceManager.createInstanceWithContext(
+ "com.sun.star.bridge.UnoUrlResolver", xLocalContext)
+ sConnect = "".join(['uno:', connect_string, 'StarOffice.ComponentContext'])
+
+ @retry(delays=delays,
+ exception=NoConnectException,
+ report=report)
+ def resolve():
+ return resolver.resolve(sConnect) # may raise NoConnectException
+
+ ctx = resolve()
+ if not ctx:
+ raise BootstrapException
+ return ctx
+
+
+# ============
+# 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()
- # 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)
-
- # ---------
-
- xLocalContext = uno.getComponentContext()
- resolver = xLocalContext.ServiceManager.createInstanceWithContext(
- "com.sun.star.bridge.UnoUrlResolver", xLocalContext)
- sConnect = "".join(["uno:pipe,name=", sPipeName, ";urp;StarOffice.ComponentContext"])
-
- # 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.
-
- except BootstrapException:
- raise
- except Exception as e: # Any other exception
- raise BootstrapException("Caught exception " + str(e), None)
-
- return xContext
-
-# vim: set shiftwidth=4 softtabstop=4 expandtab:
+# vim: set shiftwidth=4 softtabstop=4 expandtab \ No newline at end of file