pyuno/source/officehelper.py |  234 +++++++++++++++++++++++++++++++++----------
 1 file changed, 184 insertions(+), 50 deletions(-)

New commits:
commit d8978a8c4ffabd6b36a691fd3e2df68563808234
Author:     Alain Romedenne <alain.romede...@libreoffice.org>
AuthorDate: Thu Feb 29 10:23:55 2024 +0100
Commit:     Noel Grandin <noel.gran...@collabora.co.uk>
CommitDate: Sat Mar 2 13:00:12 2024 +0100

    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.gran...@collabora.co.uk>

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

Reply via email to