summaryrefslogtreecommitdiffstats
path: root/desktop/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /desktop/source
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'desktop/source')
-rw-r--r--desktop/source/app/app.cxx2582
-rw-r--r--desktop/source/app/appinit.cxx279
-rw-r--r--desktop/source/app/check_ext_deps.cxx427
-rw-r--r--desktop/source/app/cmdlineargs.cxx786
-rw-r--r--desktop/source/app/cmdlineargs.hxx186
-rw-r--r--desktop/source/app/cmdlinehelp.cxx266
-rw-r--r--desktop/source/app/cmdlinehelp.hxx30
-rw-r--r--desktop/source/app/crashreport.cxx477
-rw-r--r--desktop/source/app/desktopcontext.cxx55
-rw-r--r--desktop/source/app/desktopcontext.hxx40
-rw-r--r--desktop/source/app/dispatchwatcher.cxx863
-rw-r--r--desktop/source/app/dispatchwatcher.hxx86
-rw-r--r--desktop/source/app/langselect.cxx148
-rw-r--r--desktop/source/app/langselect.hxx29
-rw-r--r--desktop/source/app/lockfile2.cxx60
-rw-r--r--desktop/source/app/main.c62
-rw-r--r--desktop/source/app/officeipcthread.cxx1357
-rw-r--r--desktop/source/app/officeipcthread.hxx157
-rw-r--r--desktop/source/app/opencl.cxx257
-rw-r--r--desktop/source/app/sofficemain.cxx104
-rw-r--r--desktop/source/app/sofficemain.h34
-rw-r--r--desktop/source/app/updater.cxx920
-rw-r--r--desktop/source/app/updater.hxx37
-rw-r--r--desktop/source/app/userinstall.cxx176
-rw-r--r--desktop/source/app/userinstall.hxx38
-rw-r--r--desktop/source/deployment/deployment.component65
-rw-r--r--desktop/source/deployment/dp_log.cxx143
-rw-r--r--desktop/source/deployment/dp_persmap.cxx312
-rw-r--r--desktop/source/deployment/dp_xml.cxx51
-rw-r--r--desktop/source/deployment/gui/deploymentgui.component34
-rw-r--r--desktop/source/deployment/gui/dp_gui.h28
-rw-r--r--desktop/source/deployment/gui/dp_gui_dependencydialog.cxx42
-rw-r--r--desktop/source/deployment/gui/dp_gui_dependencydialog.hxx49
-rw-r--r--desktop/source/deployment/gui/dp_gui_dialog2.cxx1403
-rw-r--r--desktop/source/deployment/gui/dp_gui_dialog2.hxx267
-rw-r--r--desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx1115
-rw-r--r--desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx94
-rw-r--r--desktop/source/deployment/gui/dp_gui_extlistbox.cxx1143
-rw-r--r--desktop/source/deployment/gui/dp_gui_extlistbox.hxx215
-rw-r--r--desktop/source/deployment/gui/dp_gui_service.cxx316
-rw-r--r--desktop/source/deployment/gui/dp_gui_theextmgr.cxx539
-rw-r--r--desktop/source/deployment/gui/dp_gui_theextmgr.hxx127
-rw-r--r--desktop/source/deployment/gui/dp_gui_updatedata.hxx74
-rw-r--r--desktop/source/deployment/gui/dp_gui_updatedialog.cxx988
-rw-r--r--desktop/source/deployment/gui/dp_gui_updatedialog.hxx168
-rw-r--r--desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx662
-rw-r--r--desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx112
-rw-r--r--desktop/source/deployment/gui/license_dialog.cxx244
-rw-r--r--desktop/source/deployment/gui/license_dialog.hxx53
-rw-r--r--desktop/source/deployment/inc/dp_dependencies.hxx72
-rw-r--r--desktop/source/deployment/inc/dp_descriptioninfoset.hxx287
-rw-r--r--desktop/source/deployment/inc/dp_identifier.hxx84
-rw-r--r--desktop/source/deployment/inc/dp_interact.h140
-rw-r--r--desktop/source/deployment/inc/dp_misc_api.hxx31
-rw-r--r--desktop/source/deployment/inc/dp_package.hxx42
-rw-r--r--desktop/source/deployment/inc/dp_persmap.h60
-rw-r--r--desktop/source/deployment/inc/dp_platform.hxx41
-rw-r--r--desktop/source/deployment/inc/dp_registry.hxx40
-rw-r--r--desktop/source/deployment/inc/dp_resource.h30
-rw-r--r--desktop/source/deployment/inc/dp_ucb.h94
-rw-r--r--desktop/source/deployment/inc/dp_update.hxx136
-rw-r--r--desktop/source/deployment/inc/dp_version.hxx36
-rw-r--r--desktop/source/deployment/inc/dp_xml.h42
-rw-r--r--desktop/source/deployment/inc/lockfile.hxx89
-rw-r--r--desktop/source/deployment/manager/dp_activepackages.cxx217
-rw-r--r--desktop/source/deployment/manager/dp_activepackages.hxx95
-rw-r--r--desktop/source/deployment/manager/dp_commandenvironments.cxx246
-rw-r--r--desktop/source/deployment/manager/dp_commandenvironments.hxx139
-rw-r--r--desktop/source/deployment/manager/dp_extensionmanager.cxx1432
-rw-r--r--desktop/source/deployment/manager/dp_extensionmanager.hxx227
-rw-r--r--desktop/source/deployment/manager/dp_informationprovider.cxx338
-rw-r--r--desktop/source/deployment/manager/dp_manager.cxx1597
-rw-r--r--desktop/source/deployment/manager/dp_manager.h233
-rw-r--r--desktop/source/deployment/manager/dp_managerfac.cxx186
-rw-r--r--desktop/source/deployment/manager/dp_properties.cxx145
-rw-r--r--desktop/source/deployment/manager/dp_properties.hxx58
-rw-r--r--desktop/source/deployment/misc/dp_dependencies.cxx196
-rw-r--r--desktop/source/deployment/misc/dp_descriptioninfoset.cxx809
-rw-r--r--desktop/source/deployment/misc/dp_identifier.cxx56
-rw-r--r--desktop/source/deployment/misc/dp_interact.cxx137
-rw-r--r--desktop/source/deployment/misc/dp_misc.cxx562
-rw-r--r--desktop/source/deployment/misc/dp_platform.cxx210
-rw-r--r--desktop/source/deployment/misc/dp_resource.cxx42
-rw-r--r--desktop/source/deployment/misc/dp_ucb.cxx304
-rw-r--r--desktop/source/deployment/misc/dp_update.cxx408
-rw-r--r--desktop/source/deployment/misc/dp_version.cxx64
-rw-r--r--desktop/source/deployment/misc/lockfile.cxx213
-rw-r--r--desktop/source/deployment/registry/component/dp_compbackenddb.cxx131
-rw-r--r--desktop/source/deployment/registry/component/dp_compbackenddb.hxx95
-rw-r--r--desktop/source/deployment/registry/component/dp_component.cxx1714
-rw-r--r--desktop/source/deployment/registry/configuration/dp_configuration.cxx786
-rw-r--r--desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx161
-rw-r--r--desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx74
-rw-r--r--desktop/source/deployment/registry/dp_backend.cxx769
-rw-r--r--desktop/source/deployment/registry/dp_backenddb.cxx655
-rw-r--r--desktop/source/deployment/registry/dp_registry.cxx526
-rw-r--r--desktop/source/deployment/registry/executable/dp_executable.cxx336
-rw-r--r--desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx64
-rw-r--r--desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx55
-rw-r--r--desktop/source/deployment/registry/help/README.md1
-rw-r--r--desktop/source/deployment/registry/help/dp_help.cxx621
-rw-r--r--desktop/source/deployment/registry/help/dp_helpbackenddb.cxx126
-rw-r--r--desktop/source/deployment/registry/help/dp_helpbackenddb.hxx70
-rw-r--r--desktop/source/deployment/registry/inc/dp_backend.h287
-rw-r--r--desktop/source/deployment/registry/inc/dp_backenddb.hxx165
-rw-r--r--desktop/source/deployment/registry/package/dp_extbackenddb.cxx111
-rw-r--r--desktop/source/deployment/registry/package/dp_extbackenddb.hxx69
-rw-r--r--desktop/source/deployment/registry/package/dp_package.cxx1594
-rw-r--r--desktop/source/deployment/registry/script/dp_lib_container.cxx64
-rw-r--r--desktop/source/deployment/registry/script/dp_lib_container.h48
-rw-r--r--desktop/source/deployment/registry/script/dp_script.cxx480
-rw-r--r--desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx64
-rw-r--r--desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx55
-rw-r--r--desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx103
-rw-r--r--desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx64
-rw-r--r--desktop/source/deployment/registry/sfwk/dp_sfwk.cxx378
-rw-r--r--desktop/source/inc/helpids.h27
-rw-r--r--desktop/source/lib/init.cxx8129
-rw-r--r--desktop/source/lib/lokandroid.cxx422
-rw-r--r--desktop/source/lib/lokclipboard.cxx243
-rw-r--r--desktop/source/lib/lokclipboard.hxx114
-rw-r--r--desktop/source/lib/lokinteractionhandler.cxx457
-rw-r--r--desktop/source/lib/lokinteractionhandler.hxx107
-rw-r--r--desktop/source/migration/migration.cxx1217
-rw-r--r--desktop/source/migration/migration_impl.hxx198
-rw-r--r--desktop/source/migration/services/basicmigration.cxx202
-rw-r--r--desktop/source/migration/services/basicmigration.hxx71
-rw-r--r--desktop/source/migration/services/cppumaker.mk27
-rw-r--r--desktop/source/migration/services/jvmfwk.cxx395
-rw-r--r--desktop/source/migration/services/jvmfwk.hxx33
-rw-r--r--desktop/source/migration/services/migrationoo2.component30
-rw-r--r--desktop/source/migration/services/migrationoo3.component26
-rw-r--r--desktop/source/migration/services/misc.hxx39
-rw-r--r--desktop/source/migration/services/oo3extensionmigration.cxx409
-rw-r--r--desktop/source/migration/services/oo3extensionmigration.hxx117
-rw-r--r--desktop/source/migration/services/wordbookmigration.cxx231
-rw-r--r--desktop/source/migration/services/wordbookmigration.hxx72
-rw-r--r--desktop/source/minidump/minidump.cxx239
-rw-r--r--desktop/source/minidump/minidump_upload.cxx47
-rw-r--r--desktop/source/offacc/acceptor.cxx254
-rw-r--r--desktop/source/offacc/acceptor.hxx95
-rw-r--r--desktop/source/offacc/offacc.component26
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_app.cxx655
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx387
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_main.c26
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_main.h34
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_misc.cxx455
-rw-r--r--desktop/source/pkgchk/unopkg/unopkg_shared.h119
-rw-r--r--desktop/source/splash/spl.component30
-rw-r--r--desktop/source/splash/splash.cxx622
-rw-r--r--desktop/source/splash/unxsplash.cxx128
-rw-r--r--desktop/source/splash/unxsplash.hxx53
152 files changed, 52240 insertions, 0 deletions
diff --git a/desktop/source/app/app.cxx b/desktop/source/app/app.cxx
new file mode 100644
index 0000000000..0d66a48daa
--- /dev/null
+++ b/desktop/source/app/app.cxx
@@ -0,0 +1,2582 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <config_features.h>
+#include <config_feature_desktop.h>
+#include <config_feature_opencl.h>
+#include <config_java.h>
+#include <config_folders.h>
+#include <config_extensions.h>
+#include <config_wasm_strip.h>
+
+#include <sal/config.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <string_view>
+
+#include <app.hxx>
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include "cmdlineargs.hxx"
+#include <lockfile.hxx>
+#include "userinstall.hxx"
+#include "desktopcontext.hxx"
+#include <migration.hxx>
+#include "officeipcthread.hxx"
+#if HAVE_FEATURE_UPDATE_MAR
+#include "updater.hxx"
+#endif
+
+#include <framework/desktop.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <svl/ctloptions.hxx>
+#include <svtools/javacontext.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/theAutoRecovery.hpp>
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <com/sun/star/frame/SessionListener.hpp>
+#include <com/sun/star/frame/XSynchronousDispatch.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/StartModule.hpp>
+#include <com/sun/star/view/XPrintable.hpp>
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/lang/ServiceNotRegisteredException.hpp>
+#include <com/sun/star/configuration/MissingBootstrapFileException.hpp>
+#include <com/sun/star/configuration/InvalidBootstrapFileException.hpp>
+#include <com/sun/star/configuration/InstallationIncompleteException.hpp>
+#include <com/sun/star/configuration/backend/BackendSetupException.hpp>
+#include <com/sun/star/configuration/backend/BackendAccessException.hpp>
+#include <com/sun/star/task/theJobExecutor.hpp>
+#include <com/sun/star/task/OfficeRestartManager.hpp>
+#include <com/sun/star/task/XRestartManager.hpp>
+#include <com/sun/star/document/XDocumentEventListener.hpp>
+#include <com/sun/star/office/Quickstart.hpp>
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <com/sun/star/loader/XImplementationLoader.hpp>
+
+#include <desktop/exithelper.h>
+#include <sal/log.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/fileurl.hxx>
+#include <comphelper/threadpool.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/backupfilehelper.hxx>
+#include <uno/current_context.hxx>
+#include <unotools/bootstrap.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <unotools/localfilehelper.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Office/Recovery.hxx>
+#include <officecfg/Office/Update.hxx>
+#include <officecfg/Setup.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/byteseq.hxx>
+#include <unotools/pathoptions.hxx>
+#if !ENABLE_WASM_STRIP_PINGUSER
+#include <unotools/VersionConfig.hxx>
+#endif
+#include <rtl/bootstrap.hxx>
+#include <vcl/test/GraphicsRenderTests.hxx>
+#include <vcl/help.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/settings.hxx>
+#include <sfx2/flatpak.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/app.hxx>
+#include <sfx2/safemode.hxx>
+#include <svl/itemset.hxx>
+#include <svl/eitem.hxx>
+#include <basic/sbstar.hxx>
+#include <desktop/crashreport.hxx>
+#include <tools/time.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <svtools/fontsubstconfig.hxx>
+#include <svtools/accessibilityoptions.hxx>
+#include <svtools/apearcfg.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/window.hxx>
+#include "langselect.hxx"
+#include <salhelper/thread.hxx>
+
+#if defined MACOSX
+#include <errno.h>
+#include <sys/wait.h>
+#endif
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <vcl/fileregistration.hxx>
+#endif
+
+#if defined(_WIN32)
+#include <process.h>
+#define GETPID _getpid
+#else
+#include <unistd.h>
+#define GETPID getpid
+#endif
+
+#include <strings.hxx>
+
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::view;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::system;
+using namespace ::com::sun::star::ui;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::container;
+
+namespace desktop
+{
+
+static oslSignalHandler pSignalHandler = nullptr;
+
+namespace {
+
+#if HAVE_FEATURE_EXTENSIONS
+
+// Remove any existing UserInstallation's extensions cache data remaining from
+// old installations. This addresses at least two problems:
+//
+// For one, apparently due to the old share/prereg/bundled mechanism (disabled
+// since 5c47e5f63a79a9e72ec4a100786b1bbf65137ed4 "fdo#51252 Disable copying
+// share/prereg/bundled to avoid startup crashes"), the user/extensions/bundled
+// cache could contain corrupted information (like a UNO component registered
+// twice, which got changed from active to passive registration in one LO
+// version, but the version of the corresponding bundled extension only
+// incremented in a later LO version).
+//
+// For another, UserInstallations have been seen in the wild where no extensions
+// were installed per-user (any longer), but user/uno_packages/cache/registry/
+// com.sun.star.comp.deployment.component.PackageRegistryBackend/*.rdb files
+// contained data nevertheless.
+//
+// When a LO upgrade is detected (i.e., no user/extensions/buildid or one
+// containing an old build ID), then user/extensions and
+// user/uno_packages/cache/registry/
+// com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc are
+// removed. That should prevent any problems starting the service manager due
+// to old junk. Later on in Desktop::SynchronizeExtensionRepositories, the
+// removed cache data is recreated.
+//
+// Multiple instances of soffice.bin can execute this code in parallel for a
+// single UserInstallation, as it is called before RequestHandler is set up.
+// Therefore, any errors here only lead to SAL_WARNs.
+//
+// At least in theory, this function could be removed again once no
+// UserInstallation can be poisoned by old junk any more.
+bool cleanExtensionCache() {
+ OUString buildId(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(buildId); //TODO: detect failure
+ OUString extDir(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
+ ":UserInstallation}/user/extensions");
+ rtl::Bootstrap::expandMacros(extDir); //TODO: detect failure
+ OUString buildIdFile(extDir + "/buildid");
+ osl::File fr(buildIdFile);
+ osl::FileBase::RC rc = fr.open(osl_File_OpenFlag_Read);
+ switch (rc) {
+ case osl::FileBase::E_None:
+ {
+ rtl::ByteSequence s1;
+ rc = fr.readLine(s1);
+ osl::FileBase::RC rc2 = fr.close();
+ SAL_WARN_IF(
+ rc2 != osl::FileBase::E_None, "desktop.app",
+ "cannot close " << fr.getURL() << " after reading: " << +rc2);
+ // readLine returns E_AGAIN for a zero-size file:
+ if (rc != osl::FileBase::E_None && rc != osl::FileBase::E_AGAIN) {
+ SAL_WARN( "desktop.app", "cannot read from " << fr.getURL() << ": " << +rc);
+ break;
+ }
+ OUString s2(
+ reinterpret_cast< char const * >(s1.getConstArray()),
+ s1.getLength(), RTL_TEXTENCODING_ISO_8859_1);
+ // using ISO 8859-1 avoids any and all conversion errors; the
+ // content should only be a subset of ASCII, anyway
+ if (s2 == buildId) {
+ return false;
+ }
+ break;
+ }
+ case osl::FileBase::E_NOENT:
+ break;
+ default:
+ SAL_WARN( "desktop.app", "cannot open " << fr.getURL() << " for reading: " << +rc);
+ break;
+ }
+ utl::removeTree(extDir);
+ OUString userRcFile(
+ "$UNO_USER_PACKAGES_CACHE/registry/"
+ "com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc");
+ rtl::Bootstrap::expandMacros(userRcFile); //TODO: detect failure
+ rc = osl::File::remove(userRcFile);
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None && rc != osl::FileBase::E_NOENT, "desktop.app",
+ "cannot remove file " << userRcFile << ": " << +rc);
+ rc = osl::Directory::createPath(extDir);
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None && rc != osl::FileBase::E_EXIST, "desktop.app",
+ "cannot create path " << extDir << ": " << +rc);
+ osl::File fw(buildIdFile);
+ rc = fw.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (rc != osl::FileBase::E_None) {
+ SAL_WARN( "desktop.app", "cannot open " << fw.getURL() << " for writing: " << +rc);
+ return true;
+ }
+ OString buf(OUStringToOString(buildId, RTL_TEXTENCODING_UTF8));
+ // using UTF-8 avoids almost all conversion errors (and buildid
+ // containing single surrogate halves should never happen, anyway); the
+ // content should only be a subset of ASCII, anyway
+ sal_uInt64 n = 0;
+ rc = fw.write(buf.getStr(), buf.getLength(), n);
+ SAL_WARN_IF(
+ (rc != osl::FileBase::E_None
+ || n != static_cast< sal_uInt32 >(buf.getLength())),
+ "desktop.app",
+ "cannot write to " << fw.getURL() << ": " << +rc << ", " << n);
+ rc = fw.close();
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None, "desktop.app",
+ "cannot close " << fw.getURL() << " after writing: " << +rc);
+ return true;
+}
+
+#endif
+
+bool shouldLaunchQuickstart()
+{
+ bool bQuickstart = Desktop::GetCommandLineArgs().IsQuickstart();
+ if (!bQuickstart)
+ {
+ SfxItemSetFixed<SID_ATTR_QUICKLAUNCHER, SID_ATTR_QUICKLAUNCHER> aQLSet(SfxGetpApp()->GetPool());
+ SfxApplication::GetOptions(aQLSet);
+ const SfxBoolItem* pLauncherItem = aQLSet.GetItemIfSet(SID_ATTR_QUICKLAUNCHER, false);
+ if (pLauncherItem)
+ bQuickstart = pLauncherItem->GetValue();
+ }
+ return bQuickstart;
+}
+
+void SetRestartState() {
+ try {
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::OfficeRestartInProgress::set(true, batch);
+ batch->commit();
+ } catch (css::uno::Exception) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+}
+
+void DoRestartActionsIfNecessary(bool quickstart) {
+ if (!quickstart)
+ return;
+
+ try {
+ if (officecfg::Setup::Office::OfficeRestartInProgress::get()) {
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::OfficeRestartInProgress::set(
+ false, batch);
+ batch->commit();
+ css::office::Quickstart::createStart(
+ comphelper::getProcessComponentContext(),
+ shouldLaunchQuickstart());
+ }
+ } catch (css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+}
+
+void RemoveIconCacheDirectory()
+{
+ // See getIconCacheUrl in vcl/source/image/ImplImageTree.cxx
+ OUString sUrl = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
+ "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache";
+ rtl::Bootstrap::expandMacros(sUrl);
+ utl::UCBContentHelper::Kill(sUrl);
+}
+
+}
+
+namespace {
+
+void runGraphicsRenderTests()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+#if !ENABLE_WASM_STRIP_PINGUSER
+ if (!utl::isProductVersionUpgraded(false))
+ {
+ return;
+ }
+#endif
+ GraphicsRenderTests TestObject;
+ TestObject.run();
+}
+
+
+OUString MakeStartupErrorMessage(std::u16string_view aErrorMessage)
+{
+ return DpResId(STR_BOOTSTRAP_ERR_CANNOT_START) + "\n" + aErrorMessage;
+}
+
+
+// shows a simple error box with the given message ... but exits from these process !
+// Fatal errors can't be solved by the process ... nor any recovery can help.
+// Mostly the installation was damaged and must be repaired manually .. or by calling
+// setup again.
+// On the other side we must make sure that no further actions will be possible within
+// the current office process ! No pipe requests, no menu/toolbar/shortcut actions
+// are allowed. Otherwise we will force a "crash inside a crash".
+// That's why we have to use a special native message box here which does not use yield :-)
+
+void FatalError(const OUString& sMessage)
+{
+ OUString sProductKey = ::utl::Bootstrap::getProductKey();
+ if ( sProductKey.isEmpty())
+ {
+ osl_getExecutableFile( &sProductKey.pData );
+
+ ::sal_uInt32 nLastIndex = sProductKey.lastIndexOf('/');
+ if ( nLastIndex > 0 )
+ sProductKey = sProductKey.copy( nLastIndex+1 );
+ }
+
+ OUString sTitle = sProductKey + " - Fatal Error";
+ Application::ShowNativeErrorBox (sTitle, sMessage);
+ std::cerr << sTitle << ": " << sMessage << std::endl;
+ _exit(EXITHELPER_FATAL_ERROR);
+}
+
+}
+
+CommandLineArgs& Desktop::GetCommandLineArgs()
+{
+ static CommandLineArgs theCommandLineArgs;
+ return theCommandLineArgs;
+}
+
+OUString ReplaceStringHookProc( const OUString& rStr )
+{
+ const static OUString sBuildId(utl::Bootstrap::getBuildIdData("development")),
+ sBrandName(utl::ConfigManager::getProductName()),
+ sVersion(utl::ConfigManager::getProductVersion()),
+ sAboutBoxVersion(utl::ConfigManager::getAboutBoxProductVersion()),
+ sAboutBoxVersionSuffix(utl::ConfigManager::getAboutBoxProductVersionSuffix()),
+ sExtension(utl::ConfigManager::getProductExtension());
+
+ OUString sRet(rStr);
+ if (sRet.indexOf("%PRODUCT") != -1 || sRet.indexOf("%ABOUTBOX") != -1)
+ {
+ sRet = sRet.replaceAll( "%PRODUCTNAME", sBrandName );
+ sRet = sRet.replaceAll( "%PRODUCTVERSION", sVersion );
+ sRet = sRet.replaceAll( "%BUILDID", sBuildId );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSIONSUFFIX", sAboutBoxVersionSuffix );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSION", sAboutBoxVersion );
+ sRet = sRet.replaceAll( "%PRODUCTEXTENSION", sExtension );
+ }
+
+ if ( sRet.indexOf( "%OOOVENDOR" ) != -1 )
+ {
+ const static OUString sOOOVendor = utl::ConfigManager::getVendor();
+ sRet = sRet.replaceAll( "%OOOVENDOR", sOOOVendor );
+ }
+
+ return sRet;
+}
+
+Desktop::Desktop()
+ : m_bCleanedExtensionCache(false)
+ , m_bServicesRegistered(false)
+ , m_aBootstrapError(BE_OK)
+ , m_aBootstrapStatus(BS_OK)
+ , m_firstRunTimer( "desktop::Desktop m_firstRunTimer" )
+{
+ m_firstRunTimer.SetTimeout(3000); // 3 sec.
+ m_firstRunTimer.SetInvokeHandler(LINK(this, Desktop, AsyncInitFirstRun));
+}
+
+Desktop::~Desktop()
+{
+}
+
+void Desktop::Init()
+{
+ SetBootstrapStatus(BS_OK);
+
+#if HAVE_FEATURE_EXTENSIONS
+ m_bCleanedExtensionCache = cleanExtensionCache();
+#endif
+
+ // We need to have service factory before going further, but see fdo#37195.
+ // Doing this will mmap common.rdb, making it not overwritable on windows,
+ // so this can't happen before the synchronization above. Lets rework this
+ // so that the above is called *from* CreateApplicationServiceManager or
+ // something to enforce this gotcha
+ try
+ {
+ InitApplicationServiceManager();
+ }
+ catch (css::uno::Exception & e)
+ {
+ HandleBootstrapErrors( BE_UNO_SERVICEMANAGER, e.Message );
+ std::abort();
+ }
+
+ // Check whether safe mode is enabled
+ const CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+ // Check if we are restarting from safe mode - in that case we don't want to enter it again
+ if (sfx2::SafeMode::hasRestartFlag())
+ sfx2::SafeMode::removeRestartFlag();
+ else if (rCmdLineArgs.IsSafeMode() || sfx2::SafeMode::hasFlag())
+ Application::EnableSafeMode();
+
+ // When we are in SafeMode we need to do changes before the configuration
+ // gets read (langselect::prepareLocale() by UNO API -> Components::Components)
+ // This may prepare SafeMode or restore from it by moving data in
+ // the UserConfiguration directory
+ comphelper::BackupFileHelper::reactOnSafeMode(Application::IsSafeModeEnabled());
+
+ try
+ {
+ if (!langselect::prepareLocale())
+ {
+ SetBootstrapError( BE_LANGUAGE_MISSING, OUString() );
+ }
+ }
+ catch (css::uno::Exception & e)
+ {
+ SetBootstrapError( BE_OFFICECONFIG_BROKEN, e.Message );
+ }
+
+ // test code for ProfileSafeMode to allow testing the fail
+ // of loading the office configuration initially. To use,
+ // either set to true and compile, or set a breakpoint
+ // in debugger and change the local bool
+ static bool bTryHardOfficeconfigBroken(false); // loplugin:constvars:ignore
+
+ if (bTryHardOfficeconfigBroken)
+ {
+ SetBootstrapError(BE_OFFICECONFIG_BROKEN, OUString());
+ }
+
+ // start ipc thread only for non-remote offices
+ RequestHandler::Status aStatus = RequestHandler::Enable(true);
+ if ( aStatus == RequestHandler::IPC_STATUS_PIPE_ERROR )
+ {
+#if defined(ANDROID) || defined(EMSCRIPTEN)
+ // Ignore crack pipe errors on Android
+#else
+ // Keep using this oddly named BE_PATHINFO_MISSING value
+ // for pipe-related errors on other platforms. Of course
+ // this crack with two (if not more) levels of our own
+ // error codes hiding the actual system error code is
+ // broken, but that is done all over the code, let's leave
+ // reengineering that to another year.
+ SetBootstrapError( BE_PATHINFO_MISSING, OUString() );
+#endif
+ }
+ else if ( aStatus == RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR )
+ {
+ SetBootstrapError( BE_PATHINFO_MISSING, OUString() );
+ }
+ else if ( aStatus == RequestHandler::IPC_STATUS_2ND_OFFICE )
+ {
+ // 2nd office startup should terminate after sending cmdlineargs through pipe
+ if (rCmdLineArgs.IsTextCat() || rCmdLineArgs.IsScriptCat())
+ {
+ HandleBootstrapErrors( BE_2NDOFFICE_WITHCAT, OUString() );
+ }
+ SetBootstrapStatus(BS_TERMINATE);
+ }
+ else if ( !rCmdLineArgs.GetUnknown().isEmpty()
+ || rCmdLineArgs.IsHelp() || rCmdLineArgs.IsVersion() )
+ {
+ // disable IPC thread in an instance that is just showing a help message
+ RequestHandler::Disable();
+ }
+ pSignalHandler = osl_addSignalHandler(SalMainPipeExchangeSignal_impl, nullptr);
+}
+
+void Desktop::InitFinished()
+{
+ CloseSplashScreen();
+}
+
+void Desktop::DeInit()
+{
+ try {
+ // instead of removing of the configManager just let it commit all the changes
+ utl::ConfigManager::storeConfigItems();
+ FlushConfiguration();
+
+ // close splashscreen if it's still open
+ CloseSplashScreen();
+ Reference< XComponent >(
+ comphelper::getProcessComponentContext(), UNO_QUERY_THROW )->
+ dispose();
+ // nobody should get a destroyed service factory...
+ ::comphelper::setProcessServiceFactory( nullptr );
+
+ // clear lockfile
+ m_xLockfile.reset();
+
+ RequestHandler::Disable();
+ if( pSignalHandler )
+ osl_removeSignalHandler( pSignalHandler );
+ } catch (const RuntimeException&) {
+ // someone threw an exception during shutdown
+ // this will leave some garbage behind...
+ TOOLS_WARN_EXCEPTION("desktop.app", "exception throwing during shutdown, will leave some garbage behind");
+ }
+}
+
+bool Desktop::QueryExit()
+{
+ try
+ {
+ utl::ConfigManager::storeConfigItems();
+ }
+ catch ( const RuntimeException& )
+ {
+ }
+
+ static constexpr OUString SUSPEND_QUICKSTARTVETO = u"SuspendQuickstartVeto"_ustr;
+
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XPropertySet > xPropertySet(xDesktop, UNO_QUERY_THROW);
+ xPropertySet->setPropertyValue( SUSPEND_QUICKSTARTVETO, Any(true) );
+
+ bool bExit = xDesktop->terminate();
+
+ if ( !bExit )
+ {
+ xPropertySet->setPropertyValue( SUSPEND_QUICKSTARTVETO, Any(false) );
+ }
+ else
+ {
+ FlushConfiguration();
+ try
+ {
+ // it is no problem to call RequestHandler::Disable() more than once
+ // it also looks to be threadsafe
+ RequestHandler::Disable();
+ }
+ catch ( const RuntimeException& )
+ {
+ }
+
+ m_xLockfile.reset();
+
+ }
+
+ return bExit;
+}
+
+void Desktop::Shutdown()
+{
+ framework::getDesktop(::comphelper::getProcessComponentContext())->shutdown();
+}
+
+void Desktop::HandleBootstrapPathErrors( ::utl::Bootstrap::Status aBootstrapStatus, std::u16string_view aDiagnosticMessage )
+{
+ if ( aBootstrapStatus == ::utl::Bootstrap::DATA_OK )
+ return;
+
+ OUString aProductKey;
+ OUString aTemp;
+
+ osl_getExecutableFile( &aProductKey.pData );
+ sal_uInt32 lastIndex = aProductKey.lastIndexOf('/');
+ if ( lastIndex > 0 )
+ aProductKey = aProductKey.copy( lastIndex+1 );
+
+ aTemp = ::utl::Bootstrap::getProductKey( aProductKey );
+ if ( !aTemp.isEmpty() )
+ aProductKey = aTemp;
+
+ OUString const aMessage(OUString::Concat(aDiagnosticMessage) + "\n");
+
+ std::unique_ptr<weld::MessageDialog> xBootstrapFailedBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok, aMessage));
+ xBootstrapFailedBox->set_title(aProductKey);
+ xBootstrapFailedBox->run();
+}
+
+// Create an error message depending on bootstrap failure code and an optional file url
+OUString Desktop::CreateErrorMsgString(
+ utl::Bootstrap::FailureCode nFailureCode,
+ const OUString& aFileURL )
+{
+ OUString aMsg;
+ bool bFileInfo = true;
+
+ switch ( nFailureCode )
+ {
+ /// the shared installation directory could not be located
+ case ::utl::Bootstrap::MISSING_INSTALL_DIRECTORY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_PATH_INVALID);
+ bFileInfo = false;
+ }
+ break;
+
+ /// the bootstrap INI file could not be found or read
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_MISSING);
+ }
+ break;
+
+ /// the bootstrap INI is missing a required entry
+ /// the bootstrap INI contains invalid data
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_CORRUPT);
+ }
+ break;
+
+ /// the version locator INI file could not be found or read
+ case ::utl::Bootstrap::MISSING_VERSION_FILE:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_MISSING);
+ }
+ break;
+
+ /// the version locator INI has no entry for this version
+ case ::utl::Bootstrap::MISSING_VERSION_FILE_ENTRY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_NO_SUPPORT);
+ }
+ break;
+
+ /// the user installation directory does not exist
+ case ::utl::Bootstrap::MISSING_USER_DIRECTORY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_DIR_MISSING);
+ }
+ break;
+
+ /// some bootstrap data was invalid in unexpected ways
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_DATA:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_INTERNAL);
+ bFileInfo = false;
+ }
+ break;
+
+ case ::utl::Bootstrap::INVALID_VERSION_FILE_ENTRY:
+ {
+ // This needs to be improved, see #i67575#:
+ aMsg = "Invalid version file entry";
+ bFileInfo = false;
+ }
+ break;
+
+ case ::utl::Bootstrap::NO_FAILURE:
+ {
+ OSL_ASSERT(false);
+ }
+ break;
+ }
+
+ if ( bFileInfo )
+ {
+ OUString aMsgString( aMsg );
+ OUString aFilePath;
+
+ osl::File::getSystemPathFromFileURL( aFileURL, aFilePath );
+
+ aMsgString = aMsgString.replaceFirst( "$1", aFilePath );
+ aMsg = aMsgString;
+ }
+
+ return MakeStartupErrorMessage( aMsg );
+}
+
+void Desktop::HandleBootstrapErrors(
+ BootstrapError aBootstrapError, OUString const & aErrorMessage )
+{
+ if ( aBootstrapError == BE_PATHINFO_MISSING )
+ {
+ OUString aErrorMsg;
+ OUString aBuffer;
+ utl::Bootstrap::Status aBootstrapStatus;
+ utl::Bootstrap::FailureCode nFailureCode;
+
+ aBootstrapStatus = ::utl::Bootstrap::checkBootstrapStatus( aBuffer, nFailureCode );
+ if ( aBootstrapStatus != ::utl::Bootstrap::DATA_OK )
+ {
+ switch ( nFailureCode )
+ {
+ case ::utl::Bootstrap::MISSING_INSTALL_DIRECTORY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_DATA:
+ {
+ aErrorMsg = CreateErrorMsgString( nFailureCode, OUString() );
+ }
+ break;
+
+ /// the bootstrap INI file could not be found or read
+ /// the bootstrap INI is missing a required entry
+ /// the bootstrap INI contains invalid data
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE:
+ {
+ OUString aBootstrapFileURL;
+
+ utl::Bootstrap::locateBootstrapFile( aBootstrapFileURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aBootstrapFileURL );
+ }
+ break;
+
+ /// the version locator INI file could not be found or read
+ /// the version locator INI has no entry for this version
+ /// the version locator INI entry is not a valid directory URL
+ case ::utl::Bootstrap::INVALID_VERSION_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_VERSION_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_VERSION_FILE:
+ {
+ OUString aVersionFileURL;
+
+ utl::Bootstrap::locateVersionFile( aVersionFileURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aVersionFileURL );
+ }
+ break;
+
+ /// the user installation directory does not exist
+ case ::utl::Bootstrap::MISSING_USER_DIRECTORY:
+ {
+ OUString aUserInstallationURL;
+
+ utl::Bootstrap::locateUserInstallation( aUserInstallationURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aUserInstallationURL );
+ }
+ break;
+
+ case ::utl::Bootstrap::NO_FAILURE:
+ {
+ OSL_ASSERT(false);
+ }
+ break;
+ }
+
+ HandleBootstrapPathErrors( aBootstrapStatus, aErrorMsg );
+ }
+ }
+ else if ( aBootstrapError == BE_UNO_SERVICEMANAGER || aBootstrapError == BE_UNO_SERVICE_CONFIG_MISSING )
+ {
+ // UNO service manager is not available. VCL needs a UNO service manager to display a message box!!!
+ // Currently we are not able to display a message box with a service manager due to this limitations inside VCL.
+
+ // When UNO is not properly initialized, all kinds of things can fail
+ // and cause the process to crash. To give the user a hint even if
+ // generating and displaying a message box below crashes, print a
+ // hard-coded message on stderr first:
+ std::cerr
+ << "The application cannot be started.\n"
+ // STR_BOOTSTRAP_ERR_CANNOT_START
+ << (aBootstrapError == BE_UNO_SERVICEMANAGER
+ ? "The component manager is not available.\n"
+ // STR_BOOTSTRAP_ERR_NO_SERVICE
+ : "The configuration service is not available.\n");
+ // STR_BOOTSTRAP_ERR_NO_CFG_SERVICE
+ if ( !aErrorMessage.isEmpty() )
+ {
+ std::cerr << "(\"" << aErrorMessage << "\")\n";
+ }
+
+ // First sentence. We cannot bootstrap office further!
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NO_CFG_SERVICE) + "\n";
+ if ( !aErrorMessage.isEmpty() )
+ {
+ aDiagnosticMessage += "(\"" + aErrorMessage + "\")\n";
+ }
+
+ // Due to the fact the we haven't a backup applicat.rdb file anymore it is not possible to
+ // repair the installation with the setup executable besides the office executable. Now
+ // we have to ask the user to start the setup on CD/installation directory manually!!
+ aDiagnosticMessage += DpResId(STR_ASK_START_SETUP_MANUALLY);
+
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_OFFICECONFIG_BROKEN )
+ {
+ // set flag at BackupFileHelper to be able to know if _exit was called and
+ // actions are executed after this. This method we are in will not return,
+ // but end up in a _exit() call
+ comphelper::BackupFileHelper::setExitWasCalled();
+
+ // enter safe mode, too
+ sfx2::SafeMode::putFlag();
+
+ OUString msg(DpResId(STR_CONFIG_ERR_ACCESS_GENERAL));
+ if (!aErrorMessage.isEmpty()) {
+ msg += "\n(\"" + aErrorMessage + "\")";
+ }
+ FatalError(MakeStartupErrorMessage(msg));
+ }
+ else if ( aBootstrapError == BE_USERINSTALL_FAILED )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_USERINSTALL_FAILED);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_LANGUAGE_MISSING )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_LANGUAGE_MISSING);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if (( aBootstrapError == BE_USERINSTALL_NOTENOUGHDISKSPACE ) ||
+ ( aBootstrapError == BE_USERINSTALL_NOWRITEACCESS ))
+ {
+ OUString aUserInstallationURL;
+ OUString aUserInstallationPath;
+ utl::Bootstrap::locateUserInstallation( aUserInstallationURL );
+ osl::File::getSystemPathFromFileURL( aUserInstallationURL, aUserInstallationPath );
+
+ OUString aDiagnosticMessage;
+ if ( aBootstrapError == BE_USERINSTALL_NOTENOUGHDISKSPACE )
+ aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NOTENOUGHDISKSPACE);
+ else
+ aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NOACCESSRIGHTS);
+ aDiagnosticMessage += aUserInstallationPath;
+
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_2NDOFFICE_WITHCAT )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_2NDOFFICE_WITHCAT);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+}
+
+
+namespace {
+
+
+#if HAVE_FEATURE_BREAKPAD
+void handleCrashReport()
+{
+ static constexpr OUStringLiteral SERVICENAME_CRASHREPORT = u"com.sun.star.comp.svx.CrashReportUI";
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XSynchronousDispatch > xRecoveryUI(
+ xContext->getServiceManager()->createInstanceWithContext(SERVICENAME_CRASHREPORT, xContext),
+ css::uno::UNO_QUERY_THROW);
+
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(::comphelper::getProcessComponentContext());
+
+ css::util::URL aURL;
+ css::uno::Any aRet = xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+}
+#endif
+
+#if !defined ANDROID
+void handleSafeMode()
+{
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XSynchronousDispatch > xSafeModeUI(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.svx.SafeModeUI", xContext),
+ css::uno::UNO_QUERY_THROW);
+
+ css::util::URL aURL;
+ css::uno::Any aRet = xSafeModeUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+}
+#endif
+
+/** @short check if recovery must be started or not.
+
+ @param bCrashed [boolean ... out!]
+ the office crashed last times.
+ But may be there are no recovery data.
+ Useful to trigger the error report tool without
+ showing the recovery UI.
+
+ @param bRecoveryDataExists [boolean ... out!]
+ there exists some recovery data.
+
+ @param bSessionDataExists [boolean ... out!]
+ there exists some session data.
+ Because the user may be logged out last time from its
+ unix session...
+*/
+void impl_checkRecoveryState(bool& bCrashed ,
+ bool& bRecoveryDataExists,
+ bool& bSessionDataExists )
+{
+ bCrashed = officecfg::Office::Recovery::RecoveryInfo::Crashed::get()
+#if HAVE_FEATURE_BREAKPAD
+ || CrashReporter::crashReportInfoExists();
+#else
+ ;
+#endif
+ bool elements = officecfg::Office::Recovery::RecoveryList::get()->
+ hasElements();
+ bool session
+ = officecfg::Office::Recovery::RecoveryInfo::SessionData::get();
+ bRecoveryDataExists = elements && !session;
+ bSessionDataExists = elements && session;
+}
+
+Reference< css::frame::XSynchronousDispatch > g_xRecoveryUI;
+
+template <class Ref>
+struct RefClearGuard
+{
+ Ref& m_Ref;
+ RefClearGuard(Ref& ref) : m_Ref(ref) {}
+ ~RefClearGuard() { m_Ref.clear(); }
+};
+
+/* @short start the recovery wizard.
+
+ @param bEmergencySave
+ differs between EMERGENCY_SAVE and RECOVERY
+*/
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+bool impl_callRecoveryUI(bool bEmergencySave ,
+ bool bExistsRecoveryData)
+{
+ constexpr OUStringLiteral COMMAND_EMERGENCYSAVE = u"vnd.sun.star.autorecovery:/doEmergencySave";
+ constexpr OUStringLiteral COMMAND_RECOVERY = u"vnd.sun.star.autorecovery:/doAutoRecovery";
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ g_xRecoveryUI.set(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.svx.RecoveryUI", xContext),
+ css::uno::UNO_QUERY_THROW);
+ RefClearGuard<Reference< css::frame::XSynchronousDispatch >> refClearGuard(g_xRecoveryUI);
+
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(xContext);
+
+ css::util::URL aURL;
+ if (bEmergencySave)
+ aURL.Complete = COMMAND_EMERGENCYSAVE;
+ else if (bExistsRecoveryData)
+ aURL.Complete = COMMAND_RECOVERY;
+ else
+ return false;
+
+ xURLParser->parseStrict(aURL);
+
+ css::uno::Any aRet = g_xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+ return bRet;
+}
+#endif
+
+bool impl_bringToFrontRecoveryUI()
+{
+ Reference< css::frame::XSynchronousDispatch > xRecoveryUI(g_xRecoveryUI);
+ if (!xRecoveryUI.is())
+ return false;
+
+ css::util::URL aURL;
+ aURL.Complete = "vnd.sun.star.autorecovery:/doBringToFront";
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(::comphelper::getProcessComponentContext());
+ xURLParser->parseStrict(aURL);
+
+ css::uno::Any aRet = xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+ return bRet;
+}
+
+}
+
+namespace {
+
+void restartOnMac(bool passArguments) {
+#if defined MACOSX
+ RequestHandler::Disable();
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ (void) passArguments; // avoid warnings
+ OUString aMessage = DpResId(STR_LO_MUST_BE_RESTARTED);
+
+ std::unique_ptr<weld::MessageDialog> xRestartBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok, aMessage));
+ xRestartBox->run();
+#else
+ OUString execUrl;
+ OSL_VERIFY(osl_getExecutableFile(&execUrl.pData) == osl_Process_E_None);
+ OUString execPath;
+ OString execPath8;
+ if ((osl::FileBase::getSystemPathFromFileURL(execUrl, execPath)
+ != osl::FileBase::E_None) ||
+ !execPath.convertToString(
+ &execPath8, osl_getThreadTextEncoding(),
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ std::abort();
+ }
+ std::vector< OString > args { execPath8 };
+ bool wait = false;
+ if (passArguments) {
+ sal_uInt32 n = osl_getCommandArgCount();
+ for (sal_uInt32 i = 0; i < n; ++i) {
+ OUString arg;
+ osl_getCommandArg(i, &arg.pData);
+ if (arg.match("--accept=")) {
+ wait = true;
+ }
+ OString arg8;
+ if (!arg.convertToString(
+ &arg8, osl_getThreadTextEncoding(),
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ std::abort();
+ }
+ args.push_back(arg8);
+ }
+ }
+ std::vector< char const * > argPtrs;
+ for (auto const& elem : args)
+ {
+ argPtrs.push_back(elem.getStr());
+ }
+ argPtrs.push_back(nullptr);
+ execv(execPath8.getStr(), const_cast< char ** >(argPtrs.data()));
+ if (errno == ENOTSUP) { // happens when multithreaded on macOS < 10.6
+ pid_t pid = fork();
+ if (pid == 0) {
+ execv(execPath8.getStr(), const_cast< char ** >(argPtrs.data()));
+ } else if (pid > 0) {
+ // Two simultaneously running soffice processes lead to two dock
+ // icons, so avoid waiting here unless it must be assumed that the
+ // process invoking soffice itself wants to wait for soffice to
+ // finish:
+ if (!wait) {
+ return;
+ }
+ int stat;
+ if (waitpid(pid, &stat, 0) == pid && WIFEXITED(stat)) {
+ _exit(WEXITSTATUS(stat));
+ }
+ }
+ }
+ std::abort();
+#endif
+#else
+ (void) passArguments; // avoid warnings
+#endif
+}
+
+#if HAVE_FEATURE_UPDATE_MAR
+bool isTimeForUpdateCheck()
+{
+ sal_uInt64 nLastUpdate = officecfg::Office::Update::Update::LastUpdateTime::get();
+ sal_uInt64 nNow = tools::Time::GetSystemTicks();
+
+ sal_uInt64 n7DayInMS = 1000 * 60 * 60 * 24 * 7; // 7 days in ms
+ if (nNow - n7DayInMS >= nLastUpdate)
+ return true;
+
+ return false;
+}
+#endif
+
+}
+
+void Desktop::Exception(ExceptionCategory nCategory)
+{
+ // protect against recursive calls
+ static bool bInException = false;
+
+#if HAVE_FEATURE_BREAKPAD
+ CrashReporter::removeExceptionHandler(); // disallow re-entry
+#endif
+
+ SystemWindowFlags nOldMode = Application::GetSystemWindowMode();
+ Application::SetSystemWindowMode( nOldMode & ~SystemWindowFlags::NOAUTOMODE );
+ if ( bInException )
+ {
+ Application::Abort( OUString() );
+ }
+
+ bInException = true;
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+
+ // save all modified documents ... if it's allowed doing so.
+ bool bRestart = false;
+ bool bAllowRecoveryAndSessionManagement = (
+ ( !rArgs.IsNoRestore() ) && // some use cases of office must work without recovery
+ ( !rArgs.IsHeadless() ) &&
+ ( nCategory != ExceptionCategory::UserInterface ) && // recovery can't work without UI ... but UI layer seems to be the reason for this crash
+ ( Application::IsInExecute() ) // crashes during startup and shutdown should be ignored (they indicate a corrupted installation...)
+ );
+ if ( bAllowRecoveryAndSessionManagement )
+ {
+ // Save all open documents so they will be reopened
+ // the next time the application is started
+ // returns true if at least one document could be saved...
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+ bRestart = impl_callRecoveryUI(
+ true , // force emergency save
+ false);
+#endif
+ }
+
+ FlushConfiguration();
+
+ m_xLockfile.reset();
+
+ if( bRestart )
+ {
+ RequestHandler::Disable();
+ if( pSignalHandler )
+ osl_removeSignalHandler( pSignalHandler );
+
+ restartOnMac(false);
+#if !ENABLE_WASM_STRIP_SPLASH
+ if ( m_rSplashScreen.is() )
+ m_rSplashScreen->reset();
+#endif
+
+ _exit( EXITHELPER_CRASH_WITH_RESTART );
+ }
+ else
+ {
+ Application::Abort( OUString() );
+ }
+
+ OSL_ASSERT(false); // unreachable
+}
+
+void Desktop::AppEvent( const ApplicationEvent& rAppEvent )
+{
+ HandleAppEvent( rAppEvent );
+}
+
+namespace {
+
+class JVMloadThread : public salhelper::Thread {
+public:
+ JVMloadThread() : salhelper::Thread("Preload JVM thread")
+ {
+ }
+
+private:
+ virtual void execute() override final
+ {
+ Reference< XMultiServiceFactory > xSMgr = comphelper::getProcessServiceFactory();
+
+ Reference< css::loader::XImplementationLoader > xJavaComponentLoader(
+ xSMgr->createInstance("com.sun.star.comp.stoc.JavaComponentLoader"),
+ css::uno::UNO_QUERY_THROW);
+
+ if (xJavaComponentLoader.is())
+ {
+ const css::uno::Reference< ::com::sun::star::registry::XRegistryKey > xRegistryKey;
+ try
+ {
+ xJavaComponentLoader->activate("", "", "", xRegistryKey);
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.app", "Cannot activate factory during JVM preloading");
+ }
+ }
+ }
+};
+
+struct ExecuteGlobals
+{
+ Reference < css::document::XDocumentEventListener > xGlobalBroadcaster;
+ bool bRestartRequested;
+ std::unique_ptr<SvtCTLOptions> pCTLLanguageOptions;
+ std::unique_ptr<SvtPathOptions> pPathOptions;
+ rtl::Reference< JVMloadThread > xJVMloadThread;
+
+ ExecuteGlobals()
+ : bRestartRequested( false )
+ {}
+};
+
+}
+
+static ExecuteGlobals* pExecGlobals = nullptr;
+
+int Desktop::Main()
+{
+ pExecGlobals = new ExecuteGlobals();
+
+ // Remember current context object
+ css::uno::ContextLayer layer( css::uno::getCurrentContext() );
+
+ if ( m_aBootstrapError != BE_OK )
+ {
+ HandleBootstrapErrors( m_aBootstrapError, m_aBootstrapErrorMessage );
+ return EXIT_FAILURE;
+ }
+
+ BootstrapStatus eStatus = GetBootstrapStatus();
+ if (eStatus == BS_TERMINATE) {
+ return EXIT_SUCCESS;
+ }
+
+ // Detect desktop environment - need to do this as early as possible
+ css::uno::setCurrentContext( new DesktopContext( css::uno::getCurrentContext() ) );
+
+ if (officecfg::Office::Common::Misc::PreloadJVM::get() && pExecGlobals)
+ {
+ SAL_INFO("desktop.app", "Preload JVM");
+
+ // pre-load JVM
+ pExecGlobals->xJVMloadThread = new JVMloadThread();
+ pExecGlobals->xJVMloadThread->launch();
+ }
+
+ CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+
+ Translate::SetReadStringHook(ReplaceStringHookProc);
+
+ // Startup screen
+#if !ENABLE_WASM_STRIP_SPLASH
+ OpenSplashScreen();
+#endif
+
+ SetSplashScreenProgress(10);
+
+ userinstall::Status inst_fin = userinstall::finalize();
+ if (inst_fin != userinstall::EXISTED && inst_fin != userinstall::CREATED)
+ {
+ SAL_WARN( "desktop.app", "userinstall failed: " << inst_fin);
+ if ( inst_fin == userinstall::ERROR_NO_SPACE )
+ HandleBootstrapErrors(
+ BE_USERINSTALL_NOTENOUGHDISKSPACE, OUString() );
+ else if ( inst_fin == userinstall::ERROR_CANT_WRITE )
+ HandleBootstrapErrors( BE_USERINSTALL_NOWRITEACCESS, OUString() );
+ else
+ HandleBootstrapErrors( BE_USERINSTALL_FAILED, OUString() );
+ return EXIT_FAILURE;
+ }
+ // refresh path information
+ utl::Bootstrap::reloadData();
+ SetSplashScreenProgress(20);
+
+ Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< XRestartManager > xRestartManager( OfficeRestartManager::get(xContext) );
+
+ Reference< XDesktop2 > xDesktop;
+
+ RegisterServices();
+
+ SetSplashScreenProgress(25);
+
+#if HAVE_FEATURE_DESKTOP && !defined(EMSCRIPTEN)
+ // check user installation directory for lockfile so we can be sure
+ // there is no other instance using our data files from a remote host
+
+ bool bMustLockProfile = ( getenv( "SAL_NOLOCK_PROFILE" ) == nullptr );
+ if ( bMustLockProfile )
+ {
+ m_xLockfile.reset(new Lockfile);
+
+ if ( !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsInvisible() &&
+ !rCmdLineArgs.IsNoLockcheck() && !m_xLockfile->check( Lockfile_execWarning ))
+ {
+ // Lockfile exists, and user clicked 'no'
+ return EXIT_FAILURE;
+ }
+ }
+
+ // check if accessibility is enabled but not working and allow to quit
+ if( Application::GetSettings().GetMiscSettings().GetEnableATToolSupport() )
+ {
+ if( !InitAccessBridge() )
+ return EXIT_FAILURE;
+ }
+#endif
+
+ // terminate if requested...
+ if( rCmdLineArgs.IsTerminateAfterInit() )
+ return EXIT_SUCCESS;
+
+ // Read the common configuration items for optimization purpose
+ if ( !InitializeConfiguration() )
+ return EXIT_FAILURE;
+
+ SetSplashScreenProgress(30);
+
+ // create title string
+ OUString aTitle(ReplaceStringHookProc(RID_APPTITLE));
+#ifdef DBG_UTIL
+ //include buildid in non product builds
+ aTitle += " [" + utl::Bootstrap::getBuildIdData("development") + "]";
+#endif
+
+ SetDisplayName( aTitle );
+ SetSplashScreenProgress(35);
+ pExecGlobals->pPathOptions.reset( new SvtPathOptions);
+ SetSplashScreenProgress(40);
+
+ xDesktop = css::frame::Desktop::create( xContext );
+
+#if HAVE_FEATURE_UPDATE_MAR
+ const char* pUpdaterTestEnable = std::getenv("LIBO_UPDATER_TEST_ENABLE");
+ if (pUpdaterTestEnable || officecfg::Office::Update::Update::Enabled::get())
+ {
+ // check if we just updated
+ const char* pUpdaterRunning = std::getenv("LIBO_UPDATER_TEST_RUNNING");
+ bool bUpdateRunning = officecfg::Office::Update::Update::UpdateRunning::get() || pUpdaterRunning;
+ if (bUpdateRunning)
+ {
+ OUString aSeeAlso = officecfg::Office::Update::Update::SeeAlso::get();
+ OUString aOldBuildID = officecfg::Office::Update::Update::OldBuildID::get();
+
+ OUString aBuildID = Updater::getBuildID();
+ if (aOldBuildID == aBuildID)
+ {
+ Updater::log("Old and new Build ID are the same. No Updating took place.");
+ }
+ else
+ {
+ if (!aSeeAlso.isEmpty())
+ {
+ SAL_INFO("desktop.updater", "See also: " << aSeeAlso);
+ Reference< css::system::XSystemShellExecute > xSystemShell(
+ SystemShellExecute::create(::comphelper::getProcessComponentContext()) );
+
+ xSystemShell->execute( aSeeAlso, OUString(), SystemShellExecuteFlags::URIS_ONLY );
+ }
+ }
+
+ // reset all the configuration values,
+ // all values need to be read before this code
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::UpdateRunning::set(false, batch);
+ officecfg::Office::Update::Update::SeeAlso::set(OUString(), batch);
+ officecfg::Office::Update::Update::OldBuildID::set(OUString(), batch);
+ batch->commit();
+
+ Updater::removeUpdateFiles();
+ }
+
+ osl::DirectoryItem aUpdateFile;
+ osl::DirectoryItem::get(Updater::getUpdateFileURL(), aUpdateFile);
+
+ const char* pUpdaterTestUpdate = std::getenv("LIBO_UPDATER_TEST_UPDATE");
+ const char* pForcedUpdateCheck = std::getenv("LIBO_UPDATER_TEST_UPDATE_CHECK");
+ if (pUpdaterTestUpdate || aUpdateFile.is())
+ {
+ OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(aBuildID);
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::OldBuildID::set(aBuildID, batch);
+ officecfg::Office::Update::Update::UpdateRunning::set(true, batch);
+ batch->commit();
+
+ // make sure the change is written to the configuration before we start the update
+ css::uno::Reference<css::util::XFlushable> xFlushable(css::configuration::theDefaultProvider::get(xContext), UNO_QUERY);
+ xFlushable->flush();
+ // avoid the old oosplash staying around
+ CloseSplashScreen();
+ bool bSuccess = update();
+ if (bSuccess)
+ {
+ xDesktop->terminate();
+ return EXIT_SUCCESS;
+ }
+ }
+ else if (isTimeForUpdateCheck() || pForcedUpdateCheck)
+ {
+ sal_uInt64 nNow = tools::Time::GetSystemTicks();
+ Updater::log("Update Check Time: " + OUString::number(nNow));
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::LastUpdateTime::set(nNow, batch);
+ batch->commit();
+ m_aUpdateThread = std::thread(update_checker);
+ }
+ }
+#endif
+
+ // create service for loading SFX (still needed in startup)
+ pExecGlobals->xGlobalBroadcaster = Reference < css::document::XDocumentEventListener >
+ ( css::frame::theGlobalEventBroadcaster::get(xContext), UNO_SET_THROW );
+
+ /* ensure existence of a default window that messages can be dispatched to
+ This is for the benefit of testtool which uses PostUserEvent extensively
+ and else can deadlock while creating this window from another thread while
+ the main thread is not yet in the event loop.
+ */
+ Application::GetDefaultDevice();
+
+#if HAVE_FEATURE_EXTENSIONS
+ // Check if bundled or shared extensions were added /removed
+ // and process those extensions (has to be done before checking
+ // the extension dependencies!
+ SynchronizeExtensionRepositories(m_bCleanedExtensionCache, this);
+ bool bAbort = CheckExtensionDependencies();
+ if ( bAbort )
+ return EXIT_FAILURE;
+
+ if (inst_fin == userinstall::CREATED)
+ {
+ Migration::migrateSettingsIfNecessary();
+ }
+#endif
+
+ // keep a language options instance...
+ pExecGlobals->pCTLLanguageOptions.reset( new SvtCTLOptions(true));
+
+ css::document::DocumentEvent aEvent;
+ aEvent.EventName = "OnStartApp";
+ pExecGlobals->xGlobalBroadcaster->documentEventOccured(aEvent);
+
+ SetSplashScreenProgress(50);
+
+ // Backing Component
+ bool bCrashed = false;
+ bool bExistsRecoveryData = false;
+ bool bExistsSessionData = false;
+
+ impl_checkRecoveryState(bCrashed, bExistsRecoveryData, bExistsSessionData);
+
+ OUString pidfileName = rCmdLineArgs.GetPidfileName();
+ if ( !pidfileName.isEmpty() )
+ {
+ OUString pidfileURL;
+
+ if ( osl_getFileURLFromSystemPath(pidfileName.pData, &pidfileURL.pData) == osl_File_E_None )
+ {
+ osl::File pidfile( pidfileURL );
+ osl::FileBase::RC rc;
+
+ osl::File::remove( pidfileURL );
+ if ( (rc = pidfile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) ) == osl::File::E_None )
+ {
+ OString pid( OString::number( GETPID() ) );
+ sal_uInt64 written = 0;
+ if ( pidfile.write(pid.getStr(), pid.getLength(), written) != osl::File::E_None )
+ {
+ SAL_WARN("desktop.app", "cannot write pidfile " << pidfile.getURL());
+ }
+ pidfile.close();
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "cannot open pidfile " << pidfile.getURL() << rc);
+ }
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "cannot get pidfile URL from path" << pidfileName);
+ }
+ }
+
+ pExecGlobals->bRestartRequested = xRestartManager->isRestartRequested(true);
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ if ((!rCmdLineArgs.WantsToLoadDocument() && !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsQuickstart()) &&
+ (SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE)) &&
+ (!bExistsRecoveryData ) &&
+ (!bExistsSessionData ) &&
+ (!Application::AnyInput( VclInputFlags::APPEVENT ) ))
+ {
+ ShowBackingComponent(this);
+ }
+ }
+
+ SetSplashScreenProgress(55);
+
+ svtools::ApplyFontSubstitutionsToVcl();
+
+ SvtTabAppearanceCfg::SetInitialized();
+ SvtTabAppearanceCfg::SetApplicationDefaults( this );
+ SvtAccessibilityOptions::SetVCLSettings();
+ SetSplashScreenProgress(60);
+
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ Application::SetFilterHdl( LINK( this, Desktop, ImplInitFilterHdl ) );
+
+ // Preload function depends on an initialized sfx application!
+ SetSplashScreenProgress(75);
+
+ // use system window dialogs
+ Application::SetSystemWindowMode( SystemWindowFlags::DIALOG );
+
+ SetSplashScreenProgress(80);
+
+ if ( !rCmdLineArgs.IsInvisible() &&
+ !rCmdLineArgs.IsNoQuickstart() )
+ InitializeQuickstartMode( xContext );
+
+ if ( xDesktop.is() )
+ xDesktop->addTerminateListener( new RequestHandlerController );
+ SetSplashScreenProgress(100);
+
+ // FIXME: move this somewhere sensible.
+#if HAVE_FEATURE_OPENCL
+ CheckOpenCLCompute(xDesktop);
+#endif
+
+#if !defined(EMSCRIPTEN)
+ //Running the VCL graphics rendering tests
+ const char * pDisplay = std::getenv("DISPLAY");
+ if (!pDisplay || pDisplay[0] == ':')
+ {
+ runGraphicsRenderTests();
+ }
+#endif
+
+ // Post user event to startup first application component window
+ // We have to send this OpenClients message short before execute() to
+ // minimize the risk that this message overtakes type detection construction!!
+ Application::PostUserEvent( LINK( this, Desktop, OpenClients_Impl ) );
+
+ // Post event to enable acceptors
+ Application::PostUserEvent( LINK( this, Desktop, EnableAcceptors_Impl) );
+
+ // call Application::Execute to process messages in vcl message loop
+#if HAVE_FEATURE_JAVA
+ // The JavaContext contains an interaction handler which is used when
+ // the creation of a Java Virtual Machine fails
+ css::uno::ContextLayer layer2(
+ new svt::JavaContext( css::uno::getCurrentContext() ) );
+#endif
+ // check whether the shutdown is caused by restart just before entering the Execute
+ pExecGlobals->bRestartRequested = pExecGlobals->bRestartRequested ||
+ xRestartManager->isRestartRequested(true);
+
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ // if this run of the office is triggered by restart, some additional actions should be done
+ DoRestartActionsIfNecessary( !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsNoQuickstart() );
+
+ Execute();
+ }
+ }
+ else
+ {
+ if (xDesktop.is())
+ xDesktop->terminate();
+ }
+ // CAUTION: you do not necessarily get here e.g. on the Mac.
+ // please put all deinitialization code into doShutdown
+ return doShutdown();
+}
+
+int Desktop::doShutdown()
+{
+ if( ! pExecGlobals )
+ return EXIT_SUCCESS;
+
+ if (m_aUpdateThread.joinable())
+ m_aUpdateThread.join();
+
+ if (pExecGlobals->xJVMloadThread.is())
+ {
+ pExecGlobals->xJVMloadThread->join();
+ pExecGlobals->xJVMloadThread.clear();
+ }
+
+ pExecGlobals->bRestartRequested = pExecGlobals->bRestartRequested ||
+ OfficeRestartManager::get(comphelper::getProcessComponentContext())->
+ isRestartRequested(true);
+ if ( pExecGlobals->bRestartRequested )
+ SetRestartState();
+
+ const CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+ OUString pidfileName = rCmdLineArgs.GetPidfileName();
+ if ( !pidfileName.isEmpty() )
+ {
+ OUString pidfileURL;
+
+ if ( osl_getFileURLFromSystemPath(pidfileName.pData, &pidfileURL.pData) == osl_File_E_None )
+ {
+ if ( osl::File::remove( pidfileURL ) != osl::FileBase::E_None )
+ {
+ SAL_WARN("desktop.app", "shutdown: cannot remove pidfile " << pidfileURL);
+ }
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "shutdown: cannot get pidfile URL from path" << pidfileName);
+ }
+ }
+
+ // remove temp directory
+ RemoveTemporaryDirectory();
+ flatpak::removeTemporaryHtmlDirectory();
+
+ // flush evtl. configuration changes so that all config files in user
+ // dir are written
+ FlushConfiguration();
+
+ if (pExecGlobals->bRestartRequested)
+ {
+ // tdf#128523
+ RemoveIconCacheDirectory();
+
+ // a restart is already requested, usually due to a configuration change
+ // that needs a restart to get active. If this is the case, do not try
+ // to use SecureUserConfig to safe this still untested new configuration
+ }
+ else
+ {
+ // Test if SecureUserConfig is active. If yes and we are at this point, regular shutdown
+ // is in progress and the currently used configuration was working. Try to secure this
+ // working configuration for later eventually necessary restores
+ comphelper::BackupFileHelper aBackupFileHelper;
+
+ aBackupFileHelper.tryPush();
+ aBackupFileHelper.tryPushExtensionInfo();
+ }
+
+ // The acceptors in the AcceptorMap must be released (in DeregisterServices)
+ // with the solar mutex unlocked, to avoid deadlock:
+ {
+ SolarMutexReleaser aReleaser;
+ DeregisterServices();
+#if HAVE_FEATURE_SCRIPTING
+ StarBASIC::DetachAllDocBasicItems();
+#endif
+ }
+
+ // be sure that path/language options gets destroyed before
+ // UCB is deinitialized
+ pExecGlobals->pCTLLanguageOptions.reset();
+ pExecGlobals->pPathOptions.reset();
+
+ comphelper::ThreadPool::getSharedOptimalPool().shutdown();
+
+ bool bRR = pExecGlobals->bRestartRequested;
+ delete pExecGlobals;
+ pExecGlobals = nullptr;
+
+ if ( bRR )
+ {
+ restartOnMac(true);
+#if !ENABLE_WASM_STRIP_SPLASH
+ if ( m_rSplashScreen.is() )
+ m_rSplashScreen->reset();
+#endif
+
+ return EXITHELPER_NORMAL_RESTART;
+ }
+ return EXIT_SUCCESS;
+}
+
+IMPL_STATIC_LINK( Desktop, ImplInitFilterHdl, ::ConvertData&, rData, bool )
+{
+ return GraphicFilter::GetGraphicFilter().GetFilterCallback().Call( rData );
+}
+
+bool Desktop::InitializeConfiguration()
+{
+ try
+ {
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() );
+ return true;
+ }
+ catch( css::lang::ServiceNotRegisteredException & e )
+ {
+ HandleBootstrapErrors(
+ Desktop::BE_UNO_SERVICE_CONFIG_MISSING, e.Message );
+ }
+ catch( const css::configuration::MissingBootstrapFileException& e )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::MISSING_BOOTSTRAP_FILE,
+ e.BootstrapFileURL ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_USER_INSTALL, aMsg );
+ }
+ catch( const css::configuration::InvalidBootstrapFileException& e )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY,
+ e.BootstrapFileURL ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ catch( const css::configuration::InstallationIncompleteException& )
+ {
+ OUString aVersionFileURL;
+ OUString aMsg;
+ utl::Bootstrap::PathStatus aPathStatus = utl::Bootstrap::locateVersionFile( aVersionFileURL );
+ if ( aPathStatus == utl::Bootstrap::PATH_EXISTS )
+ aMsg = CreateErrorMsgString( utl::Bootstrap::MISSING_VERSION_FILE_ENTRY, aVersionFileURL );
+ else
+ aMsg = CreateErrorMsgString( utl::Bootstrap::MISSING_VERSION_FILE, aVersionFileURL );
+
+ HandleBootstrapPathErrors( ::utl::Bootstrap::MISSING_USER_INSTALL, aMsg );
+ }
+ catch ( const css::configuration::backend::BackendAccessException& exception)
+ {
+ // [cm122549] It is assumed in this case that the message
+ // coming from InitConfiguration (in fact CreateApplicationConf...)
+ // is suitable for display directly.
+ FatalError( MakeStartupErrorMessage( exception.Message ) );
+ }
+ catch ( const css::configuration::backend::BackendSetupException& exception)
+ {
+ // [cm122549] It is assumed in this case that the message
+ // coming from InitConfiguration (in fact CreateApplicationConf...)
+ // is suitable for display directly.
+ FatalError( MakeStartupErrorMessage( exception.Message ) );
+ }
+ catch ( const css::configuration::CannotLoadConfigurationException& )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_DATA,
+ OUString() ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ catch( const css::uno::Exception& )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_DATA,
+ OUString() ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ return false;
+}
+
+void Desktop::FlushConfiguration()
+{
+ css::uno::Reference< css::util::XFlushable >(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->flush();
+}
+
+bool Desktop::InitializeQuickstartMode( const Reference< XComponentContext >& rxContext )
+{
+ try
+ {
+ // the shutdown icon sits in the systray and allows the user to keep
+ // the office instance running for quicker restart
+ // this will only be activated if --quickstart was specified on cmdline
+
+ bool bQuickstart = shouldLaunchQuickstart();
+
+ // Try to instantiate quickstart service. This service is not mandatory, so
+ // do nothing if service is not available
+
+ // #i105753# the following if was invented for performance
+ // unfortunately this broke the Mac behavior which is to always run
+ // in quickstart mode since Mac applications do not usually quit
+ // when the last document closes.
+ // Note that this claim that on macOS we "always run in quickstart mode"
+ // has nothing to do with (quick) *starting* (i.e. starting automatically
+ // when the user logs in), though, but with not quitting when no documents
+ // are open.
+ #ifndef MACOSX
+ if ( bQuickstart )
+ #endif
+ {
+ css::office::Quickstart::createStart(rxContext, bQuickstart);
+ }
+ return true;
+ }
+ catch( const css::uno::Exception& )
+ {
+ return false;
+ }
+}
+
+void Desktop::OverrideSystemSettings( AllSettings& rSettings )
+{
+ if ( !SvtTabAppearanceCfg::IsInitialized () )
+ return;
+
+ StyleSettings hStyleSettings = rSettings.GetStyleSettings();
+ MouseSettings hMouseSettings = rSettings.GetMouseSettings();
+
+ DragFullOptions nDragFullOptions = hStyleSettings.GetDragFullOptions();
+
+ sal_uInt16 nDragMode = officecfg::Office::Common::View::Window::Drag::get();
+ switch ( nDragMode )
+ {
+ case 0: //FullWindow:
+ nDragFullOptions |= DragFullOptions::All;
+ break;
+ case 1: // Frame:
+ nDragFullOptions &= ~DragFullOptions::All;
+ break;
+ case 2: // SystemDep
+ default:
+ break;
+ }
+
+ MouseFollowFlags nFollow = hMouseSettings.GetFollow();
+ bool bMenuFollowMouse = officecfg::Office::Common::View::Menu::FollowMouse::get();
+ hMouseSettings.SetFollow( bMenuFollowMouse ? (nFollow|MouseFollowFlags::Menu) : (nFollow&~MouseFollowFlags::Menu));
+ rSettings.SetMouseSettings(hMouseSettings);
+
+ bool bMenuIcons = officecfg::Office::Common::View::Menu::ShowIconsInMenues::get();
+ bool bSystemMenuIcons = officecfg::Office::Common::View::Menu::IsSystemIconsInMenus::get();
+ TriState eMenuIcons = bSystemMenuIcons ? TRISTATE_INDET : static_cast<TriState>(bMenuIcons);
+ hStyleSettings.SetUseImagesInMenus(eMenuIcons);
+ hStyleSettings.SetContextMenuShortcuts(static_cast<TriState>(officecfg::Office::Common::View::Menu::ShortcutsInContextMenus::get()));
+ hStyleSettings.SetDragFullOptions( nDragFullOptions );
+ rSettings.SetStyleSettings ( hStyleSettings );
+}
+
+namespace {
+
+class ExitTimer : public Timer
+{
+ public:
+ ExitTimer() : Timer("desktop ExitTimer")
+ {
+ SetTimeout(500);
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ _exit(42);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(Desktop, OpenClients_Impl, void*, void)
+{
+ // #i114963#
+ // Enable IPC thread before OpenClients
+ //
+ // This is because it is possible for another client to connect during the OpenClients() call.
+ // This can happen on Windows when document is printed (not opened) and another client wants to print (when printing multiple documents).
+ // If the IPC thread is enabled after OpenClients, then the client will not be processed because the application will exit after printing. i.e RequestHandler::AreRequestsPending() will always return false
+ //
+ // ALSO:
+ //
+ // Multiple clients may request simultaneous connections.
+ // When this server closes down it attempts to recreate the pipe (in RequestHandler::Disable()).
+ // It's possible that the client has a pending connection request.
+ // When the IPC thread is not running, this connection locks (because maPipe.accept()) is never called
+ RequestHandler::SetReady(true);
+ OpenClients();
+
+ CloseSplashScreen();
+ CheckFirstRun( );
+#ifdef _WIN32
+ bool bDontShowDialogs
+ = Application::IsHeadlessModeEnabled(); // uitest.uicheck fails when the dialog is open
+ for (sal_uInt16 i = 0; !bDontShowDialogs && i < Application::GetCommandLineParamCount(); i++)
+ {
+ if (Application::GetCommandLineParam(i) == "--nologo")
+ bDontShowDialogs = true;
+ }
+ if (!bDontShowDialogs)
+ vcl::fileregistration::CheckFileExtRegistration(SfxGetpApp()->GetTopWindow());
+ // Registers a COM class factory of the service manager with the windows operating system.
+ Reference< XMultiServiceFactory > xSMgr= comphelper::getProcessServiceFactory();
+ xSMgr->createInstance("com.sun.star.bridge.OleApplicationRegistration");
+ xSMgr->createInstance("com.sun.star.comp.ole.EmbedServer");
+#endif
+ const char *pExitPostStartup = getenv ("OOO_EXIT_POST_STARTUP");
+ if (pExitPostStartup && *pExitPostStartup)
+ new ExitTimer();
+}
+
+void Desktop::OpenClients()
+{
+
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+
+ if (!rArgs.IsQuickstart())
+ {
+ OUString aHelpModule;
+ if (rArgs.IsHelpWriter()) {
+ aHelpModule = "swriter";
+ } else if (rArgs.IsHelpCalc()) {
+ aHelpModule = "scalc";
+ } else if (rArgs.IsHelpDraw()) {
+ aHelpModule = "sdraw";
+ } else if (rArgs.IsHelpImpress()) {
+ aHelpModule = "simpress";
+ } else if (rArgs.IsHelpBase()) {
+ aHelpModule = "sdatabase";
+ } else if (rArgs.IsHelpBasic()) {
+ aHelpModule = "sbasic";
+ } else if (rArgs.IsHelpMath()) {
+ aHelpModule = "smath";
+ }
+ if (!aHelpModule.isEmpty()) {
+ OUString aHelpURL = "vnd.sun.star.help://"
+ + aHelpModule
+ + "/start?Language="
+ + utl::ConfigManager::getUILocale();
+#if defined UNX
+ aHelpURL += "&System=UNX";
+#elif defined _WIN32
+ aHelpURL += "&System=WIN";
+#endif
+ Application::GetHelp()->Start(aHelpURL);
+ return;
+ }
+ }
+
+ // Disable AutoSave feature in case "--norestore" or a similar command line switch is set on the command line.
+ // The reason behind: AutoSave/EmergencySave/AutoRecovery share the same data.
+ // But the require that all documents, which are saved as backup should exists inside
+ // memory. May be this mechanism will be inconsistent if the configuration exists...
+ // but no document inside memory corresponds to this data.
+ // Further it's not acceptable to recover such documents without any UI. It can
+ // need some time, where the user won't see any results and wait for finishing the office startup...
+ bool bAllowRecoveryAndSessionManagement = ( !rArgs.IsNoRestore() ) && ( !rArgs.IsHeadless() );
+
+#if !defined ANDROID
+ // Enter safe mode if requested
+ if (Application::IsSafeModeEnabled()) {
+ handleSafeMode();
+ }
+#endif
+
+#if HAVE_FEATURE_BREAKPAD
+ if (officecfg::Office::Common::Misc::CrashReport::get() && CrashReporter::crashReportInfoExists())
+ handleCrashReport();
+#endif
+
+ if ( ! bAllowRecoveryAndSessionManagement )
+ {
+ try
+ {
+ Reference< XDispatch > xRecovery = css::frame::theAutoRecovery::get( ::comphelper::getProcessComponentContext() );
+ Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create( ::comphelper::getProcessComponentContext() );
+
+ css::util::URL aCmd;
+ aCmd.Complete = "vnd.sun.star.autorecovery:/disableRecovery";
+ xParser->parseStrict(aCmd);
+
+ xRecovery->dispatch(aCmd, css::uno::Sequence< css::beans::PropertyValue >());
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Could not disable AutoRecovery.");
+ }
+ }
+ else
+ {
+ bool bExistsRecoveryData = false;
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+ bool bCrashed = false;
+ bool bExistsSessionData = false;
+ bool const bDisableRecovery
+ = getenv("OOO_DISABLE_RECOVERY") != nullptr
+ || IsOnSystemEventLoop()
+ || !officecfg::Office::Recovery::RecoveryInfo::Enabled::get();
+
+ impl_checkRecoveryState(bCrashed, bExistsRecoveryData, bExistsSessionData);
+
+ if ( !bDisableRecovery &&
+ (
+ bExistsRecoveryData || // => crash with files => recovery
+ bCrashed // => crash without files => error report
+ )
+ )
+ {
+ try
+ {
+ impl_callRecoveryUI(
+ false , // false => force recovery instead of emergency save
+ bExistsRecoveryData);
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Error during recovery");
+ }
+ }
+#endif
+
+ Reference< XSessionManagerListener2 > xSessionListener;
+ try
+ {
+ // specifies whether the UI-interaction on Session shutdown is allowed
+ bool bUIOnSessionShutdownAllowed = officecfg::Office::Recovery::SessionShutdown::DocumentStoreUIEnabled::get();
+ xSessionListener = SessionListener::createWithOnQuitFlag(
+ ::comphelper::getProcessComponentContext(), bUIOnSessionShutdownAllowed);
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Registration of session listener failed");
+ }
+
+ if ( !bExistsRecoveryData && xSessionListener.is() )
+ {
+ // session management
+ try
+ {
+ xSessionListener->doRestore();
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Error in session management");
+ }
+ }
+ }
+
+ // write this information here to avoid depending on vcl in the crash reporter lib
+ CrashReporter::addKeyValue("Language", Application::GetSettings().GetLanguageTag().getBcp47(), CrashReporter::Create);
+
+ RequestHandler::EnableRequests();
+
+ ProcessDocumentsRequest aRequest(rArgs.getCwdUrl());
+ aRequest.aOpenList = rArgs.GetOpenList();
+ aRequest.aViewList = rArgs.GetViewList();
+ aRequest.aStartList = rArgs.GetStartList();
+ aRequest.aPrintList = rArgs.GetPrintList();
+ aRequest.aPrintToList = rArgs.GetPrintToList();
+ aRequest.aPrinterName = rArgs.GetPrinterName();
+ aRequest.aForceOpenList = rArgs.GetForceOpenList();
+ aRequest.aForceNewList = rArgs.GetForceNewList();
+ aRequest.aConversionList = rArgs.GetConversionList();
+ aRequest.aConversionParams = rArgs.GetConversionParams();
+ aRequest.aConversionOut = rArgs.GetConversionOut();
+ aRequest.aImageConversionType = rArgs.GetImageConversionType();
+ aRequest.aInFilter = rArgs.GetInFilter();
+ aRequest.bTextCat = rArgs.IsTextCat();
+ aRequest.bScriptCat = rArgs.IsScriptCat();
+
+ if ( !aRequest.aOpenList.empty() ||
+ !aRequest.aViewList.empty() ||
+ !aRequest.aStartList.empty() ||
+ !aRequest.aPrintList.empty() ||
+ !aRequest.aForceOpenList.empty() ||
+ !aRequest.aForceNewList.empty() ||
+ ( !aRequest.aPrintToList.empty() && !aRequest.aPrinterName.isEmpty() ) ||
+ !aRequest.aConversionList.empty() )
+ {
+ if ( rArgs.HasModuleParam() )
+ {
+ SvtModuleOptions aOpt;
+
+ // Support command line parameters to start a module (as preselection)
+ if ( rArgs.IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aRequest.aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
+ else if ( rArgs.IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aRequest.aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
+ else if ( rArgs.IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aRequest.aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( rArgs.IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aRequest.aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
+ }
+
+ // check for printing disabled
+ if( ( !(aRequest.aPrintList.empty() && aRequest.aPrintToList.empty()) )
+ && Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ {
+ aRequest.aPrintList.clear();
+ aRequest.aPrintToList.clear();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ DpResId(STR_ERR_PRINTDISABLED)));
+ xBox->run();
+ }
+
+ // Process request
+ if ( RequestHandler::ExecuteCmdLineRequests(aRequest, false) )
+ {
+ // Don't do anything if we have successfully called terminate at desktop:
+ return;
+ }
+ }
+
+ // no default document if a document was loaded by recovery or by command line or if soffice is used as server
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XElementAccess > xList( xDesktop->getFrames(), UNO_QUERY_THROW );
+ if ( xList->hasElements() )
+ return;
+
+ if ( rArgs.IsQuickstart() || rArgs.IsInvisible() || Application::AnyInput( VclInputFlags::APPEVENT ) )
+ // soffice was started as tray icon ...
+ return;
+
+ OpenDefault();
+}
+
+void Desktop::OpenDefault()
+{
+ OUString aName;
+ SvtModuleOptions aOpt;
+
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+ if ( rArgs.IsNoDefault() ) return;
+ if ( rArgs.HasModuleParam() )
+ {
+ // Support new command line parameters to start a module
+ if ( rArgs.IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITER );
+ else if ( rArgs.IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::CALC );
+ else if ( rArgs.IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( rArgs.IsBase() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DATABASE );
+ else if ( rArgs.IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DRAW );
+ else if ( rArgs.IsMath() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::MATH );
+ else if ( rArgs.IsGlobal() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITERGLOBAL );
+ else if ( rArgs.IsWeb() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITERWEB );
+ }
+
+ if ( aName.isEmpty() )
+ {
+ if (aOpt.IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE))
+ {
+ ShowBackingComponent(nullptr);
+ return;
+ }
+
+ // Old way to create a default document
+ if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITER );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::CALC );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DATABASE );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DRAW );
+ else
+ return;
+ }
+
+ ProcessDocumentsRequest aRequest(rArgs.getCwdUrl());
+ aRequest.aOpenList.push_back(aName);
+ RequestHandler::ExecuteCmdLineRequests(aRequest, false);
+}
+
+
+OUString GetURL_Impl(
+ const OUString& rName, std::optional< OUString > const & cwdUrl )
+{
+ // if rName is a vnd.sun.star.script URL do not attempt to parse it
+ // as INetURLObj does not handle URLs there
+ if (rName.startsWith("vnd.sun.star.script"))
+ {
+ return rName;
+ }
+
+ // don't touch file urls, those should already be in internal form
+ // they won't get better here (#112849#)
+ if (comphelper::isFileUrl(rName))
+ {
+ return rName;
+ }
+
+ if ( rName.startsWith("service:"))
+ {
+ return rName;
+ }
+
+ // Add path separator to these directory and make given URL (rName) absolute by using of current working directory
+ // Attention: "setFinalSlash()" is necessary for calling "smartRel2Abs()"!!!
+ // Otherwise last part will be ignored and wrong result will be returned!!!
+ // "smartRel2Abs()" interpret given URL as file not as path. So he truncate last element to get the base path ...
+ // But if we add a separator - he doesn't do it anymore.
+ INetURLObject aObj;
+ if (cwdUrl) {
+ aObj.SetURL(*cwdUrl);
+ aObj.setFinalSlash();
+ }
+
+ // Use the provided parameters for smartRel2Abs to support the usage of '%' in system paths.
+ // Otherwise this char won't get encoded and we are not able to load such files later,
+ bool bWasAbsolute;
+ INetURLObject aURL = aObj.smartRel2Abs( rName, bWasAbsolute, false, INetURLObject::EncodeMechanism::WasEncoded,
+ RTL_TEXTENCODING_UTF8, true );
+ OUString aFileURL = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ ::osl::FileStatus aStatus( osl_FileStatus_Mask_FileURL );
+ ::osl::DirectoryItem aItem;
+ if( ::osl::FileBase::E_None == ::osl::DirectoryItem::get( aFileURL, aItem ) &&
+ ::osl::FileBase::E_None == aItem.getFileStatus( aStatus ) )
+ aFileURL = aStatus.getFileURL();
+
+ return aFileURL;
+}
+
+void Desktop::HandleAppEvent( const ApplicationEvent& rAppEvent )
+{
+ switch ( rAppEvent.GetEvent() )
+ {
+ case ApplicationEvent::Type::Accept:
+ // every time an accept parameter is used we create an acceptor
+ // with the corresponding accept-string
+ createAcceptor(rAppEvent.GetStringData());
+ break;
+ case ApplicationEvent::Type::Appear:
+ if ( !GetCommandLineArgs().IsInvisible() && !impl_bringToFrontRecoveryUI() )
+ {
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ // find active task - the active task is always a visible task
+ Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
+ Reference< css::frame::XFrame > xTask = xDesktop->getActiveFrame();
+ if ( !xTask.is() )
+ {
+ // get any task if there is no active one
+ Reference< css::container::XIndexAccess > xList = xDesktop->getFrames();
+ if ( xList->getCount() > 0 )
+ xList->getByIndex(0) >>= xTask;
+ }
+
+ if ( xTask.is() )
+ {
+ Reference< css::awt::XTopWindow > xTop( xTask->getContainerWindow(), UNO_QUERY );
+ xTop->toFront();
+ }
+ else
+ {
+ // no visible task that could be activated found
+ Reference< css::awt::XWindow > xContainerWindow;
+ Reference< XFrame > xBackingFrame = xDesktop->findFrame( "_blank", 0);
+ if (xBackingFrame.is())
+ xContainerWindow = xBackingFrame->getContainerWindow();
+ if (xContainerWindow.is())
+ {
+ Reference< XController > xStartModule = StartModule::createWithParentWindow(xContext, xContainerWindow);
+ Reference< css::awt::XWindow > xBackingWin(xStartModule, UNO_QUERY);
+ // Attention: You MUST(!) call setComponent() before you call attachFrame().
+ // Because the backing component set the property "IsBackingMode" of the frame
+ // to true inside attachFrame(). But setComponent() reset this state every time ...
+ xBackingFrame->setComponent(xBackingWin, xStartModule);
+ xStartModule->attachFrame(xBackingFrame);
+ xContainerWindow->setVisible(true);
+
+ VclPtr<vcl::Window> pCompWindow = VCLUnoHelper::GetWindow(xBackingFrame->getComponentWindow());
+ if (pCompWindow)
+ pCompWindow->PaintImmediately();
+ }
+ }
+ }
+ break;
+ case ApplicationEvent::Type::Open:
+ {
+ const CommandLineArgs& rCmdLine = GetCommandLineArgs();
+ if ( !rCmdLine.IsInvisible() && !rCmdLine.IsTerminateAfterInit() )
+ {
+ ProcessDocumentsRequest docsRequest(rCmdLine.getCwdUrl());
+ std::vector<OUString> const & data(rAppEvent.GetStringsData());
+ docsRequest.aOpenList.insert(
+ docsRequest.aOpenList.end(), data.begin(), data.end());
+ RequestHandler::ExecuteCmdLineRequests(docsRequest, false);
+ }
+ }
+ break;
+ case ApplicationEvent::Type::OpenHelpUrl:
+ // start help for a specific URL
+ Application::GetHelp()->Start(rAppEvent.GetStringData());
+ break;
+ case ApplicationEvent::Type::Print:
+ {
+ const CommandLineArgs& rCmdLine = GetCommandLineArgs();
+ if ( !rCmdLine.IsInvisible() && !rCmdLine.IsTerminateAfterInit() )
+ {
+ ProcessDocumentsRequest docsRequest(rCmdLine.getCwdUrl());
+ std::vector<OUString> const & data(rAppEvent.GetStringsData());
+ docsRequest.aPrintList.insert(
+ docsRequest.aPrintList.end(), data.begin(), data.end());
+ RequestHandler::ExecuteCmdLineRequests(docsRequest, false);
+ }
+ }
+ break;
+ case ApplicationEvent::Type::PrivateDoShutdown:
+ {
+ Desktop* pD = dynamic_cast<Desktop*>(GetpApp());
+ OSL_ENSURE( pD, "no desktop ?!?" );
+ if( pD )
+ pD->doShutdown();
+ }
+ break;
+ case ApplicationEvent::Type::QuickStart:
+ if ( !GetCommandLineArgs().IsInvisible() )
+ {
+ // If the office has been started the second time its command line arguments are sent through a pipe
+ // connection to the first office. We want to reuse the quickstart option for the first office.
+ // NOTICE: The quickstart service must be initialized inside the "main thread", so we use the
+ // application events to do this (they are executed inside main thread)!!!
+ // Don't start quickstart service if the user specified "--invisible" on the command line!
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ css::office::Quickstart::createStart(xContext, true/*Quickstart*/);
+ }
+ break;
+ case ApplicationEvent::Type::ShowDialog:
+ // This is only used on macOS, and only for About or Preferences.
+ // Ignore all errors here. It's clicking a menu entry only ...
+ // The user will try it again, in case nothing happens .-)
+ try
+ {
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
+
+ Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create(xContext);
+ css::util::URL aCommand;
+ if( rAppEvent.GetStringData() == "PREFERENCES" )
+ aCommand.Complete = ".uno:OptionsTreeDialog";
+ else if( rAppEvent.GetStringData() == "ABOUT" )
+ aCommand.Complete = ".uno:About";
+ if( !aCommand.Complete.isEmpty() )
+ {
+ xParser->parseStrict(aCommand);
+
+ css::uno::Reference< css::frame::XDispatch > xDispatch = xDesktop->queryDispatch(aCommand, OUString(), 0);
+ if (xDispatch.is())
+ xDispatch->dispatch(aCommand, css::uno::Sequence< css::beans::PropertyValue >());
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("desktop.app", "exception thrown by dialog");
+ }
+ break;
+ case ApplicationEvent::Type::Unaccept:
+ // try to remove corresponding acceptor
+ destroyAcceptor(rAppEvent.GetStringData());
+ break;
+ default:
+ SAL_WARN( "desktop.app", "this cannot happen");
+ break;
+ }
+}
+
+#if !ENABLE_WASM_STRIP_SPLASH
+void Desktop::OpenSplashScreen()
+{
+ const CommandLineArgs &rCmdLine = GetCommandLineArgs();
+ // Show intro only if this is normal start (e.g. no server, no quickstart, no printing )
+ if ( !(!rCmdLine.IsInvisible() &&
+ !rCmdLine.IsHeadless() &&
+ !rCmdLine.IsQuickstart() &&
+ !rCmdLine.IsMinimized() &&
+ !rCmdLine.IsNoLogo() &&
+ !rCmdLine.IsTerminateAfterInit() &&
+ rCmdLine.GetPrintList().empty() &&
+ rCmdLine.GetPrintToList().empty() &&
+ rCmdLine.GetConversionList().empty()) )
+ return;
+
+ // Determine application name from command line parameters
+ OUString aAppName;
+ if ( rCmdLine.IsWriter() )
+ aAppName = "writer";
+ else if ( rCmdLine.IsCalc() )
+ aAppName = "calc";
+ else if ( rCmdLine.IsDraw() )
+ aAppName = "draw";
+ else if ( rCmdLine.IsImpress() )
+ aAppName = "impress";
+ else if ( rCmdLine.IsBase() )
+ aAppName = "base";
+ else if ( rCmdLine.IsGlobal() )
+ aAppName = "global";
+ else if ( rCmdLine.IsMath() )
+ aAppName = "math";
+ else if ( rCmdLine.IsWeb() )
+ aAppName = "web";
+
+ // Which splash to use
+ OUString aSplashService( "com.sun.star.office.SplashScreen" );
+ if ( rCmdLine.HasSplashPipe() )
+ aSplashService = "com.sun.star.office.PipeSplashScreen";
+
+ Sequence< Any > aSeq{ Any(true) /* bVisible */, Any(aAppName) };
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ m_rSplashScreen.set(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aSplashService, aSeq, xContext),
+ UNO_QUERY);
+
+ if(m_rSplashScreen.is())
+ m_rSplashScreen->start("SplashScreen", 100);
+
+}
+#endif
+
+void Desktop::SetSplashScreenProgress(sal_Int32 iProgress)
+{
+#if ENABLE_WASM_STRIP_SPLASH
+ (void) iProgress;
+#else
+ if(m_rSplashScreen.is())
+ {
+ m_rSplashScreen->setValue(iProgress);
+ }
+#endif
+}
+
+void Desktop::SetSplashScreenText( const OUString& rText )
+{
+#if ENABLE_WASM_STRIP_SPLASH
+ (void) rText;
+#else
+ if( m_rSplashScreen.is() )
+ {
+ m_rSplashScreen->setText( rText );
+ }
+#endif
+}
+
+void Desktop::CloseSplashScreen()
+{
+#if !ENABLE_WASM_STRIP_SPLASH
+ if(m_rSplashScreen.is())
+ {
+ SolarMutexGuard ensureSolarMutex;
+ m_rSplashScreen->end();
+ m_rSplashScreen = nullptr;
+ }
+#endif
+}
+
+
+IMPL_STATIC_LINK_NOARG(Desktop, AsyncInitFirstRun, Timer *, void)
+{
+ // does initializations which are necessary for the first run of the office
+ try
+ {
+ Reference< XJobExecutor > xExecutor = theJobExecutor::get( ::comphelper::getProcessComponentContext() );
+ xExecutor->trigger( "onFirstRunInitialization" );
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Desktop::DoFirstRunInitializations: caught an exception while trigger job executor" );
+ }
+}
+
+void Desktop::ShowBackingComponent(Desktop * progress)
+{
+ if (GetCommandLineArgs().IsNoDefault())
+ {
+ return;
+ }
+ Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create(xContext);
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(60);
+ }
+ Reference< XFrame > xBackingFrame = xDesktop->findFrame( "_blank", 0);
+ Reference< css::awt::XWindow > xContainerWindow;
+
+ if (xBackingFrame.is())
+ xContainerWindow = xBackingFrame->getContainerWindow();
+ if (!xContainerWindow.is())
+ return;
+
+ // set the WindowExtendedStyle::Document style. Normally, this is done by the TaskCreator service when a "_blank"
+ // frame/window is created. Since we do not use the TaskCreator here, we need to mimic its behavior,
+ // otherwise documents loaded into this frame will later on miss functionality depending on the style.
+ VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow );
+ SAL_WARN_IF( !pContainerWindow, "desktop.app", "Desktop::Main: no implementation access to the frame's container window!" );
+ pContainerWindow->SetExtendedStyle( pContainerWindow->GetExtendedStyle() | WindowExtendedStyle::Document );
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(75);
+ }
+
+ Reference< XController > xStartModule = StartModule::createWithParentWindow( xContext, xContainerWindow);
+ // Attention: You MUST(!) call setComponent() before you call attachFrame().
+ // Because the backing component set the property "IsBackingMode" of the frame
+ // to true inside attachFrame(). But setComponent() reset this state everytimes ...
+ xBackingFrame->setComponent(Reference< XWindow >(xStartModule, UNO_QUERY), xStartModule);
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(100);
+ }
+ xStartModule->attachFrame(xBackingFrame);
+ if (progress != nullptr)
+ {
+ progress->CloseSplashScreen();
+ }
+ xContainerWindow->setVisible(true);
+}
+
+
+void Desktop::CheckFirstRun( )
+{
+ if (!officecfg::Office::Common::Misc::FirstRun::get())
+ return;
+
+ // use VCL timer, which won't trigger during shutdown if the
+ // application exits before timeout
+ m_firstRunTimer.Start();
+
+#ifdef _WIN32
+ // Check if Quickstarter should be started (on Windows only)
+ OUString sRootKey = ReplaceStringHookProc("Software\\%OOOVENDOR\\%PRODUCTNAME\\%PRODUCTVERSION");
+ WCHAR szValue[8192];
+ DWORD nValueSize = sizeof(szValue);
+ HKEY hKey;
+ if (ERROR_SUCCESS == RegOpenKeyW(HKEY_LOCAL_MACHINE, o3tl::toW(sRootKey.getStr()), &hKey))
+ {
+ if ( ERROR_SUCCESS == RegQueryValueExW( hKey, L"RunQuickstartAtFirstStart", nullptr, nullptr, reinterpret_cast<LPBYTE>(szValue), &nValueSize ) )
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ css::office::Quickstart::createAutoStart(xContext, true/*Quickstart*/, true/*bAutostart*/);
+ RegCloseKey( hKey );
+ }
+ }
+#endif
+
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Misc::FirstRun::set(false, batch);
+ batch->commit();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/appinit.cxx b/desktop/source/app/appinit.cxx
new file mode 100644
index 0000000000..51b466c6b9
--- /dev/null
+++ b/desktop/source/app/appinit.cxx
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <algorithm>
+
+#include <app.hxx>
+#include <dp_shared.hxx>
+#include "cmdlineargs.hxx"
+#include <strings.hrc>
+#include <com/sun/star/registry/XSimpleRegistry.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <cppuhelper/bootstrap.hxx>
+#include <officecfg/Setup.hxx>
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/pathoptions.hxx>
+
+#include <iostream>
+#include <map>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::registry;
+using namespace ::com::sun::star::ucb;
+
+namespace desktop
+{
+
+
+static void configureUcb()
+{
+ // For backwards compatibility, in case some code still uses plain
+ // createInstance w/o args directly to obtain an instance:
+ UniversalContentBroker::create(comphelper::getProcessComponentContext());
+}
+
+void Desktop::InitApplicationServiceManager()
+{
+ Reference<XMultiServiceFactory> sm;
+#ifdef ANDROID
+ OUString aUnoRc( "file:///assets/program/unorc" );
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext( aUnoRc )->getServiceManager(),
+ UNO_QUERY_THROW);
+#elif defined(IOS)
+ OUString uri( "$APP_DATA_DIR" );
+ rtl_bootstrap_expandMacros( &uri.pData );
+ OUString aUnoRc("file://" + uri + "/unorc");
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext( aUnoRc )->getServiceManager(),
+ UNO_QUERY_THROW);
+#else
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext()->getServiceManager(),
+ UNO_QUERY_THROW);
+#endif
+ comphelper::setProcessServiceFactory(sm);
+}
+
+void Desktop::RegisterServices()
+{
+ if( m_bServicesRegistered )
+ return;
+
+ // interpret command line arguments
+ CommandLineArgs& rCmdLine = GetCommandLineArgs();
+
+ // Headless mode for FAT Office, auto cancels any dialogs that popup
+ if (rCmdLine.IsHeadless())
+ Application::EnableHeadlessMode(false);
+
+ // read accept string from configuration
+ OUString conDcpCfg(
+ officecfg::Setup::Office::ooSetupConnectionURL::get());
+ if (!conDcpCfg.isEmpty()) {
+ createAcceptor(conDcpCfg);
+ }
+
+ std::vector< OUString > const & conDcp = rCmdLine.GetAccept();
+ for (auto const& elem : conDcp)
+ {
+ createAcceptor(elem);
+ }
+
+ configureUcb();
+
+ CreateTemporaryDirectory();
+ m_bServicesRegistered = true;
+}
+
+typedef std::map< OUString, css::uno::Reference<css::lang::XInitialization> > AcceptorMap;
+
+namespace
+{
+ AcceptorMap& acceptorMap()
+ {
+ static AcceptorMap SINGLETON;
+ return SINGLETON;
+ }
+ OUString& CurrentTempURL()
+ {
+ static OUString SINGLETON;
+ return SINGLETON;
+ }
+}
+
+static bool bAccept = false;
+
+void Desktop::createAcceptor(const OUString& aAcceptString)
+{
+ // check whether the requested acceptor already exists
+ AcceptorMap &rMap = acceptorMap();
+ AcceptorMap::const_iterator pIter = rMap.find(aAcceptString);
+ if (pIter != rMap.end() )
+ {
+ // there is already an acceptor with this description
+ SAL_WARN( "desktop.app", "Acceptor already exists.");
+ return;
+ }
+
+ Sequence< Any > aSeq{ Any(aAcceptString), Any(bAccept) };
+ Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ Reference<XInitialization> rAcceptor(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.office.Acceptor", xContext),
+ UNO_QUERY );
+ if ( rAcceptor.is() )
+ {
+ try
+ {
+ rAcceptor->initialize( aSeq );
+ rMap.emplace(aAcceptString, rAcceptor);
+ }
+ catch (const css::uno::Exception&)
+ {
+ // no error handling needed...
+ // acceptor just won't come up
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Acceptor could not be created");
+ }
+ }
+ else
+ {
+ ::std::cerr << "UNO Remote Protocol acceptor could not be created, presumably because it has been disabled in configuration." << ::std::endl;
+ }
+}
+
+namespace {
+
+class enable
+{
+ private:
+ Sequence<Any> m_aSeq{ Any(true) };
+ public:
+ enable() = default;
+ void operator() (const AcceptorMap::value_type& val) {
+ if (val.second.is()) {
+ val.second->initialize(m_aSeq);
+ }
+ }
+};
+
+}
+
+// enable acceptors
+IMPL_STATIC_LINK_NOARG(Desktop, EnableAcceptors_Impl, void*, void)
+{
+ if (!bAccept)
+ {
+ // from now on, all new acceptors are enabled
+ bAccept = true;
+ // enable existing acceptors by calling initialize(true)
+ // on all existing acceptors
+ AcceptorMap &rMap = acceptorMap();
+ std::for_each(rMap.begin(), rMap.end(), enable());
+ }
+}
+
+void Desktop::destroyAcceptor(const OUString& aAcceptString)
+{
+ // special case stop all acceptors
+ AcceptorMap &rMap = acceptorMap();
+ if (aAcceptString == "all") {
+ rMap.clear();
+
+ } else {
+ // try to remove acceptor from map
+ AcceptorMap::const_iterator pIter = rMap.find(aAcceptString);
+ if (pIter != rMap.end() ) {
+ // remove reference from map
+ // this is the last reference and the acceptor will be destructed
+ rMap.erase(aAcceptString);
+ } else {
+ SAL_WARN( "desktop.app", "Found no acceptor to remove");
+ }
+ }
+}
+
+
+void Desktop::DeregisterServices()
+{
+ // stop all acceptors by clearing the map
+ acceptorMap().clear();
+}
+
+void Desktop::CreateTemporaryDirectory()
+{
+ OUString aTempBaseURL;
+ try
+ {
+ SvtPathOptions aOpt;
+ aTempBaseURL = aOpt.GetTempPath();
+ }
+ catch (RuntimeException& e)
+ {
+ // Catch runtime exception here: We have to add language dependent info
+ // to the exception message. Fallback solution uses hard coded string.
+ OUString aMsg = DpResId(STR_BOOTSTRAP_ERR_NO_PATHSET_SERVICE);
+ e.Message = aMsg + e.Message;
+ throw;
+ }
+
+ // create new current temporary directory
+ OUString aTempPath = ::utl::SetTempNameBaseDirectory( aTempBaseURL );
+ if ( aTempPath.isEmpty()
+ && ::osl::File::getTempDirURL( aTempBaseURL ) == osl::FileBase::E_None )
+ {
+ aTempPath = ::utl::SetTempNameBaseDirectory( aTempBaseURL );
+ }
+
+ // set new current temporary directory
+ OUString aRet;
+ if (osl::FileBase::getFileURLFromSystemPath( aTempPath, aRet )
+ != osl::FileBase::E_None)
+ {
+ aRet.clear();
+ }
+ CurrentTempURL() = aRet;
+}
+
+void Desktop::RemoveTemporaryDirectory()
+{
+ // remove current temporary directory
+ OUString &rCurrentTempURL = CurrentTempURL();
+ if ( !rCurrentTempURL.isEmpty() )
+ {
+ ::utl::UCBContentHelper::Kill( rCurrentTempURL );
+ }
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/check_ext_deps.cxx b/desktop/source/app/check_ext_deps.cxx
new file mode 100644
index 0000000000..4a69a8cf79
--- /dev/null
+++ b/desktop/source/app/check_ext_deps.cxx
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+#include <config_features.h>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/ui/LicenseDialog.hpp>
+#include <com/sun/star/task/OfficeRestartManager.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+
+#include <app.hxx>
+#include <utility>
+
+#include <dp_misc.h>
+
+using namespace desktop;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::task;
+using namespace com::sun::star::uno;
+
+namespace
+{
+//For use with XExtensionManager.synchronize
+class SilentCommandEnv
+ : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment,
+ task::XInteractionHandler,
+ ucb::XProgressHandler >
+{
+ uno::Reference<uno::XComponentContext> mxContext;
+ Desktop *mpDesktop;
+ sal_Int32 mnLevel;
+ sal_Int32 mnProgress;
+
+public:
+ SilentCommandEnv(
+ uno::Reference<uno::XComponentContext> xContext,
+ Desktop* pDesktop );
+ virtual ~SilentCommandEnv() override;
+
+ // XCommandEnvironment
+ virtual uno::Reference<task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual uno::Reference<ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ uno::Reference<task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( uno::Any const & Status ) override;
+ virtual void SAL_CALL update( uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+
+SilentCommandEnv::SilentCommandEnv(
+ uno::Reference<uno::XComponentContext> xContext,
+ Desktop* pDesktop ):
+ mxContext(std::move( xContext )),
+ mpDesktop( pDesktop ),
+ mnLevel( 0 ),
+ mnProgress( 25 )
+{}
+
+
+SilentCommandEnv::~SilentCommandEnv()
+{
+ if (mpDesktop)
+ mpDesktop->SetSplashScreenText(OUString());
+}
+
+
+Reference<task::XInteractionHandler> SilentCommandEnv::getInteractionHandler()
+{
+ return this;
+}
+
+
+Reference<ucb::XProgressHandler> SilentCommandEnv::getProgressHandler()
+{
+ return this;
+}
+
+
+// XInteractionHandler
+void SilentCommandEnv::handle( Reference< task::XInteractionRequest> const & xRequest )
+{
+ deployment::LicenseException licExc;
+
+ uno::Any request( xRequest->getRequest() );
+ bool bApprove = true;
+
+ if ( request >>= licExc )
+ {
+ uno::Reference< ui::dialogs::XExecutableDialog > xDialog(
+ deployment::ui::LicenseDialog::create(
+ mxContext, VCLUnoHelper::GetInterface( nullptr ),
+ licExc.ExtensionName, licExc.Text ) );
+ sal_Int16 res = xDialog->execute();
+ if ( res == ui::dialogs::ExecutableDialogResults::CANCEL )
+ bApprove = false;
+ else if ( res == ui::dialogs::ExecutableDialogResults::OK )
+ bApprove = true;
+ else
+ {
+ OSL_ASSERT(false);
+ }
+ }
+
+ // We approve everything here
+ uno::Sequence< Reference< task::XInteractionContinuation > > conts( xRequest->getContinuations() );
+ Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if ( bApprove )
+ {
+ uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY );
+ if ( xInteractionApprove.is() )
+ xInteractionApprove->select();
+ }
+ else
+ {
+ uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY );
+ if ( xInteractionAbort.is() )
+ xInteractionAbort->select();
+ }
+ }
+}
+
+
+// XProgressHandler
+void SilentCommandEnv::push( uno::Any const & rStatus )
+{
+ OUString sText;
+ mnLevel += 1;
+
+ if (mpDesktop && rStatus.hasValue() && (rStatus >>= sText))
+ {
+ if ( mnLevel <= 3 )
+ mpDesktop->SetSplashScreenText( sText );
+ else
+ mpDesktop->SetSplashScreenProgress( ++mnProgress );
+ }
+}
+
+
+void SilentCommandEnv::update( uno::Any const & rStatus )
+{
+ OUString sText;
+ if (mpDesktop && rStatus.hasValue() && (rStatus >>= sText))
+ {
+ mpDesktop->SetSplashScreenText( sText );
+ }
+}
+
+
+void SilentCommandEnv::pop()
+{
+ mnLevel -= 1;
+}
+
+} // end namespace
+
+
+constexpr OUString aAccessSrvc = u"com.sun.star.configuration.ConfigurationUpdateAccess"_ustr;
+
+static sal_Int16 impl_showExtensionDialog( uno::Reference< uno::XComponentContext > const &xContext )
+{
+ uno::Reference< uno::XInterface > xService;
+ sal_Int16 nRet = 0;
+
+ uno::Reference< lang::XMultiComponentFactory > xServiceManager( xContext->getServiceManager() );
+ if( !xServiceManager.is() )
+ throw uno::RuntimeException(
+ "impl_showExtensionDialog(): unable to obtain service manager from component context", uno::Reference< uno::XInterface > () );
+
+ xService = xServiceManager->createInstanceWithContext( "com.sun.star.deployment.ui.UpdateRequiredDialog", xContext );
+ uno::Reference< ui::dialogs::XExecutableDialog > xExecutable( xService, uno::UNO_QUERY );
+ if ( xExecutable.is() )
+ nRet = xExecutable->execute();
+
+ return nRet;
+}
+
+
+// Check dependencies of all packages
+
+static bool impl_checkDependencies( const uno::Reference< uno::XComponentContext > &xContext )
+{
+ uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages;
+ uno::Reference< deployment::XExtensionManager > xExtensionManager = deployment::ExtensionManager::get( xContext );
+
+ if ( !xExtensionManager.is() )
+ {
+ SAL_WARN( "desktop.app", "Could not get the Extension Manager!" );
+ return true;
+ }
+
+ try {
+ xAllPackages = xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException & ) { return true; }
+ catch ( const ucb::CommandFailedException & ) { return true; }
+ catch ( const ucb::CommandAbortedException & ) { return true; }
+ catch ( const lang::IllegalArgumentException & e ) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+
+#ifdef DEBUG
+ sal_Int32 const nMax = 3;
+#else
+ sal_Int32 const nMax = 2;
+#endif
+
+ for ( uno::Sequence< uno::Reference< deployment::XPackage > > const & xPackageList : std::as_const(xAllPackages) )
+ {
+ for ( sal_Int32 j = 0; (j<nMax) && (j < xPackageList.getLength()); ++j )
+ {
+ uno::Reference< deployment::XPackage > xPackage = xPackageList[j];
+ if ( xPackage.is() )
+ {
+ bool bRegistered = false;
+ try {
+ beans::Optional< beans::Ambiguous< sal_Bool > > option( xPackage->isRegistered( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() ) );
+ if ( option.IsPresent )
+ {
+ ::beans::Ambiguous< sal_Bool > const & reg = option.Value;
+ if ( reg.IsAmbiguous )
+ bRegistered = false;
+ else
+ bRegistered = reg.Value;
+ }
+ else
+ bRegistered = false;
+ }
+ catch ( const uno::RuntimeException & ) { throw; }
+ catch (const uno::Exception & ) {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "" );
+ }
+
+ if ( bRegistered )
+ {
+ bool bDependenciesValid = false;
+ try {
+ bDependenciesValid = xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException & ) {}
+ if ( ! bDependenciesValid )
+ {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+// resets the 'check needed' flag (needed, if aborted)
+
+static void impl_setNeedsCompatCheck()
+{
+ try {
+ Reference< XMultiServiceFactory > theConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() ) );
+
+ beans::NamedValue v( "nodepath",
+ Any( OUString("org.openoffice.Setup/Office") ) );
+ Sequence< Any > theArgs{ Any(v) };
+ Reference< beans::XPropertySet > pset(
+ theConfigProvider->createInstanceWithArguments( aAccessSrvc, theArgs ), UNO_QUERY_THROW );
+
+ Any value( OUString("never") );
+
+ pset->setPropertyValue("LastCompatibilityCheckID", value );
+ Reference< util::XChangesBatch >( pset, UNO_QUERY_THROW )->commitChanges();
+ }
+ catch (const Exception&) {}
+}
+
+
+// to check if we need checking the dependencies of the extensions again, we compare
+// the build id of the office with the one of the last check
+
+static bool impl_needsCompatCheck()
+{
+ bool bNeedsCheck = false;
+ OUString aLastCheckBuildID;
+ OUString aCurrentBuildID( "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}" );
+ rtl::Bootstrap::expandMacros( aCurrentBuildID );
+
+ try {
+ Reference< XMultiServiceFactory > theConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() ) );
+
+ beans::NamedValue v( "nodepath",
+ Any( OUString("org.openoffice.Setup/Office") ) );
+ Sequence< Any > theArgs{ Any(v) };
+ Reference< beans::XPropertySet > pset(
+ theConfigProvider->createInstanceWithArguments( aAccessSrvc, theArgs ), UNO_QUERY_THROW );
+
+ Any result = pset->getPropertyValue("LastCompatibilityCheckID");
+
+ result >>= aLastCheckBuildID;
+ if ( aLastCheckBuildID != aCurrentBuildID )
+ {
+ bNeedsCheck = true;
+ result <<= aCurrentBuildID;
+ pset->setPropertyValue("LastCompatibilityCheckID", result );
+ Reference< util::XChangesBatch >( pset, UNO_QUERY_THROW )->commitChanges();
+ }
+#ifdef DEBUG
+ bNeedsCheck = true;
+#endif
+ }
+ catch (const css::uno::Exception&) {}
+
+ return bNeedsCheck;
+}
+
+
+// Do we need to check the dependencies of the extensions?
+// When there are unresolved issues, we can't continue with startup
+bool Desktop::CheckExtensionDependencies()
+{
+ if (!impl_needsCompatCheck())
+ {
+ return false;
+ }
+
+ uno::Reference< uno::XComponentContext > xContext(
+ comphelper::getProcessComponentContext());
+
+ bool bDependenciesValid = impl_checkDependencies( xContext );
+
+ short nRet = 0;
+
+ if ( !bDependenciesValid )
+ nRet = impl_showExtensionDialog( xContext );
+
+ if ( nRet == -1 )
+ {
+ impl_setNeedsCompatCheck();
+ return true;
+ }
+ else
+ return false;
+}
+
+void Desktop::SynchronizeExtensionRepositories(bool bCleanedExtensionCache, Desktop* pDesktop)
+{
+ uno::Reference< uno::XComponentContext > context(
+ comphelper::getProcessComponentContext());
+ uno::Reference< ucb::XCommandEnvironment > silent(
+ new SilentCommandEnv(context, pDesktop));
+ if (bCleanedExtensionCache) {
+ deployment::ExtensionManager::get(context)->reinstallDeployedExtensions(
+ true, "user", Reference<task::XAbortChannel>(), silent);
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ if (!comphelper::LibreOfficeKit::isActive())
+ task::OfficeRestartManager::get(context)->requestRestart(
+ silent->getInteractionHandler());
+#endif
+ } else {
+ // reinstallDeployedExtensions above already calls syncRepositories internally
+
+ // Force syncing repositories on startup. There are cases where the extension
+ // registration becomes invalid which leads to extensions not starting up, although
+ // installed and active. Syncing extension repos on startup fixes that.
+ dp_misc::syncRepositories(/*force=*/true, silent);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlineargs.cxx b/desktop/source/app/cmdlineargs.cxx
new file mode 100644
index 0000000000..e7f3152040
--- /dev/null
+++ b/desktop/source/app/cmdlineargs.cxx
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#if HAVE_FEATURE_MACOSX_SANDBOX
+#include <premac.h>
+#include <Foundation/Foundation.h>
+#include <postmac.h>
+#endif
+
+#include "cmdlineargs.hxx"
+#include <osl/thread.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/process.h>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <unotools/bootstrap.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+
+#include <mutex>
+
+namespace desktop
+{
+
+namespace {
+
+OUString translateExternalUris(OUString const & input) {
+ OUString t(
+ css::uri::ExternalUriReferenceTranslator::create(
+ comphelper::getProcessComponentContext())->
+ translateToInternal(input));
+ return t.isEmpty() ? input : t;
+}
+
+std::vector< OUString > translateExternalUris(
+ std::vector< OUString > const & input)
+{
+ std::vector< OUString > t;
+ t.reserve(input.size());
+ for (auto const& elem : input)
+ {
+ t.push_back(translateExternalUris(elem));
+ }
+ return t;
+}
+
+class ExtCommandLineSupplier: public CommandLineArgs::Supplier {
+public:
+ explicit ExtCommandLineSupplier():
+ m_count(
+ comphelper::LibreOfficeKit::isActive()
+ ? 0 : rtl_getAppCommandArgCount()),
+ m_index(0)
+ {
+ OUString url;
+ if (utl::Bootstrap::getProcessWorkingDir(url)) {
+ m_cwdUrl = url;
+ }
+ }
+
+ virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
+
+ virtual bool next(OUString * argument) override {
+ OSL_ASSERT(argument != nullptr);
+ if (m_index < m_count) {
+ rtl_getAppCommandArg(m_index++, &argument->pData);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+private:
+ std::optional< OUString > m_cwdUrl;
+ sal_uInt32 m_count;
+ sal_uInt32 m_index;
+};
+
+enum class CommandLineEvent {
+ Open, Print, View, Start, PrintTo,
+ ForceOpen, ForceNew, Conversion, BatchPrint
+};
+
+// Office URI Schemes: see https://msdn.microsoft.com/en-us/library/dn906146
+// This functions checks if the arg is an Office URI.
+// If applicable, it updates arg to inner URI.
+// If no event argument is explicitly set in command line,
+// then it returns updated command line event,
+// according to Office URI command.
+CommandLineEvent CheckOfficeURI(/* in,out */ OUString& arg, CommandLineEvent curEvt)
+{
+ // 1. Strip the scheme name
+ OUString rest1;
+ bool isOfficeURI = ( arg.startsWithIgnoreAsciiCase("vnd.libreoffice.command:", &rest1) // Proposed extended schema
+ || arg.startsWithIgnoreAsciiCase("ms-word:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-powerpoint:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-excel:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-visio:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-access:", &rest1));
+ if (!isOfficeURI)
+ return curEvt;
+
+ OUString rest2;
+ tools::Long nURIlen = -1;
+
+ // URL might be encoded
+ OUString decoded_rest = rest1.replaceAll("%7C", "|").replaceAll("%7c", "|");
+
+ // 2. Discriminate by command name (incl. 1st command argument descriptor)
+ // Extract URI: everything up to possible next argument
+ if (decoded_rest.startsWith("ofv|u|", &rest2))
+ {
+ // Open for view - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::View;
+ nURIlen = rest2.indexOf("|");
+ }
+ else if (decoded_rest.startsWith("ofe|u|", &rest2))
+ {
+ // Open for editing - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::ForceOpen;
+ nURIlen = rest2.indexOf("|");
+ }
+ else if (decoded_rest.startsWith("nft|u|", &rest2))
+ {
+ // New from template - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::ForceNew;
+ nURIlen = rest2.indexOf("|");
+ // TODO: process optional second argument (default save-to location)
+ // For now, we just ignore it
+ }
+ else
+ {
+ // Abbreviated scheme: <scheme-name>:URI
+ // "ofv|u|" implied
+ // override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::View;
+ rest2 = rest1;
+ }
+ if (nURIlen < 0)
+ nURIlen = rest2.getLength();
+ auto const uri = rest2.subView(0, nURIlen);
+ if (INetURLObject(uri).GetProtocol() == INetProtocol::Macro) {
+ // Let the "Open" machinery process the full command URI (leading to failure, by intention,
+ // as the "Open" machinery does not know about those command URI schemes):
+ curEvt = CommandLineEvent::Open;
+ } else {
+ arg = uri;
+ }
+ return curEvt;
+}
+
+// Skip single newline (be it *NIX LF, MacOS CR, of Win CRLF)
+// Changes the offset, and returns true if moved
+bool SkipNewline(const char* & pStr)
+{
+ if ((*pStr != '\r') && (*pStr != '\n'))
+ return false;
+ if (*pStr == '\r')
+ ++pStr;
+ if (*pStr == '\n')
+ ++pStr;
+ return true;
+}
+
+// Web query: http://support.microsoft.com/kb/157482
+CommandLineEvent CheckWebQuery(/* in,out */ OUString& arg, CommandLineEvent curEvt)
+{
+ // Only handle files with extension .iqy
+ if (!arg.endsWithIgnoreAsciiCase(".iqy"))
+ return curEvt;
+
+ static std::mutex aMutex;
+ std::lock_guard aGuard(aMutex);
+
+ try
+ {
+ OUString sFileURL;
+ // Cannot use translateExternalUris yet, because process service factory is not yet available
+ if (osl::FileBase::getFileURLFromSystemPath(arg, sFileURL) != osl::FileBase::RC::E_None)
+ return curEvt;
+ SvFileStream stream(sFileURL, StreamMode::READ);
+
+ const sal_Int32 nBufLen = 32000;
+ char sBuffer[nBufLen];
+ size_t nRead = stream.ReadBytes(sBuffer, nBufLen);
+ if (nRead < 8) // WEB\n1\n...
+ return curEvt;
+
+ const char* pPos = sBuffer;
+ if (strncmp(pPos, "WEB", 3) != 0)
+ return curEvt;
+ pPos += 3;
+ if (!SkipNewline(pPos))
+ return curEvt;
+ if (*pPos != '1')
+ return curEvt;
+ ++pPos;
+ if (!SkipNewline(pPos))
+ return curEvt;
+
+ OStringBuffer aResult(nRead);
+ do
+ {
+ const char* pPos1 = pPos;
+ const char* pEnd = sBuffer + nRead;
+ while ((pPos1 < pEnd) && (*pPos1 != '\r') && (*pPos1 != '\n'))
+ ++pPos1;
+ aResult.append(pPos, pPos1 - pPos);
+ if (pPos1 < pEnd) // newline
+ break;
+ pPos = sBuffer;
+ } while ((nRead = stream.ReadBytes(sBuffer, nBufLen)) > 0);
+
+ stream.Close();
+
+ arg = OStringToOUString(aResult, osl_getThreadTextEncoding());
+ return CommandLineEvent::ForceNew;
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.app", "An error processing Web Query file: " << arg);
+ }
+
+ return curEvt;
+}
+
+} // namespace
+
+CommandLineArgs::Supplier::Exception::Exception() {}
+
+CommandLineArgs::Supplier::Exception::Exception(Exception const &) {}
+
+CommandLineArgs::Supplier::Exception &
+CommandLineArgs::Supplier::Exception::operator =(Exception const &)
+{ return *this; }
+
+CommandLineArgs::Supplier::~Supplier() {}
+
+// initialize class with command line parameters from process environment
+CommandLineArgs::CommandLineArgs()
+{
+ InitParamValues();
+ ExtCommandLineSupplier s;
+ ParseCommandLine_Impl( s );
+}
+
+CommandLineArgs::CommandLineArgs( Supplier& supplier )
+{
+ InitParamValues();
+ ParseCommandLine_Impl( supplier );
+}
+
+void CommandLineArgs::ParseCommandLine_Impl( Supplier& supplier )
+{
+ m_cwdUrl = supplier.getCwdUrl();
+ CommandLineEvent eCurrentEvent = CommandLineEvent::Open;
+
+ for (;;)
+ {
+ OUString aArg;
+ if ( !supplier.next( &aArg ) )
+ {
+ break;
+ }
+
+ if ( !aArg.isEmpty() )
+ {
+ m_bEmpty = false;
+ OUString oArg;
+ OUString oDeprecatedArg;
+ if (!aArg.startsWith("--", &oArg) && aArg.startsWith("-", &oArg)
+ && aArg.getLength() > 2) // -h, -?, -n, -o, -p are still valid
+ {
+ oDeprecatedArg = aArg; // save here, since aArg can change later
+ }
+
+ OUString rest;
+ if ( oArg == "minimized" )
+ {
+ m_minimized = true;
+ }
+ else if ( oArg == "invisible" )
+ {
+ m_invisible = true;
+ }
+ else if ( oArg == "norestore" )
+ {
+ m_norestore = true;
+ }
+ else if ( oArg == "nodefault" )
+ {
+ m_nodefault = true;
+ }
+ else if ( oArg == "headless" )
+ {
+ setHeadless();
+ }
+ else if ( oArg == "safe-mode" )
+ {
+ m_safemode = true;
+ }
+ else if ( oArg == "cat" )
+ {
+ m_textcat = true;
+ m_conversionparams = "txt:Text";
+ eCurrentEvent = CommandLineEvent::Conversion;
+ setHeadless();
+ }
+ else if ( oArg == "script-cat" )
+ {
+ m_scriptcat = true;
+ eCurrentEvent = CommandLineEvent::Conversion;
+ setHeadless();
+ }
+ else if ( oArg == "quickstart" )
+ {
+#if defined(ENABLE_QUICKSTART_APPLET)
+ m_quickstart = true;
+#endif
+ m_noquickstart = false;
+ }
+ else if ( oArg == "quickstart=no" )
+ {
+ m_noquickstart = true;
+ m_quickstart = false;
+ }
+ else if ( oArg == "terminate_after_init" )
+ {
+ m_terminateafterinit = true;
+ }
+ else if ( oArg == "nofirststartwizard" )
+ {
+ // Do nothing, accept only for backward compatibility
+ }
+ else if ( oArg == "nologo" )
+ {
+ m_nologo = true;
+ }
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ else if ( oArg == "nolockcheck" )
+ {
+ m_nolockcheck = true;
+ }
+#endif
+ else if ( oArg == "help" || aArg == "-h" || aArg == "-?" )
+ {
+ m_help = true;
+ }
+ else if ( oArg == "helpwriter" )
+ {
+ m_helpwriter = true;
+ }
+ else if ( oArg == "helpcalc" )
+ {
+ m_helpcalc = true;
+ }
+ else if ( oArg == "helpdraw" )
+ {
+ m_helpdraw = true;
+ }
+ else if ( oArg == "helpimpress" )
+ {
+ m_helpimpress = true;
+ }
+ else if ( oArg == "helpbase" )
+ {
+ m_helpbase = true;
+ }
+ else if ( oArg == "helpbasic" )
+ {
+ m_helpbasic = true;
+ }
+ else if ( oArg == "helpmath" )
+ {
+ m_helpmath = true;
+ }
+ else if ( oArg == "protector" )
+ {
+ // Not relevant for us here, but can be used in unit tests.
+ // Usually unit tests would not end up here, but e.g. the
+ // LOK Tiled Rendering tests end up running a full soffice
+ // process, and we can't bail on the use of --protector.
+
+ // We specifically need to consume the following 2 arguments
+ // for --protector
+ if ((!supplier.next(&aArg) || !supplier.next(&aArg)) && m_unknown.isEmpty())
+ m_unknown = "--protector must be followed by two arguments";
+ }
+ else if ( oArg == "version" )
+ {
+ m_version = true;
+ }
+ else if ( oArg.startsWith("splash-pipe=") )
+ {
+ m_splashpipe = true;
+ }
+#ifdef MACOSX
+ /* #i84053# ignore -psn on Mac
+ Platform dependent #ifdef here is ugly, however this is currently
+ the only platform dependent parameter. Should more appear
+ we should find a better solution
+ */
+ else if ( aArg.startsWith("-psn") )
+ {
+ oDeprecatedArg.clear();
+ }
+#endif
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ else if ( oArg == "nstemporarydirectory" )
+ {
+ printf("%s\n", [NSTemporaryDirectory() UTF8String]);
+ exit(0);
+ }
+#endif
+#ifdef _WIN32
+ /* fdo#57203 ignore -Embedding on Windows
+ when LibreOffice is launched by COM+
+ */
+ else if ( oArg == "Embedding" )
+ {
+ oDeprecatedArg.clear();
+ }
+#endif
+ else if ( oArg.startsWith("infilter=", &rest))
+ {
+ m_infilter.push_back(rest);
+ }
+ else if ( oArg.startsWith("accept=", &rest))
+ {
+ m_accept.push_back(rest);
+ }
+ else if ( oArg.startsWith("unaccept=", &rest))
+ {
+ m_unaccept.push_back(rest);
+ }
+ else if ( oArg.startsWith("language=", &rest))
+ {
+ m_language = rest;
+ }
+ else if ( oArg.startsWith("pidfile=", &rest))
+ {
+ m_pidfile = rest;
+ }
+ else if ( oArg == "writer" )
+ {
+ m_writer = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "calc" )
+ {
+ m_calc = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "draw" )
+ {
+ m_draw = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "impress" )
+ {
+ m_impress = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "base" )
+ {
+ m_base = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "global" )
+ {
+ m_global = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "math" )
+ {
+ m_math = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "web" )
+ {
+ m_web = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( aArg == "-n" )
+ {
+ // force new documents based on the following documents
+ eCurrentEvent = CommandLineEvent::ForceNew;
+ }
+ else if ( aArg == "-o" )
+ {
+ // force open documents regardless if they are templates or not
+ eCurrentEvent = CommandLineEvent::ForceOpen;
+ }
+ else if ( oArg == "pt" )
+ {
+ // Print to special printer
+ eCurrentEvent = CommandLineEvent::PrintTo;
+ // first argument after "-pt" must be the printer name
+ if (supplier.next(&aArg))
+ m_printername = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--pt must be followed by printername";
+ }
+ else if ( aArg == "-p" )
+ {
+ // Print to default printer
+ eCurrentEvent = CommandLineEvent::Print;
+ }
+ else if ( oArg == "view")
+ {
+ // open in viewmode
+ eCurrentEvent = CommandLineEvent::View;
+ }
+ else if ( oArg == "show" )
+ {
+ // open in viewmode
+ eCurrentEvent = CommandLineEvent::Start;
+ }
+ else if ( oArg == "display" )
+ {
+ // The command line argument following --display should
+ // always be treated as the argument of --display.
+ // --display and its argument are handled "out of line"
+ // in Unix-only desktop/unx/source/splashx.c and vcl/unx/*,
+ // and just ignored here
+ (void)supplier.next(&aArg);
+ }
+ else if ( oArg == "convert-to" )
+ {
+ eCurrentEvent = CommandLineEvent::Conversion;
+ // first argument must be the params
+ if (supplier.next(&aArg))
+ {
+ m_conversionparams = aArg;
+ // It doesn't make sense to use convert-to without headless.
+ setHeadless();
+ }
+ else if (m_unknown.isEmpty())
+ m_unknown = "--convert-to must be followed by output_file_extension[:output_filter_name]";
+ }
+ else if ( oArg == "print-to-file" )
+ {
+ eCurrentEvent = CommandLineEvent::BatchPrint;
+ }
+ else if ( oArg == "printer-name" )
+ {
+ if (eCurrentEvent == CommandLineEvent::BatchPrint)
+ {
+ // first argument is the printer name
+ if (supplier.next(&aArg))
+ m_printername = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--printer-name must be followed by printername";
+ }
+ else if (m_unknown.isEmpty())
+ {
+ m_unknown = "--printer-name must directly follow --print-to-file";
+ }
+ }
+ else if ( oArg == "outdir" )
+ {
+ if (eCurrentEvent == CommandLineEvent::Conversion ||
+ eCurrentEvent == CommandLineEvent::BatchPrint)
+ {
+ if (supplier.next(&aArg))
+ m_conversionout = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--outdir must be followed by output directory path";
+ }
+ else if (m_unknown.isEmpty())
+ {
+ m_unknown = "--outdir must directly follow either output filter specification of --convert-to, or --print-to-file or its printer specification";
+ }
+ }
+ else if ( eCurrentEvent == CommandLineEvent::Conversion
+ && oArg == "convert-images-to" )
+ {
+ if (supplier.next(&aArg))
+ m_convertimages = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--convert-images-to must be followed by an image type";
+ }
+ else if ( aArg.startsWith("-") )
+ {
+ // because it's impossible to filter these options that
+ // are handled in the soffice shell script with the
+ // primitive tools that /bin/sh offers, ignore them here
+ if (
+#if defined UNX
+ oArg != "record" &&
+ oArg != "backtrace" &&
+ oArg != "strace" &&
+ oArg != "valgrind" &&
+ // for X Session Management, handled in
+ // vcl/unx/generic/app/sm.cxx:
+ oArg != "session=" &&
+#endif
+ //ignore additional legacy options that don't do anything anymore
+ oArg != "nocrashreport" &&
+ m_unknown.isEmpty())
+ {
+ m_unknown = aArg;
+ }
+ oDeprecatedArg.clear();
+ }
+ else
+ {
+ // handle this argument as a filename
+
+ // First check if this is an Office URI
+ // This will possibly adjust event for this argument
+ // and put real URI to aArg
+ CommandLineEvent eThisEvent = CheckOfficeURI(aArg, eCurrentEvent);
+
+ // Now check if this is a Web Query file
+ eThisEvent = CheckWebQuery(aArg, eThisEvent);
+
+ switch (eThisEvent)
+ {
+ case CommandLineEvent::Open:
+ m_openlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::View:
+ m_viewlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Start:
+ m_startlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Print:
+ m_printlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::PrintTo:
+ m_printtolist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::ForceNew:
+ m_forcenewlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::ForceOpen:
+ m_forceopenlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Conversion:
+ case CommandLineEvent::BatchPrint:
+ m_conversionlist.push_back(aArg);
+ break;
+ }
+ }
+
+ if (!oDeprecatedArg.isEmpty())
+ {
+ OString sArg(OUStringToOString(oDeprecatedArg, osl_getThreadTextEncoding()));
+ fprintf(stderr, "Warning: %s is deprecated. Use -%s instead.\n", sArg.getStr(), sArg.getStr());
+ }
+ }
+ }
+}
+
+void CommandLineArgs::InitParamValues()
+{
+ m_minimized = false;
+ m_norestore = false;
+#if HAVE_FEATURE_UI
+ m_invisible = false;
+ m_headless = false;
+#else
+ m_invisible = true;
+ m_headless = true;
+#endif
+ m_quickstart = false;
+ m_noquickstart = false;
+ m_terminateafterinit = false;
+ m_nologo = false;
+ m_nolockcheck = false;
+ m_nodefault = false;
+ m_help = false;
+ m_writer = false;
+ m_calc = false;
+ m_draw = false;
+ m_impress = false;
+ m_global = false;
+ m_math = false;
+ m_web = false;
+ m_base = false;
+ m_helpwriter = false;
+ m_helpcalc = false;
+ m_helpdraw = false;
+ m_helpbasic = false;
+ m_helpmath = false;
+ m_helpimpress = false;
+ m_helpbase = false;
+ m_version = false;
+ m_splashpipe = false;
+ m_bEmpty = true;
+ m_bDocumentArgs = false;
+ m_textcat = false;
+ m_scriptcat = false;
+ m_safemode = false;
+}
+
+bool CommandLineArgs::HasModuleParam() const
+{
+ return m_writer || m_calc || m_draw || m_impress || m_global || m_math
+ || m_web || m_base;
+}
+
+std::vector< OUString > CommandLineArgs::GetOpenList() const
+{
+ return translateExternalUris(m_openlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetViewList() const
+{
+ return translateExternalUris(m_viewlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetStartList() const
+{
+ return translateExternalUris(m_startlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetForceOpenList() const
+{
+ return translateExternalUris(m_forceopenlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetForceNewList() const
+{
+ return translateExternalUris(m_forcenewlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetPrintList() const
+{
+ return translateExternalUris(m_printlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetPrintToList() const
+{
+ return translateExternalUris(m_printtolist);
+}
+
+std::vector< OUString > CommandLineArgs::GetConversionList() const
+{
+ return translateExternalUris(m_conversionlist);
+}
+
+OUString CommandLineArgs::GetConversionOut() const
+{
+ return translateExternalUris(m_conversionout);
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlineargs.hxx b/desktop/source/app/cmdlineargs.hxx
new file mode 100644
index 0000000000..64a1bcfd0c
--- /dev/null
+++ b/desktop/source/app/cmdlineargs.hxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vector>
+
+#include <rtl/ustring.hxx>
+#include <optional>
+
+namespace desktop
+{
+
+class CommandLineArgs
+{
+ public:
+ struct Supplier
+ {
+ // Thrown from constructors and next:
+ class Exception final
+ {
+ public:
+ Exception();
+ Exception(Exception const &);
+ Exception & operator =(Exception const &);
+ };
+
+ virtual ~Supplier();
+ virtual std::optional< OUString > getCwdUrl() = 0;
+ virtual bool next(OUString * argument) = 0;
+ };
+
+ CommandLineArgs();
+ explicit CommandLineArgs( Supplier& supplier );
+
+ CommandLineArgs(const CommandLineArgs&) = delete;
+ const CommandLineArgs& operator=(const CommandLineArgs&) = delete;
+
+ const std::optional< OUString >& getCwdUrl() const { return m_cwdUrl; }
+
+ // Access to bool parameters
+ bool IsMinimized() const { return m_minimized;}
+ bool IsInvisible() const
+ {
+ return m_invisible || m_headless;
+ }
+ bool IsNoRestore() const { return m_norestore;}
+ bool IsNoDefault() const { return m_nodefault;}
+ bool IsHeadless() const { return m_headless;}
+ bool IsQuickstart() const { return m_quickstart;}
+ bool IsNoQuickstart() const { return m_noquickstart;}
+ bool IsTerminateAfterInit() const { return m_terminateafterinit;}
+ bool IsNoLogo() const { return m_nologo;}
+ bool IsNoLockcheck() const { return m_nolockcheck;}
+ bool IsHelp() const { return m_help;}
+ bool IsHelpWriter() const { return m_helpwriter;}
+ bool IsHelpCalc() const { return m_helpcalc;}
+ bool IsHelpDraw() const { return m_helpdraw;}
+ bool IsHelpImpress() const { return m_helpimpress;}
+ bool IsHelpBase() const { return m_helpbase;}
+ bool IsHelpMath() const { return m_helpmath;}
+ bool IsHelpBasic() const { return m_helpbasic;}
+ bool IsWriter() const { return m_writer;}
+ bool IsCalc() const { return m_calc;}
+ bool IsDraw() const { return m_draw;}
+ bool IsImpress() const { return m_impress;}
+ bool IsBase() const { return m_base;}
+ bool IsGlobal() const { return m_global;}
+ bool IsMath() const { return m_math;}
+ bool IsWeb() const { return m_web;}
+ bool IsVersion() const { return m_version;}
+ bool HasModuleParam() const;
+ bool WantsToLoadDocument() const { return m_bDocumentArgs;}
+ bool IsTextCat() const { return m_textcat;}
+ bool IsScriptCat() const { return m_scriptcat;}
+ bool IsSafeMode() const { return m_safemode; }
+
+ const OUString& GetUnknown() const { return m_unknown;}
+
+ // Access to string parameters
+ bool HasSplashPipe() const { return m_splashpipe;}
+ std::vector< OUString > const & GetAccept() const { return m_accept;}
+ std::vector< OUString > const & GetUnaccept() const { return m_unaccept;}
+ std::vector< OUString > GetOpenList() const;
+ std::vector< OUString > GetViewList() const;
+ std::vector< OUString > GetStartList() const;
+ std::vector< OUString > GetForceOpenList() const;
+ std::vector< OUString > GetForceNewList() const;
+ std::vector< OUString > GetPrintList() const;
+ std::vector< OUString > GetPrintToList() const;
+ const OUString& GetPrinterName() const { return m_printername;}
+ const OUString& GetLanguage() const { return m_language;}
+ std::vector< OUString > const & GetInFilter() const { return m_infilter;}
+ std::vector< OUString > GetConversionList() const;
+ const OUString& GetConversionParams() const { return m_conversionparams;}
+ OUString GetConversionOut() const;
+ OUString const & GetImageConversionType() const { return m_convertimages; }
+ const OUString& GetPidfileName() const { return m_pidfile;}
+
+ // Special analyzed states (does not match directly to a command line parameter!)
+ bool IsEmpty() const { return m_bEmpty;}
+
+ void setHeadless() { m_headless = true; }
+
+ private:
+ void ParseCommandLine_Impl( Supplier& supplier );
+ void InitParamValues();
+
+ std::optional< OUString > m_cwdUrl;
+
+ bool m_minimized;
+ bool m_invisible;
+ bool m_norestore;
+ bool m_headless;
+ bool m_quickstart;
+ bool m_noquickstart;
+ bool m_terminateafterinit;
+ bool m_nologo;
+ bool m_nolockcheck;
+ bool m_nodefault;
+ bool m_help;
+ bool m_writer;
+ bool m_calc;
+ bool m_draw;
+ bool m_impress;
+ bool m_global;
+ bool m_math;
+ bool m_web;
+ bool m_base;
+ bool m_helpwriter;
+ bool m_helpcalc;
+ bool m_helpdraw;
+ bool m_helpbasic;
+ bool m_helpmath;
+ bool m_helpimpress;
+ bool m_helpbase;
+ bool m_version;
+ bool m_splashpipe;
+ bool m_textcat;
+ bool m_scriptcat;
+ bool m_safemode;
+
+ OUString m_unknown;
+
+ bool m_bEmpty; // No Args at all
+ bool m_bDocumentArgs; // A document creation/open/load arg is used
+ std::vector< OUString > m_accept;
+ std::vector< OUString > m_unaccept;
+ std::vector< OUString > m_openlist; // contains external URIs
+ std::vector< OUString > m_viewlist; // contains external URIs
+ std::vector< OUString > m_startlist; // contains external URIs
+ std::vector< OUString > m_forceopenlist; // contains external URIs
+ std::vector< OUString > m_forcenewlist; // contains external URIs
+ std::vector< OUString > m_printlist; // contains external URIs
+ std::vector< OUString > m_printtolist; // contains external URIs
+ OUString m_printername;
+ std::vector< OUString > m_conversionlist; // contains external URIs
+ OUString m_conversionparams;
+ OUString m_conversionout; // contains external URIs
+ OUString m_convertimages; // The format in which images should be converted
+ std::vector< OUString > m_infilter;
+ OUString m_language;
+ OUString m_pidfile;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlinehelp.cxx b/desktop/source/app/cmdlinehelp.cxx
new file mode 100644
index 0000000000..9c9fd940f2
--- /dev/null
+++ b/desktop/source/app/cmdlinehelp.cxx
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <stdio.h>
+#include <comphelper/string.hxx>
+#include <app.hxx>
+
+#include "cmdlinehelp.hxx"
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <io.h>
+#include <fcntl.h>
+#endif
+
+namespace desktop
+{
+ constexpr OUString aCmdLineHelp_version =
+ u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION %BUILDID\n"
+ "\n"_ustr;
+ constexpr OUStringLiteral aCmdLineHelp =
+ u"Usage: %CMDNAME [argument...]\n"
+ " argument - switches, switch parameters and document URIs (filenames). \n\n"
+ "Using without special arguments: \n"
+ "Opens the start center, if it is used without any arguments. \n"
+ " {file} Tries to open the file (files) in the components \n"
+ " suitable for them. \n"
+ " {file} {macro:///Library.Module.MacroName} \n"
+ " Opens the file and runs specified macro from \n"
+ " My Macros container. \n"
+ " {file} {macro://./Library.Module.MacroName} \n"
+ " Opens the file and runs specified macro from \n"
+ " the file. \n\n"
+ "Getting help and information: \n"
+ " --help | -h | -? Shows this help and quits. \n"
+ " --helpwriter Opens built-in or online Help on Writer. \n"
+ " --helpcalc Opens built-in or online Help on Calc. \n"
+ " --helpdraw Opens built-in or online Help on Draw. \n"
+ " --helpimpress Opens built-in or online Help on Impress. \n"
+ " --helpbase Opens built-in or online Help on Base. \n"
+ " --helpbasic Opens built-in or online Help on Basic scripting \n"
+ " language. \n"
+ " --helpmath Opens built-in or online Help on Math. \n"
+ " --version Shows the version and quits. \n"
+ " --nstemporarydirectory \n"
+ " (MacOS X sandbox only) Returns path of the temporary \n"
+ " directory for the current user and exits. Overrides \n"
+ " all other arguments. \n\n"
+ "General arguments: \n"
+ " --quickstart[=no] Activates[Deactivates] the Quickstarter service. \n"
+ " --nolockcheck Disables check for remote instances using one \n"
+ " installation. \n"
+ " --infilter={filter} Force an input filter type if possible. For example: \n"
+ " --infilter=\"Calc Office Open XML\" \n"
+ " --infilter=\"Text (encoded):UTF8,LF,,,\" \n"
+ " --pidfile={file} Store soffice.bin pid to {file}. \n"
+ " --display {display} Sets the DISPLAY environment variable on UNIX-like \n"
+ " platforms to the value {display} (only supported by a \n"
+ " start script). \n\n"
+ "User/programmatic interface control: \n"
+ " --nologo Disables the splash screen at program start. \n"
+ " --minimized Starts minimized. The splash screen is not displayed. \n"
+ " --nodefault Starts without displaying anything except the splash \n"
+ " screen (do not display initial window). \n"
+ " --invisible Starts in invisible mode. Neither the start-up logo nor \n"
+ " the initial program window will be visible. Application \n"
+ " can be controlled, and documents and dialogs can be \n"
+ " controlled and opened via the API. Using the parameter, \n"
+ " the process can only be ended using the taskmanager \n"
+ " (Windows) or the kill command (UNIX-like systems). It \n"
+ " cannot be used in conjunction with --quickstart. \n"
+ " --headless Starts in \"headless mode\" which allows using the \n"
+ " application without GUI. This special mode can be used \n"
+ " when the application is controlled by external clients \n"
+ " via the API. \n"
+ " --norestore Disables restart and file recovery after a system crash.\n"
+ " --safe-mode Starts in a safe mode, i.e. starts temporarily with a \n"
+ " fresh user profile and helps to restore a broken \n"
+ " configuration. \n"
+ " --accept={connect-string} Specifies a UNO connect-string to create a UNO \n"
+ " acceptor through which other programs can connect to \n"
+ " access the API. Note that API access allows execution \n"
+ " of arbitrary commands. \n"
+ " The syntax of the {connect-string} is: \n"
+ " connection-type,params;protocol-name,params \n"
+ " e.g. pipe,name={some name};urp \n"
+ " or socket,host=localhost,port=54321;urp \n"
+ " --unaccept={connect-string} Closes an acceptor that was created with \n"
+ " --accept. Use --unaccept=all to close all acceptors. \n"
+ " --language={lang} Uses specified language, if language is not selected \n"
+ " yet for UI. The lang is a tag of the language in IETF \n"
+ " language tag. \n\n"
+ "Developer arguments: \n"
+ " --terminate_after_init \n"
+ " Exit after initialization complete (no documents loaded)\n"
+ " --eventtesting Exit after loading documents. \n\n"
+ "New document creation arguments: \n"
+ "The arguments create an empty document of specified kind. Only one of them may \n"
+ "be used in one command line. If filenames are specified after an argument, \n"
+ "then it tries to open those files in the specified component. \n"
+ " --writer Creates an empty Writer document. \n"
+ " --calc Creates an empty Calc document. \n"
+ " --draw Creates an empty Draw document. \n"
+ " --impress Creates an empty Impress document. \n"
+ " --base Creates a new database. \n"
+ " --global Creates an empty Writer master (global) document. \n"
+ " --math Creates an empty Math document (formula). \n"
+ " --web Creates an empty HTML document. \n\n"
+ "File open arguments: \n"
+ "The arguments define how following filenames are treated. New treatment begins \n"
+ "after the argument and ends at the next argument. The default treatment is to \n"
+ "open documents for editing, and create new documents from document templates. \n"
+ " -n Treats following files as templates for creation of new \n"
+ " documents. \n"
+ " -o Opens following files for editing, regardless whether \n"
+ " they are templates or not. \n"
+ " --pt {Printername} Prints following files to the printer {Printername}, \n"
+ " after which those files are closed. The splash screen \n"
+ " does not appear. If used multiple times, only last \n"
+ " {Printername} is effective for all documents of all \n"
+ " --pt runs. Also, --printer-name argument of \n"
+ " --print-to-file switch interferes with {Printername}. \n"
+ " -p Prints following files to the default printer, after \n"
+ " which those files are closed. The splash screen does \n"
+ " not appear. If the file name contains spaces, then it \n"
+ " must be enclosed in quotation marks. \n"
+ " --view Opens following files in viewer mode (read-only). \n"
+ " --show Opens and starts the following presentation documents \n"
+ " of each immediately. Files are closed after the showing.\n"
+ " Files other than Impress documents are opened in \n"
+ " default mode , regardless of previous mode. \n"
+ " --convert-to OutputFileExtension[:OutputFilterName] \\ \n"
+ " [--outdir output_dir] [--convert-images-to] \n"
+ " Batch convert files (implies --headless). If --outdir \n"
+ " isn't specified, then current working directory is used \n"
+ " as output_dir. If --convert-images-to is given, its \n"
+ " parameter is taken as the target filter format for *all*\n"
+ " images written to the output format. If --convert-to is \n"
+ " used more than once, the last value of \n"
+ " OutputFileExtension[:OutputFilterName] is effective. If \n"
+ " --outdir is used more than once, only its last value is \n"
+ " effective. For example: \n"
+ " --convert-to pdf *.odt \n"
+ " --convert-to epub *.doc \n"
+ " --convert-to pdf:writer_pdf_Export --outdir /home/user *.doc\n"
+ " --convert-to \"html:XHTML Writer File:UTF8\" \\ \n"
+ " --convert-images-to \"jpg\" *.doc \n"
+ " --convert-to \"txt:Text (encoded):UTF8\" *.doc \n"
+ " --print-to-file [--printer-name printer_name] [--outdir output_dir] \n"
+ " Batch print files to file. If --outdir is not specified,\n"
+ " then current working directory is used as output_dir. \n"
+ " If --printer-name or --outdir used multiple times, only \n"
+ " last value of each is effective. Also, {Printername} of \n"
+ " --pt switch interferes with --printer-name. \n"
+ " --cat Dump text content of the following files to console \n"
+ " (implies --headless). Cannot be used with --convert-to. \n"
+ " --script-cat Dump text content of any scripts embedded in the files \n"
+ " to console (implies --headless). Cannot be used with \n"
+ " --convert-to. \n"
+ " -env:<VAR>[=<VALUE>] Set a bootstrap variable. For example: to set \n"
+ " a non-default user profile path: \n"
+ " -env:UserInstallation=file:///tmp/test \n\n"
+ "Ignored switches: \n"
+ " -psn Ignored (MacOS X only). \n"
+ " -Embedding Ignored (COM+ related; Windows only). \n"
+ " --nofirststartwizard Does nothing, accepted only for backward compatibility.\n"
+ " --protector {arg1} {arg2} \n"
+ " Used only in unit tests and should have two arguments. \n\n";
+#ifdef _WIN32
+ namespace{
+ // This class is only used to create a console when soffice.bin is run without own console
+ // (like using soffice.exe launcher as opposed to soffice.com), and either --version or
+ // --help command line options were specified, or an error in a command line option was
+ // detected, which requires to output strings to user.
+ class lcl_Console {
+ public:
+ explicit lcl_Console(short nBufHeight)
+ : m_bOwnConsole(AllocConsole() != FALSE)
+ {
+ if (m_bOwnConsole)
+ {
+ HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ // Ensure that console buffer is enough to hold required data
+ CONSOLE_SCREEN_BUFFER_INFO cinfo;
+ GetConsoleScreenBufferInfo(hOut, &cinfo);
+ if (cinfo.dwSize.Y < nBufHeight)
+ {
+ cinfo.dwSize.Y = nBufHeight;
+ SetConsoleScreenBufferSize(hOut, cinfo.dwSize);
+ }
+
+ (void)freopen("CON", "r", stdin);
+ (void)freopen("CON", "w", stdout);
+ (void)freopen("CON", "w", stderr);
+
+ std::ios::sync_with_stdio(true);
+ }
+ }
+
+ ~lcl_Console()
+ {
+ if (m_bOwnConsole)
+ {
+ fflush(stdout);
+ fprintf(stdout, "Press Enter to continue...");
+ fgetc(stdin);
+ FreeConsole();
+ }
+ }
+ private:
+ bool m_bOwnConsole;
+ };
+ }
+#endif
+
+ void displayCmdlineHelp(OUString const & unknown)
+ {
+ OUString aHelpMessage_version = ReplaceStringHookProc(aCmdLineHelp_version);
+ OUString aHelpMessage(OUString(aCmdLineHelp).replaceFirst("%CMDNAME", "soffice"));
+ if (!unknown.isEmpty())
+ {
+ aHelpMessage = "Error in option: " + unknown + "\n\n"
+ + aHelpMessage;
+ }
+#ifdef _WIN32
+ sal_Int32 n = comphelper::string::getTokenCount(aHelpMessage, '\n');
+ lcl_Console aConsole(short(n*2));
+#endif
+ fprintf(stdout, "%s%s",
+ OUStringToOString(aHelpMessage_version, RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString(aHelpMessage, RTL_TEXTENCODING_ASCII_US).getStr());
+ }
+
+ void displayVersion()
+ {
+ OUString aVersionMsg(aCmdLineHelp_version);
+ aVersionMsg = ReplaceStringHookProc(aVersionMsg);
+#ifdef _WIN32
+ lcl_Console aConsole(short(10));
+#endif
+ fprintf(stdout, "%s", OUStringToOString(aVersionMsg, RTL_TEXTENCODING_ASCII_US).getStr());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlinehelp.hxx b/desktop/source/app/cmdlinehelp.hxx
new file mode 100644
index 0000000000..5a32125053
--- /dev/null
+++ b/desktop/source/app/cmdlinehelp.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace desktop
+{
+void displayCmdlineHelp(OUString const& unknown);
+void displayVersion();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/crashreport.cxx b/desktop/source/app/crashreport.cxx
new file mode 100644
index 0000000000..b90a04907f
--- /dev/null
+++ b/desktop/source/app/crashreport.cxx
@@ -0,0 +1,477 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <desktop/crashreport.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/proxydecider.hxx>
+#include <unotools/bootstrap.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <desktop/minidump.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <config_version.h>
+#include <config_folders.h>
+
+#include <string>
+#include <regex>
+
+
+#if HAVE_FEATURE_BREAKPAD
+
+#include <fstream>
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+#include <client/linux/handler/exception_handler.h>
+#elif defined _WIN32
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
+#endif
+#include <client/windows/handler/exception_handler.h>
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+#include <locale>
+#include <codecvt>
+#endif
+
+osl::Mutex CrashReporter::maMutex;
+osl::Mutex CrashReporter::maActiveSfxObjectNameMutex;
+osl::Mutex CrashReporter::maUnoLogCmdMutex;
+std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
+bool CrashReporter::mbInit = false;
+CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
+CrashReporter::vmaloggedUnoCommands CrashReporter::maloggedUnoCommands;
+OUString CrashReporter::msActiveSfxObjectName;
+
+
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
+{
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << descriptor.path());
+
+ return succeeded;
+}
+#elif defined _WIN32
+static bool dumpCallback(const wchar_t* path, const wchar_t* id,
+ void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
+ MDRawAssertionInfo* /*assertion*/,
+ bool succeeded)
+{
+ // TODO: moggi: can we avoid this conversion
+#ifdef _MSC_VER
+#pragma warning (disable: 4996)
+#endif
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
+ std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath, RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
+ CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << aPath);
+ return succeeded;
+}
+#endif
+
+
+void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
+{
+#if defined _WIN32
+ const std::string iniPath = getIniFileName();
+ std::wstring iniPathW;
+ const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
+ auto buf = std::make_unique<wchar_t[]>(nChars);
+ if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
+ iniPathW = buf.get();
+
+ std::ofstream ini_file
+ = iniPathW.empty() ? std::ofstream(iniPath, Openmode) : std::ofstream(iniPathW, Openmode);
+#else
+ std::ofstream ini_file(getIniFileName(), Openmode);
+#endif
+
+ for (auto& keyValue : maKeyValues)
+ {
+ ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
+ ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
+ }
+
+ maKeyValues.clear();
+ ini_file.close();
+}
+
+void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
+{
+ osl::MutexGuard aGuard(maMutex);
+
+ if (IsDumpEnable())
+ {
+ if (!rKey.isEmpty())
+ maKeyValues.push_back(mpair(rKey, rValue));
+
+ if (AddKeyHandling != AddItem)
+ {
+ if (mbInit)
+ writeToFile(std::ios_base::app);
+ else if (AddKeyHandling == Create)
+ writeCommonInfo();
+ }
+ }
+}
+
+void CrashReporter::writeCommonInfo()
+{
+ writeSystemInfo();
+
+ ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
+
+ static constexpr OUString protocol = u"https"_ustr;
+ static constexpr OUString url = u"crashreport.libreoffice.org"_ustr;
+ const sal_Int32 port = 443;
+
+ const OUString proxy_server = proxy_decider.getProxy(protocol, url, port);
+
+ // save the new Keys
+ vmaKeyValues atlast = maKeyValues;
+ // clear the keys, the following Keys should be at the begin
+ maKeyValues.clear();
+
+ // limit the amount of code that needs to be executed before the crash reporting
+ addKeyValue("ProductName", "LibreOffice", AddItem);
+ addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
+ addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem);
+ addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
+
+ if (!proxy_server.isEmpty())
+ {
+ addKeyValue("Proxy", proxy_server, AddItem);
+ }
+
+ // write the new keys at the end
+ maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
+
+ mbInit = true;
+
+ writeToFile(std::ios_base::trunc);
+
+ updateMinidumpLocation();
+}
+
+void CrashReporter::setActiveSfxObjectName(const OUString& rActiveSfxObjectName)
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ msActiveSfxObjectName = rActiveSfxObjectName;
+}
+
+OUString CrashReporter::getActiveSfxObjectName()
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ return msActiveSfxObjectName;
+}
+
+void CrashReporter::logUnoCommand(const OUString& rUnoCommand)
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ if( maloggedUnoCommands.size() == 4 )
+ maloggedUnoCommands.pop_front();
+
+ maloggedUnoCommands.push_back(rUnoCommand);
+}
+
+OUString CrashReporter::getLoggedUnoCommands()
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ OUString aCommandSeperator="";
+ OUStringBuffer aUnoCommandBuffer;
+
+ for( auto& unocommand: maloggedUnoCommands)
+ {
+ aUnoCommandBuffer.append(aCommandSeperator + unocommand);
+ aCommandSeperator=",";
+ }
+ return aUnoCommandBuffer.makeStringAndClear();
+}
+
+namespace {
+
+OUString getCrashDirectory()
+{
+ OUString aCrashURL;
+ rtl::Bootstrap::get("CrashDirectory", aCrashURL);
+ // Need to convert to URL in case of user-defined path
+ osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
+
+ if (aCrashURL.isEmpty()) { // Fall back to user profile
+ aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
+ rtl::Bootstrap::expandMacros(aCrashURL);
+ }
+
+ if (!aCrashURL.endsWith("/"))
+ aCrashURL += "/";
+
+ osl::Directory::create(aCrashURL);
+ OUString aCrashPath;
+ osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
+ return aCrashPath;
+}
+
+}
+
+void CrashReporter::updateMinidumpLocation()
+{
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ OUString aURL = getCrashDirectory();
+ OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
+ google_breakpad::MinidumpDescriptor descriptor(std::string{aOStringUrl});
+ mpExceptionHandler->set_minidump_descriptor(descriptor);
+#elif defined _WIN32
+ OUString aURL = getCrashDirectory();
+ mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
+#endif
+}
+
+bool CrashReporter::crashReportInfoExists()
+{
+ static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
+ return InfoExist;
+}
+
+bool CrashReporter::readSendConfig(std::string& response)
+{
+ return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
+}
+
+void CrashReporter::installExceptionHandler()
+{
+ if (!IsDumpEnable())
+ return;
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ google_breakpad::MinidumpDescriptor descriptor("/tmp");
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
+#elif defined _WIN32
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
+#endif
+}
+
+void CrashReporter::removeExceptionHandler()
+{
+ mpExceptionHandler.reset();
+}
+
+
+
+bool CrashReporter::IsDumpEnable()
+{
+ auto const env = std::getenv("CRASH_DUMP_ENABLE");
+ if (env != nullptr && env[0] != '\0') {
+ return true;
+ }
+ // read configuration item 'CrashDumpEnable' -> bool on/off
+ OUString sToken;
+ if (rtl::Bootstrap::get("CrashDumpEnable", sToken))
+ {
+ return sToken.toBoolean();
+ }
+ return true; // default, always on
+}
+
+
+std::string CrashReporter::getIniFileName()
+{
+ OUString url = getCrashDirectory() + "dump.ini";
+ OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ std::string aRet(aUrl);
+ return aRet;
+}
+
+// Write system-specific information such as the CPU name and features.
+// This may allow us to get some statistics for decisions (such as when
+// deciding whether SSE2 can be made a hard-requirement for Windows).
+// Breakpad provides this information poorly or not at all.
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+void CrashReporter::writeSystemInfo()
+{
+ // Get 'model name' and 'flags' from /proc/cpuinfo.
+ if( std::ifstream cpuinfo( "/proc/cpuinfo" ); cpuinfo )
+ {
+ bool haveModel = false;
+ bool haveFlags = false;
+ std::regex modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
+ std::regex flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( cpuinfo, line ); )
+ {
+ std::smatch match;
+ if( !haveModel && std::regex_match( line, match, modelRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUModelName", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveModel = true;
+ }
+ if( !haveFlags && std::regex_match( line, match, flagsRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUFlags", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveFlags = true;
+ }
+ if( haveModel && haveFlags )
+ break;
+ }
+ }
+ // Get 'MemTotal' from /proc/meminfo.
+ if( std::ifstream meminfo( "/proc/meminfo" ); meminfo )
+ {
+ std::regex memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( meminfo, line ); )
+ {
+ std::smatch match;
+ if( std::regex_match( line, match, memTotalRegex ) && match.size() == 2)
+ {
+ addKeyValue("MemoryTotal", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ break;
+ }
+ }
+ }
+}
+#elif defined _WIN32
+void CrashReporter::writeSystemInfo()
+{
+#if !defined(_ARM64_)
+ // Get CPU model name and flags.
+ // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+ // and https://en.wikipedia.org/wiki/CPUID .
+ int cpui[ 4 ];
+ __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
+ unsigned int exIds = cpui[ 0 ];
+ if( exIds >= 0x80000004 )
+ {
+ int brand[ 16 ];
+ __cpuidex( brand, 0x80000002, 0 );
+ __cpuidex( brand + 4, 0x80000003, 0 );
+ __cpuidex( brand + 8, 0x80000004, 0 );
+ brand[ 12 ] = 0;;
+ addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
+ AddItem );
+ }
+ __cpuid( cpui, 0 ); // Get the highest ID.
+ int ids = cpui[ 0 ];
+ unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
+ if( ids >= 0x1 )
+ {
+ __cpuidex( cpui, 0x1, 0 );
+ ecx1 = cpui[ 2 ];
+ edx1 = cpui[ 3 ];
+ }
+ if( ids >= 0x7 )
+ {
+ __cpuidex( cpui, 0x7, 0 );
+ ebx7 = cpui[ 1 ];
+ ecx7 = cpui[ 2 ];
+ }
+ if( exIds >= 0x80000001 )
+ {
+ __cpuidex( cpui, 0x80000001, 0 );
+ ecx81 = cpui[ 2 ];
+ edx81 = cpui[ 3 ];
+ }
+ struct FlagItem
+ {
+ unsigned int* reg;
+ int bit;
+ const char* name;
+ };
+ const FlagItem flagItems[] =
+ {
+ { &ecx1, 0, "sse3" },
+ { &ecx1, 1, "pclmulqdq" },
+ { &ecx1, 3, "monitor" },
+ { &ecx1, 9, "ssse3" },
+ { &ecx1, 12, "fma" },
+ { &ecx1, 13, "cpmxch16b" },
+ { &ecx1, 19, "sse41" },
+ { &ecx1, 20, "sse42" },
+ { &ecx1, 22, "movbe" },
+ { &ecx1, 23, "popcnt" },
+ { &ecx1, 25, "aes" },
+ { &ecx1, 26, "xsave" },
+ { &ecx1, 27, "osxsave" },
+ { &ecx1, 28, "avx" },
+ { &ecx1, 29, "f16c" },
+ { &ecx1, 30, "rdrand" },
+ { &edx1, 5, "msr" },
+ { &edx1, 8, "cx8" },
+ { &edx1, 11, "sep" },
+ { &edx1, 15, "cmov" },
+ { &edx1, 19, "clfsh" },
+ { &edx1, 23, "mmx" },
+ { &edx1, 24, "fxsr" },
+ { &edx1, 25, "sse" },
+ { &edx1, 26, "sse2" },
+ { &edx1, 28, "ht" },
+ { &ebx7, 0, "fsgsbase" },
+ { &ebx7, 3, "bmi1" },
+ { &ebx7, 4, "hle" },
+ { &ebx7, 5, "avx2" },
+ { &ebx7, 8, "bmi2" },
+ { &ebx7, 9, "erms" },
+ { &ebx7, 10, "invpcid" },
+ { &ebx7, 11, "rtm" },
+ { &ebx7, 16, "avx512f" },
+ { &ebx7, 18, "rdseed" },
+ { &ebx7, 19, "adx" },
+ { &ebx7, 26, "avx512pf" },
+ { &ebx7, 27, "avx512er" },
+ { &ebx7, 28, "avx512cd" },
+ { &ebx7, 29, "sha" },
+ { &ecx7, 0, "prefetchwt1" },
+ { &ecx81, 0, "lahf" },
+ { &ecx81, 5, "abm" },
+ { &ecx81, 6, "sse4a" },
+ { &ecx81, 11, "xop" },
+ { &ecx81, 21, "tbm" },
+ { &edx81, 11, "syscall" },
+ { &edx81, 22, "mmxext" },
+ { &edx81, 27, "rdtscp" },
+ { &edx81, 30, "3dnowext" },
+ { &edx81, 31, "3dnow" }
+ };
+ OUStringBuffer flags;
+ for( const FlagItem& item : flagItems )
+ {
+ if( *item.reg & ( 1U << item.bit ))
+ {
+ if( !flags.isEmpty())
+ flags.append( " " );
+ flags.appendAscii( item.name );
+ }
+ }
+ if( !flags.isEmpty())
+ addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
+#endif
+ // Get total memory.
+ MEMORYSTATUSEX memoryStatus;
+ memoryStatus.dwLength = sizeof( memoryStatus );
+ if( GlobalMemoryStatusEx( &memoryStatus ))
+ {
+ addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
+ + " kB", AddItem );
+ }
+}
+#else
+void CrashReporter::writeSystemInfo()
+{
+}
+#endif
+
+#endif //HAVE_FEATURE_BREAKPAD
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/desktopcontext.cxx b/desktop/source/app/desktopcontext.cxx
new file mode 100644
index 0000000000..5d43bb6489
--- /dev/null
+++ b/desktop/source/app/desktopcontext.cxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_java.h>
+
+#include "desktopcontext.hxx"
+
+#include <svtools/javainteractionhandler.hxx>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::task;
+
+namespace desktop
+{
+DesktopContext::DesktopContext(const Reference<XCurrentContext>& ctx)
+ : m_xNextContext(ctx)
+{
+}
+
+Any SAL_CALL DesktopContext::getValueByName(const OUString& Name)
+{
+ Any retVal;
+
+ if (Name == JAVA_INTERACTION_HANDLER_NAME)
+ {
+#if HAVE_FEATURE_JAVA
+ retVal <<= Reference<XInteractionHandler>(new svt::JavaInteractionHandler());
+#endif
+ }
+ else if (m_xNextContext.is())
+ {
+ // Call next context in chain if found
+ retVal = m_xNextContext->getValueByName(Name);
+ }
+ return retVal;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/desktopcontext.hxx b/desktop/source/app/desktopcontext.hxx
new file mode 100644
index 0000000000..42266a0725
--- /dev/null
+++ b/desktop/source/app/desktopcontext.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/uno/XCurrentContext.hpp>
+
+namespace desktop
+{
+ class DesktopContext: public cppu::WeakImplHelper< css::uno::XCurrentContext >
+ {
+ public:
+ explicit DesktopContext( const css::uno::Reference< css::uno::XCurrentContext > & ctx);
+
+ // XCurrentContext
+ virtual css::uno::Any SAL_CALL getValueByName( const OUString& Name ) override;
+
+ private:
+ css::uno::Reference< css::uno::XCurrentContext > m_xNextContext;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/dispatchwatcher.cxx b/desktop/source/app/dispatchwatcher.cxx
new file mode 100644
index 0000000000..863d246951
--- /dev/null
+++ b/desktop/source/app/dispatchwatcher.cxx
@@ -0,0 +1,863 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <svl/fstathelper.hxx>
+
+#include <app.hxx>
+#include "dispatchwatcher.hxx"
+#include "officeipcthread.hxx"
+#include <rtl/ustring.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/synchronousdispatch.hxx>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/container/XContainerQuery.hpp>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XNotifyingDispatch.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/view/XPrintable.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/document/XTypeDetection.hpp>
+#include <com/sun/star/document/UpdateDocMode.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/script/XLibraryContainer2.hpp>
+#include <com/sun/star/document/XEmbeddedScripts.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+
+#include <osl/thread.hxx>
+#include <osl/file.hxx>
+#include <iostream>
+#include <string_view>
+#include <utility>
+
+using namespace ::osl;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::view;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::document;
+
+namespace document = ::com::sun::star::document;
+
+namespace desktop
+{
+
+namespace {
+
+struct DispatchHolder
+{
+ DispatchHolder( URL _aURL, Reference< XDispatch > const & rDispatch ) :
+ aURL(std::move( _aURL )), xDispatch( rDispatch ) {}
+
+ URL aURL;
+ Reference< XDispatch > xDispatch;
+};
+
+std::shared_ptr<const SfxFilter> impl_lookupExportFilterForUrl( std::u16string_view rUrl, std::u16string_view rFactory )
+{
+ // create the list of filters
+ OUString sQuery = "getSortedFilterList()"
+ ":module=" +
+ OUString::Concat(rFactory) + // use long name here !
+ ":iflags=" +
+ OUString::number(static_cast<sal_Int32>(SfxFilterFlags::EXPORT)) +
+ ":eflags=" +
+ OUString::number(static_cast<int>(SFX_FILTER_NOTINSTALLED));
+
+ const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ const Reference< XContainerQuery > xFilterFactory(
+ xContext->getServiceManager()->createInstanceWithContext( "com.sun.star.document.FilterFactory", xContext ),
+ UNO_QUERY_THROW );
+
+ std::shared_ptr<const SfxFilter> pBestMatch;
+
+ const Reference< XEnumeration > xFilterEnum(
+ xFilterFactory->createSubSetEnumerationByQuery( sQuery ), UNO_SET_THROW );
+ while ( xFilterEnum->hasMoreElements() )
+ {
+ comphelper::SequenceAsHashMap aFilterProps( xFilterEnum->nextElement() );
+ const OUString aName( aFilterProps.getUnpackedValueOrDefault( "Name", OUString() ) );
+ if ( !aName.isEmpty() )
+ {
+ std::shared_ptr<const SfxFilter> pFilter( SfxFilter::GetFilterByName( aName ) );
+ if ( pFilter && pFilter->CanExport() && pFilter->GetWildcard().Matches( rUrl ) )
+ {
+ if ( !pBestMatch || ( SfxFilterFlags::PREFERED & pFilter->GetFilterFlags() ) )
+ pBestMatch = pFilter;
+ }
+ }
+ }
+
+ return pBestMatch;
+}
+
+std::shared_ptr<const SfxFilter> impl_getExportFilterFromUrl(
+ const OUString& rUrl, const OUString& rFactory)
+{
+ try
+ {
+ const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ const Reference< document::XTypeDetection > xTypeDetector(
+ xContext->getServiceManager()->createInstanceWithContext( "com.sun.star.document.TypeDetection", xContext ),
+ UNO_QUERY_THROW );
+ const OUString aTypeName( xTypeDetector->queryTypeByURL( rUrl ) );
+
+ std::shared_ptr<const SfxFilter> pFilter( SfxFilterMatcher( rFactory ).GetFilter4EA( aTypeName, SfxFilterFlags::EXPORT ) );
+ if ( !pFilter )
+ pFilter = impl_lookupExportFilterForUrl( rUrl, rFactory );
+ if ( !pFilter )
+ {
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL( rUrl, aTempName );
+ OString aSource = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
+ std::cerr << "Error: no export filter for " << aSource << " found, aborting." << std::endl;
+ }
+
+ return pFilter;
+ }
+ catch ( const Exception& )
+ {
+ return nullptr;
+ }
+}
+
+OUString impl_GuessFilter( const OUString& rUrlOut, const OUString& rDocService )
+{
+ OUString aOutFilter;
+ std::shared_ptr<const SfxFilter> pOutFilter = impl_getExportFilterFromUrl( rUrlOut, rDocService );
+ if (pOutFilter)
+ aOutFilter = pOutFilter->GetFilterName();
+
+ return aOutFilter;
+}
+
+/// dump scripts in a document to the console.
+void scriptCat(const Reference< XModel >& xDoc )
+{
+ Reference< XEmbeddedScripts > xScriptAccess( xDoc, UNO_QUERY );
+ if (!xScriptAccess)
+ {
+ std::cout << "No script access\n";
+ return;
+ }
+
+ // ignore xScriptAccess->getDialogLibraries() for now
+ Reference< css::script::XLibraryContainer2 > xLibraries(
+ xScriptAccess->getBasicLibraries() );
+
+ if ( !xLibraries.is() )
+ {
+ std::cout << "No script libraries\n";
+ return;
+ }
+
+ const Sequence< OUString > aLibNames = xLibraries->getElementNames();
+ std::cout << "Libraries: " << aLibNames.getLength() << "\n";
+ for (OUString const & libName : aLibNames)
+ {
+ std::cout << "Library: '" << libName << "' children: ";
+ Reference< XNameContainer > xContainer;
+ try {
+ if (!xLibraries->isLibraryLoaded( libName ))
+ xLibraries->loadLibrary( libName );
+ xContainer = Reference< XNameContainer >(
+ xLibraries->getByName( libName ), UNO_QUERY );
+ }
+ catch (const css::uno::Exception &e)
+ {
+ std::cout << "[" << libName << "] - failed to load library: " << e.Message << "\n";
+ continue;
+ }
+ if( !xContainer.is() )
+ std::cout << "0\n";
+ else
+ {
+ Sequence< OUString > aObjectNames = xContainer->getElementNames();
+
+ std::cout << aObjectNames.getLength() << "\n\n";
+ for ( sal_Int32 j = 0 ; j < aObjectNames.getLength() ; ++j )
+ {
+ const OUString &rObjectName = aObjectNames[j];
+
+ try
+ {
+ Any aCode = xContainer->getByName( rObjectName );
+ OUString aCodeString;
+
+ if (! (aCode >>= aCodeString ) )
+ std::cout << "[" << rObjectName << "] - error fetching code\n";
+ else
+ std::cout << "[" << rObjectName << "]\n"
+ << aCodeString.trim()
+ << "\n[/" << rObjectName << "]\n";
+ }
+ catch (const css::uno::Exception &e)
+ {
+ std::cout << "[" << rObjectName << "] - exception " << e.Message << " fetching code\n";
+ }
+
+ if (j < aObjectNames.getLength() - 1)
+ std::cout << "\n----------------------------------------------------------\n";
+ std::cout << "\n";
+ }
+ }
+ }
+}
+
+// Perform batch print
+void batchPrint( std::u16string_view rPrinterName, const Reference< XPrintable > &xDoc,
+ const INetURLObject &aObj, const OUString &aName )
+{
+ OUString aFilterOut;
+ OUString aPrinterName;
+ size_t nPathIndex = rPrinterName.rfind( ';' );
+ if( nPathIndex != std::u16string_view::npos )
+ aFilterOut=rPrinterName.substr( nPathIndex+1 );
+ if( nPathIndex != 0 )
+ aPrinterName=rPrinterName.substr( 0, nPathIndex );
+
+ INetURLObject aOutFilename( aObj );
+ aOutFilename.SetExtension( u"pdf" );
+ FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
+ OUString aOutFile = aFilterOut + "/" + aOutFilename.getName();
+
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL( aName, aTempName );
+ OString aSource8 = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
+ FileBase::getSystemPathFromFileURL( aOutFile, aTempName );
+ OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding() );
+
+ std::cout << "print " << aSource8 << " -> " << aTargetURL8;
+ std::cout << " using " << (aPrinterName.isEmpty() ? "<default_printer>"_ostr : OUStringToOString( aPrinterName, osl_getThreadTextEncoding() ));
+ std::cout << std::endl;
+
+ // create the custom printer, if given
+ Sequence < PropertyValue > aPrinterArgs;
+ if( !aPrinterName.isEmpty() )
+ {
+ aPrinterArgs = { comphelper::makePropertyValue("Name", aPrinterName) };
+ xDoc->setPrinter( aPrinterArgs );
+ }
+
+ // print ( also without user interaction )
+ aPrinterArgs = { comphelper::makePropertyValue("FileName", aOutFile),
+ comphelper::makePropertyValue("Wait", true) };
+ xDoc->print( aPrinterArgs );
+}
+
+// Get xDoc module name
+OUString getName(const Reference< XInterface > & xDoc)
+{
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if (!xModel)
+ return OUString();
+ utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
+ OUString aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
+ if (aDocService == "com.sun.star.text.TextDocument")
+ return "Writer";
+ else if (aDocService == "com.sun.star.text.GlobalDocument")
+ return "Writer master";
+ else if (aDocService == "com.sun.star.text.WebDocument")
+ return "Writer/Web";
+ else if (aDocService == "com.sun.star.drawing.DrawingDocument")
+ return "Draw";
+ else if (aDocService == "com.sun.star.presentation.PresentationDocument")
+ return "Impress";
+ else if (aDocService == "com.sun.star.sheet.SpreadsheetDocument")
+ return "Calc";
+ else if (aDocService == "com.sun.star.script.BasicIDE")
+ return "Basic";
+ else if (aDocService == "com.sun.star.formula.FormulaProperties")
+ return "Math";
+ else if (aDocService == "com.sun.star.sdb.RelationDesign")
+ return "Relation Design";
+ else if (aDocService == "com.sun.star.sdb.QueryDesign")
+ return "Query Design";
+ else if (aDocService == "com.sun.star.sdb.TableDesign")
+ return "Table Design";
+ else if (aDocService == "com.sun.star.sdb.DataSourceBrowser")
+ return "Data Source Browser";
+ else if (aDocService == "com.sun.star.sdb.DatabaseDocument")
+ return "Database";
+
+ return OUString();
+}
+
+} // anonymous namespace
+
+DispatchWatcher::DispatchWatcher()
+ : m_nRequestCount(0)
+{
+}
+
+
+DispatchWatcher::~DispatchWatcher()
+{
+}
+
+
+bool DispatchWatcher::executeDispatchRequests( const std::vector<DispatchRequest>& aDispatchRequestsList, bool bNoTerminate )
+{
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+
+ std::vector< DispatchHolder > aDispatches;
+ bool bSetInputFilter = false;
+ OUString aForcedInputFilter;
+
+ for (auto const & aDispatchRequest: aDispatchRequestsList)
+ {
+ // Set Input Filter
+ if ( aDispatchRequest.aRequestType == REQUEST_INFILTER )
+ {
+ bSetInputFilter = true;
+ aForcedInputFilter = aDispatchRequest.aURL;
+ RequestHandler::RequestsCompleted();
+ continue;
+ }
+
+ // create parameter array
+ std::vector<PropertyValue> aArgs;
+
+ // mark request as user interaction from outside
+ aArgs.emplace_back("Referer", 0, Any(OUString("private:OpenEvent")),
+ PropertyState_DIRECT_VALUE);
+
+ OUString aTarget("_default");
+
+ if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
+ aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
+ aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
+ aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT ||
+ aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT)
+ {
+ // documents opened for printing are opened readonly because they must be opened as a
+ // new document and this document could be open already
+ aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // always open a new document for printing, because it must be disposed afterwards
+ aArgs.emplace_back("OpenNewView", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // printing is done in a hidden view
+ aArgs.emplace_back("Hidden", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // load document for printing without user interaction
+ aArgs.emplace_back("Silent", 0, Any(true), PropertyState_DIRECT_VALUE);
+
+ // hidden documents should never be put into open tasks
+ aTarget = "_blank";
+ }
+ else
+ {
+ Reference < XInteractionHandler2 > xInteraction(
+ InteractionHandler::createWithParent(::comphelper::getProcessComponentContext(), nullptr) );
+
+ aArgs.emplace_back("InteractionHandler", 0, Any(xInteraction),
+ PropertyState_DIRECT_VALUE);
+
+ aArgs.emplace_back("MacroExecutionMode", 0,
+ Any(css::document::MacroExecMode::USE_CONFIG),
+ PropertyState_DIRECT_VALUE);
+
+ aArgs.emplace_back("UpdateDocMode", 0,
+ Any(css::document::UpdateDocMode::ACCORDING_TO_CONFIG),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ if ( !aDispatchRequest.aPreselectedFactory.isEmpty() )
+ {
+ aArgs.emplace_back(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, 0,
+ Any(aDispatchRequest.aPreselectedFactory),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ OUString aName( GetURL_Impl( aDispatchRequest.aURL, aDispatchRequest.aCwdUrl ) );
+
+ // load the document ... if they are loadable!
+ // Otherwise try to dispatch it ...
+ Reference < XPrintable > xDoc;
+ if(
+ ( aName.startsWith( ".uno" ) ) ||
+ ( aName.startsWith( "slot:" ) ) ||
+ ( aName.startsWith( "macro:" ) ) ||
+ ( aName.startsWith("vnd.sun.star.script") )
+ )
+ {
+ // Attention: URL must be parsed full. Otherwise some detections on it will fail!
+ // It doesn't matter, if parser isn't available. Because; We try loading of URL then ...
+ URL aURL ;
+ aURL.Complete = aName;
+
+ Reference < XDispatch > xDispatcher ;
+ Reference < XURLTransformer > xParser ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
+
+ if( xParser.is() )
+ xParser->parseStrict( aURL );
+
+ xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
+ SAL_WARN_IF(
+ !xDispatcher.is(), "desktop.app",
+ "unsupported dispatch request <" << aName << ">");
+ if( xDispatcher.is() )
+ {
+ // Remember request so we can find it in statusChanged!
+ m_nRequestCount++;
+
+ // Use local vector to store dispatcher because we have to fill our request container before
+ // we can dispatch. Otherwise it would be possible that statusChanged is called before we dispatched all requests!!
+ aDispatches.emplace_back( aURL, xDispatcher );
+ }
+ }
+ else if ( aName.startsWith( "service:" ) )
+ {
+ // TODO: the dispatch has to be done for loadComponentFromURL as well.
+ URL aURL ;
+ aURL.Complete = aName;
+
+ Reference < XDispatch > xDispatcher ;
+ Reference < XURLTransformer > xParser ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
+
+ if( xParser.is() )
+ xParser->parseStrict( aURL );
+
+ xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
+
+ if( xDispatcher.is() )
+ {
+ try
+ {
+ // We have to be listener to catch errors during dispatching URLs.
+ // Otherwise it would be possible to have an office running without an open
+ // window!!
+ Sequence < PropertyValue > aArgs2{ comphelper::makePropertyValue("SynchronMode",
+ true) };
+ Reference < XNotifyingDispatch > xDisp( xDispatcher, UNO_QUERY );
+ if ( xDisp.is() )
+ xDisp->dispatchWithNotification( aURL, aArgs2, this );
+ else
+ xDispatcher->dispatch( aURL, aArgs2 );
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Desktop::OpenDefault() ignoring Exception while calling XNotifyingDispatch");
+ }
+ }
+ }
+ else
+ {
+ INetURLObject aObj( aName );
+ if ( aObj.GetProtocol() == INetProtocol::PrivSoffice )
+ aTarget = "_default";
+
+ // Set "AsTemplate" argument according to request type
+ if ( aDispatchRequest.aRequestType == REQUEST_FORCENEW ||
+ aDispatchRequest.aRequestType == REQUEST_FORCEOPEN )
+ {
+ aArgs.emplace_back("AsTemplate", 0,
+ Any(aDispatchRequest.aRequestType == REQUEST_FORCENEW),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ // if we are called in viewmode, open document read-only
+ if(aDispatchRequest.aRequestType == REQUEST_VIEW) {
+ aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
+ }
+
+ // if we are called with --show set Start in mediadescriptor
+ if(aDispatchRequest.aRequestType == REQUEST_START) {
+ aArgs.emplace_back("StartPresentation", 0, Any(true), PropertyState_DIRECT_VALUE);
+ }
+
+ // Force input filter, if possible
+ if( bSetInputFilter )
+ {
+ sal_Int32 nFilterOptionsIndex = 0;
+ aArgs.emplace_back("FilterName", 0,
+ Any(aForcedInputFilter.getToken(0, ':', nFilterOptionsIndex)),
+ PropertyState_DIRECT_VALUE);
+
+ if (0 < nFilterOptionsIndex)
+ {
+ aArgs.emplace_back("FilterOptions", 0,
+ Any(aForcedInputFilter.copy(nFilterOptionsIndex)),
+ PropertyState_DIRECT_VALUE);
+ }
+ }
+
+ // This is a synchron loading of a component so we don't have to deal with our statusChanged listener mechanism.
+ try
+ {
+ xDoc.set(comphelper::SynchronousDispatch::dispatch(
+ xDesktop, aName, aTarget, comphelper::containerToSequence(aArgs)),
+ UNO_QUERY);
+ }
+ catch (const css::lang::IllegalArgumentException&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Dispatchwatcher IllegalArgumentException while calling loadComponentFromURL");
+ }
+ catch (const css::io::IOException&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Dispatchwatcher IOException while calling loadComponentFromURL");
+ }
+ if ( aDispatchRequest.aRequestType == REQUEST_OPEN ||
+ aDispatchRequest.aRequestType == REQUEST_VIEW ||
+ aDispatchRequest.aRequestType == REQUEST_START ||
+ aDispatchRequest.aRequestType == REQUEST_FORCEOPEN ||
+ aDispatchRequest.aRequestType == REQUEST_FORCENEW )
+ {
+ // request is completed
+ RequestHandler::RequestsCompleted();
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
+ aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
+ aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
+ aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT ||
+ aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
+ {
+ if ( xDoc.is() )
+ {
+ // Do we need to save the document in a different format?
+ if ( aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT )
+ {
+// FIXME: factor out into a method ...
+ Reference< XStorable > xStorable( xDoc, UNO_QUERY );
+ if ( xStorable.is() ) {
+ OUString aParam = aDispatchRequest.aPrinterName;
+ sal_Int32 nPathIndex = aParam.lastIndexOf( ';' );
+ sal_Int32 nFilterIndex = aParam.indexOf( ':' );
+ sal_Int32 nImgFilterIndex = aParam.lastIndexOf( '|' );
+ if( nPathIndex < nFilterIndex )
+ nFilterIndex = -1;
+
+ OUString aFilterOut;
+ OUString aImgOut;
+ OUString aFilter;
+ OUString aFilterExt;
+ bool bGuess = false;
+
+ if( nFilterIndex >= 0 )
+ {
+ aFilter = aParam.copy( nFilterIndex+1, nPathIndex-nFilterIndex-1 );
+ aFilterExt = aParam.copy( 0, nFilterIndex );
+ }
+ else
+ {
+ // Guess
+ bGuess = true;
+ aFilterExt = aParam.copy( 0, nPathIndex );
+ }
+
+ if( nImgFilterIndex >= 0 )
+ {
+ aImgOut = aParam.copy( nImgFilterIndex+1 );
+ aFilterOut = aParam.copy( nPathIndex+1, nImgFilterIndex-nPathIndex-1 );
+ }
+ else
+ aFilterOut = aParam.copy( nPathIndex+1 );
+
+ FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
+ INetURLObject aOutFilename(aFilterOut);
+ aOutFilename.Append(aObj.getName(INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::NONE));
+ aOutFilename.SetExtension(aFilterExt);
+ OUString aOutFile
+ = aOutFilename.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ std::unique_ptr<utl::TempFileNamed> fileForCat;
+ if( aDispatchRequest.aRequestType == REQUEST_CAT )
+ {
+ fileForCat = std::make_unique<utl::TempFileNamed>();
+ if (fileForCat->IsValid())
+ fileForCat->EnableKillingFile();
+ else
+ std::cerr << "Error: Cannot create temporary file..." << std::endl ;
+ aOutFile = fileForCat->GetURL();
+ }
+
+ if ( bGuess )
+ {
+ OUString aDocService;
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if ( xModel.is() )
+ {
+ utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
+ aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
+ }
+ aFilter = impl_GuessFilter( aOutFile, aDocService );
+ }
+
+ bool bMultiFileTarget = false;
+
+ if (aFilter.isEmpty())
+ {
+ std::cerr << "Error: no export filter" << std::endl;
+ }
+ else
+ {
+ sal_Int32 nFilterOptionsIndex = aFilter.indexOf(':');
+ sal_Int32 nProps = ( 0 < nFilterOptionsIndex ) ? 4 : 3;
+
+ if ( !aImgOut.isEmpty() )
+ nProps +=1;
+ Sequence<PropertyValue> conversionProperties( nProps );
+ auto pconversionProperties = conversionProperties.getArray();
+ pconversionProperties[0].Name = "ConversionRequestOrigin";
+ pconversionProperties[0].Value <<= OUString("CommandLine");
+ pconversionProperties[1].Name = "Overwrite";
+ pconversionProperties[1].Value <<= true;
+
+ pconversionProperties[2].Name = "FilterName";
+ if( 0 < nFilterOptionsIndex )
+ {
+ OUString sFilterName = aFilter.copy(0, nFilterOptionsIndex);
+ OUString sFilterOptions = aFilter.copy(nFilterOptionsIndex + 1);
+
+ if (sFilterName == "Text - txt - csv (StarCalc)")
+ {
+ sal_Int32 nIdx(0);
+ // If the 11th token is '-1' then we export a file
+ // per sheet where the file name is based on the suggested
+ // output filename concatenated with the sheet name, so adjust
+ // the output and overwrite messages
+ // If the 11th token is not present or numeric 0 then the
+ // default sheet is exported with the output filename. If it
+ // is numeric >0 then that sheet (1-based) with the output
+ // filename concatenated with the sheet name. So even if
+ // that is a single file, the multi file target mechanism is
+ // used.
+ const OUString aTok(sFilterOptions.getToken(11, ',', nIdx));
+ // Actual validity is checked in Calc, here just check for
+ // presence of numeric value at start.
+ bMultiFileTarget = (!aTok.isEmpty() && aTok.toInt32() != 0);
+ }
+
+ pconversionProperties[2].Value <<= sFilterName;
+
+ pconversionProperties[3].Name = "FilterOptions";
+ pconversionProperties[3].Value <<= sFilterOptions;
+ }
+ else
+ {
+ pconversionProperties[2].Value <<= aFilter;
+ }
+
+ if ( !aImgOut.isEmpty() )
+ {
+ assert(conversionProperties[nProps-1].Name.isEmpty());
+ pconversionProperties[nProps-1].Name = "ImageFilter";
+ pconversionProperties[nProps-1].Value <<= aImgOut;
+ }
+
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL(aName, aTempName);
+ OString aSource8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
+ FileBase::getSystemPathFromFileURL(aOutFile, aTempName);
+ OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
+ if (aDispatchRequest.aRequestType != REQUEST_CAT)
+ {
+ OUString name=getName(xDoc);
+ std::cout << "convert " << aSource8;
+ if (!name.isEmpty())
+ std::cout << " as a " << name <<" document";
+ if (!bMultiFileTarget)
+ std::cout << " -> " << aTargetURL8;
+ std::cout << " using filter : " << OUStringToOString(aFilter, osl_getThreadTextEncoding()) << std::endl;
+ if (!bMultiFileTarget && FStatHelper::IsDocument(aOutFile))
+ std::cout << "Overwriting: " << OUStringToOString(aTempName, osl_getThreadTextEncoding()) << std::endl ;
+ }
+ try
+ {
+ xStorable->storeToURL(aOutFile, conversionProperties);
+ }
+ catch (const Exception& rException)
+ {
+ std::cerr << "Error: Please verify input parameters...";
+ if (!rException.Message.isEmpty())
+ std::cerr << " (" << rException.Message << ")";
+ std::cerr << std::endl;
+ }
+
+ if (fileForCat && fileForCat->IsValid())
+ {
+ SvStream* aStream = fileForCat->GetStream(StreamMode::STD_READ);
+ while (aStream->good())
+ {
+ OString aStr;
+ aStream->ReadLine(aStr, SAL_MAX_INT32);
+ for (sal_Int32 i = 0; i < aStr.getLength(); ++i)
+ {
+ std::cout << aStr[i];
+ }
+ std::cout << std::endl;
+ }
+ }
+ }
+ }
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
+ {
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if( xModel.is() )
+ scriptCat( xModel );
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_BATCHPRINT )
+ {
+ batchPrint( aDispatchRequest.aPrinterName, xDoc, aObj, aName );
+ }
+ else
+ {
+ if ( aDispatchRequest.aRequestType == REQUEST_PRINTTO )
+ {
+ // create the printer
+ Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue(
+ "Name", aDispatchRequest.aPrinterName) };
+ xDoc->setPrinter( aPrinterArgs );
+ }
+
+ // print ( also without user interaction )
+ Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue("Wait",
+ true) };
+ xDoc->print( aPrinterArgs );
+ }
+ }
+ else
+ {
+ std::cerr << "Error: source file could not be loaded" << std::endl;
+ }
+
+ // remove the document
+ try
+ {
+ Reference < XCloseable > xClose( xDoc, UNO_QUERY );
+ if ( xClose.is() )
+ xClose->close( true );
+ else
+ {
+ Reference < XComponent > xComp( xDoc, UNO_QUERY );
+ if ( xComp.is() )
+ xComp->dispose();
+ }
+ }
+ catch (const css::util::CloseVetoException&)
+ {
+ }
+
+ // request is completed
+ RequestHandler::RequestsCompleted();
+ }
+ }
+ }
+
+ if ( !aDispatches.empty() )
+ {
+ // Execute all asynchronous dispatches now after we placed them into our request container!
+ Sequence < PropertyValue > aArgs{
+ comphelper::makePropertyValue("Referer", OUString("private:OpenEvent")),
+ comphelper::makePropertyValue("SynchronMode", true)
+ };
+
+ for (const DispatchHolder & aDispatche : aDispatches)
+ {
+ Reference< XDispatch > xDispatch = aDispatche.xDispatch;
+ Reference < XNotifyingDispatch > xDisp( xDispatch, UNO_QUERY );
+ if ( xDisp.is() )
+ xDisp->dispatchWithNotification( aDispatche.aURL, aArgs, this );
+ else
+ {
+ m_nRequestCount--;
+ xDispatch->dispatch( aDispatche.aURL, aArgs );
+ }
+ }
+ }
+
+ bool bEmpty = (m_nRequestCount == 0);
+
+ // No more asynchronous requests?
+ // The requests are removed from the request container after they called back to this
+ // implementation via statusChanged!!
+ if ( bEmpty && !bNoTerminate /*m_aRequestContainer.empty()*/ )
+ {
+ // We have to check if we have an open task otherwise we have to shutdown the office.
+ Reference< XElementAccess > xList = xDesktop->getFrames();
+
+ if ( !xList->hasElements() )
+ {
+ // We don't have any task open so we have to shutdown ourself!!
+ return xDesktop->terminate();
+ }
+ }
+
+ return false;
+}
+
+
+void SAL_CALL DispatchWatcher::disposing( const css::lang::EventObject& )
+{
+}
+
+
+void SAL_CALL DispatchWatcher::dispatchFinished( const DispatchResultEvent& )
+{
+ int nCount = --m_nRequestCount;
+ RequestHandler::RequestsCompleted();
+ if ( !nCount && !RequestHandler::AreRequestsPending() )
+ {
+ // We have to check if we have an open task otherwise we have to shutdown the office.
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XElementAccess > xList = xDesktop->getFrames();
+
+ if ( !xList->hasElements() )
+ {
+ // We don't have any task open so we have to shutdown ourself!!
+ xDesktop->terminate();
+ }
+ }
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/dispatchwatcher.hxx b/desktop/source/app/dispatchwatcher.hxx
new file mode 100644
index 0000000000..70a7fd42e6
--- /dev/null
+++ b/desktop/source/app/dispatchwatcher.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/frame/XDispatchResultListener.hpp>
+#include <optional>
+#include <atomic>
+#include <vector>
+
+namespace desktop
+{
+
+/*
+ Class for controls dispatching of command URL through office command line. There
+ are "dangerous" command URLs, that can result in a running office without UI. To prevent
+ this situation the implementation monitors all dispatches and looks for an open task if
+ there is arose a problem. If there is none the office will be shutdown to prevent a
+ running office without UI.
+*/
+class DispatchWatcher : public ::cppu::WeakImplHelper< css::frame::XDispatchResultListener >
+{
+ public:
+ enum RequestType
+ {
+ REQUEST_OPEN,
+ REQUEST_VIEW,
+ REQUEST_START,
+ REQUEST_PRINT,
+ REQUEST_PRINTTO,
+ REQUEST_FORCEOPEN,
+ REQUEST_FORCENEW,
+ REQUEST_CONVERSION,
+ REQUEST_INFILTER,
+ REQUEST_BATCHPRINT,
+ REQUEST_CAT,
+ REQUEST_SCRIPT_CAT
+ };
+
+ struct DispatchRequest
+ {
+ RequestType aRequestType;
+ OUString aURL;
+ std::optional< OUString > aCwdUrl;
+ OUString aPrinterName; // also conversion params
+ OUString aPreselectedFactory;
+ };
+
+ DispatchWatcher();
+
+ virtual ~DispatchWatcher() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XDispachResultListener
+ virtual void SAL_CALL dispatchFinished( const css::frame::DispatchResultEvent& aEvent ) override;
+
+ // execute new dispatch request
+ bool executeDispatchRequests( const std::vector<DispatchRequest>& aDispatches, bool bNoTerminate );
+
+ private:
+
+ std::atomic<int> m_nRequestCount;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/langselect.cxx b/desktop/source/app/langselect.cxx
new file mode 100644
index 0000000000..5eb2f0636b
--- /dev/null
+++ b/desktop/source/app/langselect.cxx
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/lang/XLocalizable.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/processfactory.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <officecfg/Office/Linguistic.hxx>
+#include <officecfg/Setup.hxx>
+#include <officecfg/System.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/languageoptions.hxx>
+#include <svtools/langhelp.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <app.hxx>
+
+#include "cmdlineargs.hxx"
+#include "langselect.hxx"
+
+namespace desktop::langselect {
+
+namespace {
+
+void setMsLangIdFallback(OUString const & locale) {
+ // #i32939# setting of default document language
+ // See #i42730# for rules for determining source of settings
+ if (locale.isEmpty())
+ return;
+
+ LanguageType type = LanguageTag::convertToLanguageTypeWithFallback(locale);
+ switch (SvtLanguageOptions::GetScriptTypeOfLanguage(type)) {
+ case SvtScriptType::ASIAN:
+ MsLangId::setConfiguredAsianFallback(type);
+ break;
+ case SvtScriptType::COMPLEX:
+ MsLangId::setConfiguredComplexFallback(type);
+ break;
+ default:
+ MsLangId::setConfiguredWesternFallback(type);
+ break;
+ }
+}
+
+}
+
+bool prepareLocale() {
+ // #i42730# Get the windows 16Bit locale, it should be preferred over the UI
+ // locale:
+ setMsLangIdFallback(officecfg::System::L10N::SystemLocale::get());
+ // #i32939# Use system locale to set document default locale:
+ setMsLangIdFallback(officecfg::System::L10N::Locale::get());
+ css::uno::Sequence<OUString> inst(
+ officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ OUString locale(officecfg::Office::Linguistic::General::UILocale::get());
+ if (!locale.isEmpty()) {
+ locale = getInstalledLocaleForLanguage(inst, locale);
+ if (locale.isEmpty()) {
+ // Selected language is not/no longer installed:
+ try {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Linguistic::General::UILocale::set(
+ "", batch);
+ batch->commit();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+ }
+ }
+ if (locale.isEmpty()) {
+ locale = getInstalledLocaleForLanguage(
+ inst, Desktop::GetCommandLineArgs().GetLanguage());
+ }
+ if (locale.isEmpty()) {
+ locale = getInstalledLocaleForSystemUILanguage(inst, true);
+ }
+ if (locale.isEmpty()) {
+ return false;
+ }
+ LanguageTag tag(locale);
+ // Prepare default config provider by localizing it to the selected
+ // locale this will ensure localized configuration settings to be
+ // selected according to the UI language:
+ css::uno::Reference<css::lang::XLocalizable>(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->setLocale(tag.getLocale(false));
+ try {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::L10N::ooLocale::set(locale, batch);
+ batch->commit();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+ MsLangId::setConfiguredSystemUILanguage(tag.getLanguageType(false));
+
+ // Note the system language/locale here may or may not be correct before we
+ // actually set it below. It is what could be figured from the system
+ // setting and may only partially match, like "en" for an unsupported
+ // English locale.
+ LanguageTag aSysLocTag( MsLangId::getSystemLanguage());
+ OUString setupSysLoc(officecfg::Setup::L10N::ooSetupSystemLocale::get());
+ if (!setupSysLoc.isEmpty())
+ aSysLocTag.reset( setupSysLoc);
+ // Ensure the system locale is set to a known supported locale.
+ aSysLocTag.makeFallback();
+ LanguageTag::setConfiguredSystemLanguage( aSysLocTag.getLanguageType(false));
+
+ // #i32939# setting of default document locale
+ // #i32939# this should not be based on the UI language
+ // So obtain the system locale now configured just above and pass it on,
+ // resolved of course.
+ LanguageTag docTag(LANGUAGE_SYSTEM);
+ setMsLangIdFallback(docTag.getBcp47());
+
+ return true;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/langselect.hxx b/desktop/source/app/langselect.hxx
new file mode 100644
index 0000000000..496f67570b
--- /dev/null
+++ b/desktop/source/app/langselect.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+namespace desktop::langselect
+{
+bool prepareLocale();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/lockfile2.cxx b/desktop/source/app/lockfile2.cxx
new file mode 100644
index 0000000000..98c2903f94
--- /dev/null
+++ b/desktop/source/app/lockfile2.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include <tools/config.hxx>
+#include <lockfile.hxx>
+
+namespace desktop {
+
+bool Lockfile_execWarning( Lockfile const * that )
+{
+ // read information from lock
+ OUString aLockname = that->m_aLockname;
+ Config aConfig(aLockname);
+ aConfig.SetGroup( LOCKFILE_GROUP ""_ostr );
+ OString aHost = aConfig.ReadKey( LOCKFILE_HOSTKEY ""_ostr );
+ OString aUser = aConfig.ReadKey( LOCKFILE_USERKEY ""_ostr );
+ OString aTime = aConfig.ReadKey( LOCKFILE_TIMEKEY ""_ostr );
+
+ // display warning and return response
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Question, VclButtonsType::YesNo, DpResId(STR_QUERY_USERDATALOCKED)));
+ // set box title
+ OUString aTitle = DpResId(STR_TITLE_USERDATALOCKED);
+ xBox->set_title( aTitle );
+ // insert values...
+ OUString aMsgText = xBox->get_primary_text();
+ aMsgText = aMsgText.replaceFirst(
+ "$u", OStringToOUString( aUser, RTL_TEXTENCODING_ASCII_US) );
+ aMsgText = aMsgText.replaceFirst(
+ "$h", OStringToOUString( aHost, RTL_TEXTENCODING_ASCII_US) );
+ aMsgText = aMsgText.replaceFirst(
+ "$t", OStringToOUString( aTime, RTL_TEXTENCODING_ASCII_US) );
+ xBox->set_primary_text(aMsgText);
+ // do it
+ return xBox->run() == RET_YES;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/main.c b/desktop/source/app/main.c
new file mode 100644
index 0000000000..fdd2eb3505
--- /dev/null
+++ b/desktop/source/app/main.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/main.h>
+
+#include "sofficemain.h"
+
+#ifndef NOTEST_xmlCleanupParser
+#ifdef DBG_UTIL
+#ifdef __gnu_linux__
+#include <stdio.h>
+#include <stdlib.h>
+
+static int g_Exiting = 0;
+
+/* HACK: detect calls to xmlCleanupParser, which causes hard to debug crashes */
+__attribute__((visibility("default"))) void xmlCleanupParser(void)
+{
+ /* there are libraries that register xmlCleanupParser as an atexit handler,
+ which is not entirely sound (another atexit handler could want to
+ use libxml), but not enough of a problem to complain.
+ (example found by llunak: KDE's Strigi library) */
+ if (!g_Exiting)
+ {
+ fprintf(stderr, "\n*** ERROR: DO NOT call xmlCleanupParser()\n\n");
+ abort();
+ }
+}
+#endif
+#endif
+#endif // NOTEST_xmlCleanupParser
+
+SAL_IMPLEMENT_MAIN()
+{
+ int ret = soffice_main();
+#ifndef NOTEST_xmlCleanupParser
+#ifdef DBG_UTIL
+#ifdef __gnu_linux__
+ g_Exiting = 1;
+#endif
+#endif
+#endif
+ return ret;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/officeipcthread.cxx b/desktop/source/app/officeipcthread.cxx
new file mode 100644
index 0000000000..9d342bf35a
--- /dev/null
+++ b/desktop/source/app/officeipcthread.cxx
@@ -0,0 +1,1357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <config_dbus.h>
+#include <config_features.h>
+#include <config_feature_desktop.h>
+
+#include <app.hxx>
+#include "officeipcthread.hxx"
+#include "cmdlineargs.hxx"
+#include "dispatchwatcher.hxx"
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <salhelper/thread.hxx>
+#include <sal/log.hxx>
+#include <unotools/bootstrap.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/pipe.hxx>
+#include <rtl/digest.h>
+#include <rtl/ustrbuf.hxx>
+#include <osl/conditn.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <rtl/strbuf.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/file.hxx>
+#include <rtl/process.h>
+#include <o3tl/string_view.hxx>
+
+#include <cassert>
+#include <cstdlib>
+#include <memory>
+#include <thread>
+
+#if ENABLE_DBUS
+#include <dbus/dbus.h>
+#include <sys/socket.h>
+#endif
+
+using namespace desktop;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::frame;
+
+namespace {
+
+char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
+char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
+char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
+
+// Receives packets from the pipe until a packet ends in a NUL character (that
+// will not be included in the returned string) or it cannot read anything (due
+// to error or closed pipe, in which case an empty string will be returned to
+// signal failure):
+OString readStringFromPipe(osl::StreamPipe const & pipe) {
+ for (OStringBuffer str;;) {
+ char buf[1024];
+ sal_Int32 n = pipe.recv(buf, std::size(buf));
+ if (n <= 0) {
+ SAL_INFO("desktop.app", "read empty string");
+ return ""_ostr;
+ }
+ bool end = false;
+ if (buf[n - 1] == '\0') {
+ end = true;
+ --n;
+ }
+ str.append(buf, n);
+ //TODO: how does OStringBuffer.append handle overflow?
+ if (end) {
+ auto s = str.makeStringAndClear();
+ SAL_INFO("desktop.app", "read <" << s << ">");
+ return s;
+ }
+ }
+}
+
+}
+
+namespace desktop
+{
+
+namespace {
+
+class Parser: public CommandLineArgs::Supplier {
+public:
+ explicit Parser(OString input): m_input(std::move(input)) {
+ if (!m_input.match(ARGUMENT_PREFIX) ||
+ m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
+ {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
+ switch (m_input[m_index++]) {
+ case '0':
+ break;
+ case '1':
+ {
+ OUString url;
+ if (!next(&url, false)) {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ m_cwdUrl = url;
+ break;
+ }
+ case '2':
+ {
+ OUString path;
+ if (!next(&path, false)) {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ OUString url;
+ if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
+ osl::FileBase::E_None)
+ {
+ m_cwdUrl = url;
+ }
+ break;
+ }
+ default:
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ }
+
+ virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
+
+ virtual bool next(OUString * argument) override { return next(argument, true); }
+
+private:
+ bool next(OUString * argument, bool prefix) {
+ OSL_ASSERT(argument != nullptr);
+ if (m_index < m_input.getLength()) {
+ if (prefix) {
+ if (m_input[m_index] != ',') {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ ++m_index;
+ }
+ OStringBuffer b;
+ while (m_index < m_input.getLength()) {
+ char c = m_input[m_index];
+ if (c == ',') {
+ break;
+ }
+ ++m_index;
+ if (c == '\\') {
+ if (m_index >= m_input.getLength())
+ throw CommandLineArgs::Supplier::Exception();
+ c = m_input[m_index++];
+ switch (c) {
+ case '0':
+ c = '\0';
+ break;
+ case ',':
+ case '\\':
+ break;
+ default:
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ }
+ b.append(c);
+ }
+ OString b2(b.makeStringAndClear());
+ if (!rtl_convertStringToUString(
+ &argument->pData, b2.getStr(), b2.getLength(),
+ RTL_TEXTENCODING_UTF8,
+ (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
+ RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
+ {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ std::optional< OUString > m_cwdUrl;
+ OString m_input;
+ sal_Int32 m_index;
+};
+
+bool addArgument(OStringBuffer &rArguments, char prefix,
+ const OUString &rArgument)
+{
+ OString utf8;
+ if (!rArgument.convertToString(
+ &utf8, RTL_TEXTENCODING_UTF8,
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ return false;
+ }
+ rArguments.append(prefix);
+ for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
+ char c = utf8[i];
+ switch (c) {
+ case '\0':
+ rArguments.append("\\0");
+ break;
+ case ',':
+ rArguments.append("\\,");
+ break;
+ case '\\':
+ rArguments.append("\\\\");
+ break;
+ default:
+ rArguments.append(c);
+ break;
+ }
+ }
+ return true;
+}
+
+}
+
+rtl::Reference< RequestHandler > RequestHandler::pGlobal;
+
+// Turns a string in aMsg such as file:///home/foo/.libreoffice/3
+// Into a hex string of well known length ff132a86...
+static OUString CreateMD5FromString( const OUString& aMsg )
+{
+ SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
+
+ rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
+ if ( handle )
+ {
+ const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
+ sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
+ sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
+ std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
+
+ rtl_digest_init( handle, pData, nSize );
+ rtl_digest_update( handle, pData, nSize );
+ rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
+ rtl_digest_destroy( handle );
+
+ // Create hex-value string from the MD5 value to keep the string size minimal
+ OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
+ for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
+ aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
+
+ return aBuffer.makeStringAndClear();
+ }
+
+ return OUString();
+}
+
+namespace {
+
+class ProcessEventsClass_Impl
+{
+public:
+ DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
+ DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
+};
+
+}
+
+IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
+{
+ // Application events are processed by the Desktop::HandleAppEvent implementation.
+ Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
+ delete static_cast<ApplicationEvent*>(pEvent);
+}
+
+IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
+{
+ // Documents requests are processed by the RequestHandler implementation
+ ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
+ RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
+ delete pDocsRequest;
+}
+
+static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
+{
+ Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
+}
+
+static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
+{
+ Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
+}
+
+oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
+{
+ if( pInfo->Signal == osl_Signal_Terminate )
+ RequestHandler::Disable();
+ return osl_Signal_ActCallNextHdl;
+}
+
+
+// The RequestHandlerController implementation is a bookkeeper for all pending requests
+// that were created by the RequestHandler. The requests are waiting to be processed by
+// our framework loadComponentFromURL function (e.g. open/print request).
+// During shutdown the framework is asking RequestHandlerController about pending requests.
+// If there are pending requests framework has to stop the shutdown process. It is waiting
+// for these requests because framework is not able to handle shutdown and open a document
+// concurrently.
+
+
+// XServiceInfo
+OUString SAL_CALL RequestHandlerController::getImplementationName()
+{
+ return "com.sun.star.comp.RequestHandlerController";
+}
+
+sal_Bool RequestHandlerController::supportsService(
+ OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
+{
+ return { };
+}
+
+// XEventListener
+void SAL_CALL RequestHandlerController::disposing( const EventObject& )
+{
+}
+
+// XTerminateListener
+void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
+{
+ // Desktop ask about pending request through our office ipc pipe. We have to
+ // be sure that no pending request is waiting because framework is not able to
+ // handle shutdown and open a document concurrently.
+
+ if ( RequestHandler::AreRequestsPending() )
+ throw TerminationVetoException();
+ RequestHandler::SetDowning();
+}
+
+void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
+{
+}
+
+class IpcThread: public salhelper::Thread {
+public:
+ void start(RequestHandler * handler) {
+ m_handler = handler;
+ launch();
+ }
+
+ virtual void close() = 0;
+
+protected:
+ explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
+
+ virtual ~IpcThread() override {}
+
+ bool process(OString const & arguments, bool * waitProcessed);
+
+ RequestHandler * m_handler;
+};
+
+class PipeIpcThread: public IpcThread {
+public:
+ static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
+
+private:
+ explicit PipeIpcThread(osl::Pipe pipe):
+ IpcThread("PipeIPC"), pipe_(std::move(pipe))
+ {}
+
+ virtual ~PipeIpcThread() override {}
+
+ void execute() override;
+
+ void close() override { pipe_.close(); }
+
+ osl::Pipe pipe_;
+};
+
+#if ENABLE_DBUS
+
+namespace {
+
+struct DbusConnectionHolder {
+ explicit DbusConnectionHolder(DBusConnection * theConnection):
+ connection(theConnection)
+ {}
+
+ DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
+ { std::swap(connection, other.connection); }
+
+ ~DbusConnectionHolder() {
+ if (connection != nullptr) {
+ dbus_connection_close(connection);
+ dbus_connection_unref(connection);
+ }
+ }
+
+ DBusConnection * connection;
+};
+
+struct DbusMessageHolder {
+ explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
+
+ ~DbusMessageHolder() { clear(); }
+
+ void clear() {
+ if (message != nullptr) {
+ dbus_message_unref(message);
+ }
+ message = nullptr;
+ }
+
+ DBusMessage * message;
+
+private:
+ DbusMessageHolder(DbusMessageHolder const &) = delete;
+ DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
+};
+
+}
+
+class DbusIpcThread: public IpcThread {
+public:
+ static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
+
+private:
+ explicit DbusIpcThread(DbusConnectionHolder && connection):
+ IpcThread("DbusIPC"), connection_(std::move(connection))
+ {}
+
+ virtual ~DbusIpcThread() override {}
+
+ void execute() override;
+
+ void close() override;
+
+ DbusConnectionHolder connection_;
+};
+
+RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
+{
+ assert(thread != nullptr);
+ if (!dbus_threads_init_default()) {
+ SAL_WARN("desktop.app", "dbus_threads_init_default failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DBusError e;
+ dbus_error_init(&e);
+ DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
+ assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
+ if (con.connection == nullptr) {
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_get_private failed with: " << e.name << ": "
+ << e.message);
+ dbus_error_free(&e);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ for (;;) {
+ int n = dbus_bus_request_name(
+ con.connection, "org.libreoffice.LibreOfficeIpc0",
+ DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
+ assert((n == -1) == bool(dbus_error_is_set(&e)));
+ switch (n) {
+ case -1:
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_request_name failed with: " << e.name << ": "
+ << e.message);
+ dbus_error_free(&e);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ *thread = new DbusIpcThread(std::move(con));
+ return RequestHandler::IPC_STATUS_OK;
+ case DBUS_REQUEST_NAME_REPLY_EXISTS:
+ {
+ OStringBuffer buf(ARGUMENT_PREFIX);
+ OUString arg;
+ if (!(utl::Bootstrap::getProcessWorkingDir(arg)
+ && addArgument(buf, '1', arg)))
+ {
+ buf.append('0');
+ }
+ sal_uInt32 narg = rtl_getAppCommandArgCount();
+ for (sal_uInt32 i = 0; i != narg; ++i) {
+ rtl_getAppCommandArg(i, &arg.pData);
+ if (!addArgument(buf, ',', arg)) {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ }
+ char const * argstr = buf.getStr();
+ DbusMessageHolder msg(
+ dbus_message_new_method_call(
+ "org.libreoffice.LibreOfficeIpc0",
+ "/org/libreoffice/LibreOfficeIpc0",
+ "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
+ if (msg.message == nullptr) {
+ SAL_WARN(
+ "desktop.app", "dbus_message_new_method_call failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DBusMessageIter it;
+ dbus_message_iter_init_append(msg.message, &it);
+ if (!dbus_message_iter_append_basic(
+ &it, DBUS_TYPE_STRING, &argstr))
+ {
+ SAL_WARN(
+ "desktop.app", "dbus_message_iter_append_basic failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DbusMessageHolder repl(
+ dbus_connection_send_with_reply_and_block(
+ con.connection, msg.message, 0x7FFFFFFF, &e));
+ assert(
+ (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
+ if (repl.message == nullptr) {
+ SAL_INFO(
+ "desktop.app",
+ "dbus_connection_send_with_reply_and_block failed"
+ " with: " << e.name << ": " << e.message);
+ dbus_error_free(&e);
+ break;
+ }
+ return RequestHandler::IPC_STATUS_2ND_OFFICE;
+ }
+ case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
+ case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_request_name failed with unexpected " << +n);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ default:
+ for (;;) std::abort();
+ }
+ }
+}
+
+void DbusIpcThread::execute()
+{
+ assert(m_handler != nullptr);
+ m_handler->cReady.wait();
+ for (;;) {
+ {
+ osl::MutexGuard g(RequestHandler::GetMutex());
+ if (m_handler->mState == RequestHandler::State::Downing) {
+ break;
+ }
+ }
+ if (!dbus_connection_read_write(connection_.connection, -1)) {
+ break;
+ }
+ for (;;) {
+ DbusMessageHolder msg(
+ dbus_connection_pop_message(connection_.connection));
+ if (msg.message == nullptr) {
+ break;
+ }
+ if (!dbus_message_is_method_call(
+ msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
+ "Execute"))
+ {
+ SAL_INFO("desktop.app", "unknown DBus message ignored");
+ continue;
+ }
+ DBusMessageIter it;
+ if (!dbus_message_iter_init(msg.message, &it)) {
+ SAL_WARN(
+ "desktop.app", "DBus message without argument ignored");
+ continue;
+ }
+ if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
+ SAL_WARN(
+ "desktop.app",
+ "DBus message with non-string argument ignored");
+ continue;
+ }
+ char const * argstr;
+ dbus_message_iter_get_basic(&it, &argstr);
+ bool waitProcessed = false;
+ {
+ osl::MutexGuard g(RequestHandler::GetMutex());
+ if (!process(argstr, &waitProcessed)) {
+ continue;
+ }
+ }
+ if (waitProcessed) {
+ m_handler->cProcessed.wait();
+ }
+ DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
+ if (repl.message == nullptr) {
+ SAL_WARN(
+ "desktop.app", "dbus_message_new_method_return failed");
+ continue;
+ }
+ dbus_uint32_t serial = 0;
+ if (!dbus_connection_send(
+ connection_.connection, repl.message, &serial)) {
+ SAL_WARN("desktop.app", "dbus_connection_send failed");
+ continue;
+ }
+ dbus_connection_flush(connection_.connection);
+ }
+ }
+}
+
+void DbusIpcThread::close() {
+ assert(connection_.connection != nullptr);
+ // Make dbus_connection_read_write fall out of internal poll call blocking
+ // on POLLIN:
+ int fd;
+ if (!dbus_connection_get_socket(connection_.connection, &fd)) {
+ SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
+ return;
+ }
+ if (shutdown(fd, SHUT_RD) == -1) {
+ auto const e = errno;
+ SAL_WARN("desktop.app", "shutdown failed with errno " << e);
+ }
+}
+
+#endif
+
+::osl::Mutex& RequestHandler::GetMutex()
+{
+ static ::osl::Mutex theRequestHandlerMutex;
+ return theRequestHandlerMutex;
+}
+
+void RequestHandler::SetDowning()
+{
+ // We have the order to block all incoming requests. Framework
+ // wants to shutdown and we have to make sure that no loading/printing
+ // requests are executed anymore.
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if ( pGlobal.is() )
+ pGlobal->mState = State::Downing;
+}
+
+void RequestHandler::EnableRequests()
+{
+ // switch between just queueing the requests and executing them
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if ( pGlobal.is() )
+ {
+ if (pGlobal->mState != State::Downing) {
+ pGlobal->mState = State::RequestsEnabled;
+ }
+ ProcessDocumentsRequest aEmptyReq(std::nullopt);
+ // trigger already queued requests
+ RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
+ }
+}
+
+bool RequestHandler::AreRequestsPending()
+{
+ // Give info about pending requests
+ ::osl::MutexGuard aGuard( GetMutex() );
+ if ( pGlobal.is() )
+ return ( pGlobal->mnPendingRequests > 0 );
+ else
+ return false;
+}
+
+void RequestHandler::RequestsCompleted()
+{
+ // Remove nCount pending requests from our internal counter
+ ::osl::MutexGuard aGuard( GetMutex() );
+ if ( pGlobal.is() )
+ {
+ if ( pGlobal->mnPendingRequests > 0 )
+ pGlobal->mnPendingRequests --;
+ }
+}
+
+RequestHandler::Status RequestHandler::Enable(bool ipc)
+{
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if( pGlobal.is() )
+ return IPC_STATUS_OK;
+
+#if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX || defined(EMSCRIPTEN)
+ ipc = false;
+#endif
+
+ if (!ipc) {
+ pGlobal = new RequestHandler;
+ return IPC_STATUS_OK;
+ }
+
+ enum class Kind { Pipe, Dbus };
+ Kind kind;
+#if ENABLE_DBUS
+ kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
+#else
+ kind = Kind::Pipe;
+#endif
+ rtl::Reference<IpcThread> thread;
+ Status stat = Status(); // silence bogus potentially-uninitialized warnings
+ switch (kind) {
+ case Kind::Pipe:
+ stat = PipeIpcThread::enable(&thread);
+ break;
+ case Kind::Dbus:
+#if ENABLE_DBUS
+ stat = DbusIpcThread::enable(&thread);
+ break;
+#endif
+ default:
+ assert(false);
+ }
+ assert(thread.is() == (stat == IPC_STATUS_OK));
+ if (stat == IPC_STATUS_OK) {
+ pGlobal = new RequestHandler;
+ pGlobal->mIpcThread = thread;
+ pGlobal->mIpcThread->start(pGlobal.get());
+ }
+ return stat;
+}
+
+RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
+{
+ assert(thread != nullptr);
+
+ // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
+ // this information from a unotools implementation.
+ OUString aUserInstallPath;
+ ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
+ if (aLocateResult != utl::Bootstrap::PATH_EXISTS
+ && aLocateResult != utl::Bootstrap::PATH_VALID)
+ {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ // Try to determine if we are the first office or not! This should prevent multiple
+ // access to the user directory !
+ // First we try to create our pipe if this fails we try to connect. We have to do this
+ // in a loop because the other office can crash or shutdown between createPipe
+ // and connectPipe!!
+ auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
+
+ // Check result to create a hash code from the user install path
+ if ( aUserInstallPathHashCode.isEmpty() )
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
+
+ osl::Pipe pipe;
+ enum PipeMode
+ {
+ PIPEMODE_DONTKNOW,
+ PIPEMODE_CREATED,
+ PIPEMODE_CONNECTED
+ };
+ PipeMode nPipeMode = PIPEMODE_DONTKNOW;
+
+ OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
+ do
+ {
+ osl::Security security;
+
+ // Try to create pipe
+ if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
+ {
+ // Pipe created
+ nPipeMode = PIPEMODE_CREATED;
+ }
+ else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
+ {
+ osl::StreamPipe aStreamPipe(pipe.getHandle());
+ if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
+ {
+ // Pipe connected to first office
+ nPipeMode = PIPEMODE_CONNECTED;
+ }
+ else
+ {
+ // Pipe connection failed (other office exited or crashed)
+ std::this_thread::sleep_for( std::chrono::milliseconds(500) );
+ }
+ }
+ else
+ {
+ oslPipeError eReason = pipe.getError();
+ if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
+ return RequestHandler::IPC_STATUS_PIPE_ERROR;
+
+ // Wait for second office to be ready
+ std::this_thread::sleep_for( std::chrono::milliseconds(10) );
+ }
+
+ } while ( nPipeMode == PIPEMODE_DONTKNOW );
+
+ if ( nPipeMode == PIPEMODE_CREATED )
+ {
+ // Seems we are the one and only, so create listening thread
+ *thread = new PipeIpcThread(pipe);
+ return RequestHandler::IPC_STATUS_OK;
+ }
+ else
+ {
+ // Seems another office is running. Pipe arguments to it and self terminate
+ osl::StreamPipe aStreamPipe(pipe.getHandle());
+
+ OStringBuffer aArguments(ARGUMENT_PREFIX);
+ OUString cwdUrl;
+ if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
+ addArgument(aArguments, '1', cwdUrl)))
+ {
+ aArguments.append('0');
+ }
+ sal_uInt32 nCount = rtl_getAppCommandArgCount();
+ for( sal_uInt32 i=0; i < nCount; i++ )
+ {
+ rtl_getAppCommandArg( i, &aUserInstallPath.pData );
+ if (!addArgument(aArguments, ',', aUserInstallPath)) {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ }
+ aArguments.append('\0');
+ // finally, write the string onto the pipe
+ SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
+ sal_Int32 n = aStreamPipe.write(
+ aArguments.getStr(), aArguments.getLength());
+ if (n != aArguments.getLength()) {
+ SAL_INFO("desktop.app", "short write: " << n);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
+ {
+ // something went wrong
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ return RequestHandler::IPC_STATUS_2ND_OFFICE;
+ }
+}
+
+void RequestHandler::Disable()
+{
+ osl::ClearableMutexGuard aMutex( GetMutex() );
+
+ if( !pGlobal.is() )
+ return;
+
+ rtl::Reference< RequestHandler > handler(pGlobal);
+ pGlobal.clear();
+
+ handler->mState = State::Downing;
+ if (handler->mIpcThread.is()) {
+ handler->mIpcThread->close();
+ }
+
+ // release mutex to avoid deadlocks
+ aMutex.clear();
+
+ handler->cReady.set();
+
+ // exit gracefully and join
+ if (handler->mIpcThread.is())
+ {
+ handler->mIpcThread->join();
+ handler->mIpcThread.clear();
+ }
+
+ handler->cReady.reset();
+}
+
+RequestHandler::RequestHandler() :
+ mState( State::Starting ),
+ mnPendingRequests( 0 )
+{
+}
+
+RequestHandler::~RequestHandler()
+{
+ assert(!mIpcThread.is());
+}
+
+void RequestHandler::SetReady(bool bIsReady)
+{
+ osl::MutexGuard g(GetMutex());
+ if (pGlobal.is())
+ {
+ if (bIsReady)
+ pGlobal->cReady.set();
+ else
+ pGlobal->cReady.reset();
+ }
+}
+
+void RequestHandler::WaitForReady()
+{
+ rtl::Reference<RequestHandler> t;
+ {
+ osl::MutexGuard g(GetMutex());
+ t = pGlobal;
+ }
+ if (t.is())
+ {
+ t->cReady.wait();
+ }
+}
+
+bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
+ assert(waitProcessed != nullptr);
+
+ std::unique_ptr< CommandLineArgs > aCmdLineArgs;
+ try
+ {
+ Parser p(arguments);
+ aCmdLineArgs.reset( new CommandLineArgs( p ) );
+ }
+ catch ( const CommandLineArgs::Supplier::Exception & )
+ {
+ SAL_WARN("desktop.app", "Error in received command line arguments");
+ return false;
+ }
+
+ bool bDocRequestSent = false;
+
+ OUString aUnknown( aCmdLineArgs->GetUnknown() );
+ if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
+ {
+ const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
+
+ if ( aCmdLineArgs->IsQuickstart() )
+ {
+ // we have to use application event, because we have to start quickstart service in main thread!!
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::QuickStart);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+
+ // handle request for acceptor
+ std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
+ for (auto const& elem : accept)
+ {
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::Accept, elem);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ // handle acceptor removal
+ std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
+ for (auto const& elem : unaccept)
+ {
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::Unaccept, elem);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+
+ std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
+ aCmdLineArgs->getCwdUrl()));
+ m_handler->cProcessed.reset();
+ pRequest->pcProcessed = &m_handler->cProcessed;
+ m_handler->mbSuccess = false;
+ pRequest->mpbSuccess = &m_handler->mbSuccess;
+
+ // Print requests are not dependent on the --invisible cmdline argument as they are
+ // loaded with the "hidden" flag! So they are always checked.
+ pRequest->aPrintList = aCmdLineArgs->GetPrintList();
+ bDocRequestSent |= !pRequest->aPrintList.empty();
+ pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
+ pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
+ bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
+ pRequest->aConversionList = aCmdLineArgs->GetConversionList();
+ pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
+ pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
+ pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
+ pRequest->aInFilter = aCmdLineArgs->GetInFilter();
+ pRequest->bTextCat = aCmdLineArgs->IsTextCat();
+ pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
+ bDocRequestSent |= !pRequest->aConversionList.empty();
+
+ if ( !rCurrentCmdLineArgs.IsInvisible() )
+ {
+ // Read cmdline args that can open/create documents. As they would open a window
+ // they are only allowed if the "--invisible" is currently not used!
+ pRequest->aOpenList = aCmdLineArgs->GetOpenList();
+ bDocRequestSent |= !pRequest->aOpenList.empty();
+ pRequest->aViewList = aCmdLineArgs->GetViewList();
+ bDocRequestSent |= !pRequest->aViewList.empty();
+ pRequest->aStartList = aCmdLineArgs->GetStartList();
+ bDocRequestSent |= !pRequest->aStartList.empty();
+ pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
+ bDocRequestSent |= !pRequest->aForceOpenList.empty();
+ pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
+ bDocRequestSent |= !pRequest->aForceNewList.empty();
+
+ // Special command line args to create an empty document for a given module
+
+ // #i18338# (lo)
+ // we only do this if no document was specified on the command line,
+ // since this would be inconsistent with the behaviour of
+ // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
+ if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
+ {
+ SvtModuleOptions aOpt;
+ SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
+ if ( aCmdLineArgs->IsWriter() )
+ eFactory = SvtModuleOptions::EFactory::WRITER;
+ else if ( aCmdLineArgs->IsCalc() )
+ eFactory = SvtModuleOptions::EFactory::CALC;
+ else if ( aCmdLineArgs->IsDraw() )
+ eFactory = SvtModuleOptions::EFactory::DRAW;
+ else if ( aCmdLineArgs->IsImpress() )
+ eFactory = SvtModuleOptions::EFactory::IMPRESS;
+ else if ( aCmdLineArgs->IsBase() )
+ eFactory = SvtModuleOptions::EFactory::DATABASE;
+ else if ( aCmdLineArgs->IsMath() )
+ eFactory = SvtModuleOptions::EFactory::MATH;
+ else if ( aCmdLineArgs->IsGlobal() )
+ eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
+ else if ( aCmdLineArgs->IsWeb() )
+ eFactory = SvtModuleOptions::EFactory::WRITERWEB;
+
+ if ( !pRequest->aOpenList.empty() )
+ pRequest->aModule = aOpt.GetFactoryName( eFactory );
+ else
+ pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
+ bDocRequestSent = true;
+ }
+ }
+
+ if ( !aCmdLineArgs->IsQuickstart() ) {
+ bool bShowHelp = false;
+ OUStringBuffer aHelpURLBuffer;
+ if (aCmdLineArgs->IsHelpWriter()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
+ } else if (aCmdLineArgs->IsHelpCalc()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
+ } else if (aCmdLineArgs->IsHelpDraw()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
+ } else if (aCmdLineArgs->IsHelpImpress()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
+ } else if (aCmdLineArgs->IsHelpBase()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
+ } else if (aCmdLineArgs->IsHelpBasic()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
+ } else if (aCmdLineArgs->IsHelpMath()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
+ }
+ if (bShowHelp) {
+ aHelpURLBuffer.append("?Language="
+ + utl::ConfigManager::getUILocale()
+#if defined UNX
+ + "&System=UNX");
+#elif defined _WIN32
+ + "&System=WIN");
+#endif
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::OpenHelpUrl,
+ aHelpURLBuffer.makeStringAndClear());
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ }
+
+ if ( bDocRequestSent )
+ {
+ // Send requests to dispatch watcher if we have at least one. The receiver
+ // is responsible to delete the request after processing it.
+ if ( aCmdLineArgs->HasModuleParam() )
+ {
+ SvtModuleOptions aOpt;
+
+ // Support command line parameters to start a module (as preselection)
+ if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
+ else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
+ else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
+ }
+
+ ImplPostProcessDocumentsEvent( std::move(pRequest) );
+ }
+ else
+ {
+ // delete not used request again
+ pRequest.reset();
+ }
+ if (aCmdLineArgs->IsEmpty())
+ {
+ // no document was sent, just bring Office to front
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::Appear);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ }
+ *waitProcessed = bDocRequestSent;
+ return true;
+}
+
+void PipeIpcThread::execute()
+{
+ assert(m_handler != nullptr);
+ do
+ {
+ osl::StreamPipe aStreamPipe;
+ oslPipeError nError = pipe_.accept( aStreamPipe );
+
+
+ if( nError == osl_Pipe_E_None )
+ {
+ // if we receive a request while the office is displaying some dialog or error during
+ // bootstrap, that dialogs event loop might get events that are dispatched by this thread
+ // we have to wait for cReady to be set by the real main loop.
+ // only requests that don't dispatch events may be processed before cReady is set.
+ m_handler->cReady.wait();
+
+ // we might have decided to shutdown while we were sleeping
+ if (!RequestHandler::pGlobal.is()) return;
+
+ // only lock the mutex when processing starts, otherwise we deadlock when the office goes
+ // down during wait
+ osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
+
+ if (m_handler->mState == RequestHandler::State::Downing)
+ {
+ break;
+ }
+
+ // notify client we're ready to process its args:
+ SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
+ std::size_t n = aStreamPipe.write(
+ SEND_ARGUMENTS, std::size(SEND_ARGUMENTS));
+ // incl. terminating NUL
+ if (n != std::size(SEND_ARGUMENTS)) {
+ SAL_WARN("desktop.app", "short write: " << n);
+ continue;
+ }
+
+ OString aArguments = readStringFromPipe(aStreamPipe);
+
+ // Is this a lookup message from another application? if so, ignore
+ if (aArguments.isEmpty())
+ continue;
+
+ bool waitProcessed = false;
+ if (!process(aArguments, &waitProcessed)) {
+ continue;
+ }
+
+ // we don't need the mutex any longer...
+ aGuard.clear();
+ bool bSuccess = true;
+ // wait for processing to finish
+ if (waitProcessed)
+ {
+ m_handler->cProcessed.wait();
+ bSuccess = m_handler->mbSuccess;
+ }
+ if (bSuccess)
+ {
+ // processing finished, inform the requesting end:
+ SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
+ n = aStreamPipe.write(PROCESSING_DONE, std::size(PROCESSING_DONE));
+ // incl. terminating NUL
+ if (n != std::size(PROCESSING_DONE))
+ {
+ SAL_WARN("desktop.app", "short write: " << n);
+ continue;
+ }
+ }
+ }
+ else
+ {
+ {
+ osl::MutexGuard aGuard( RequestHandler::GetMutex() );
+ if (m_handler->mState == RequestHandler::State::Downing)
+ {
+ break;
+ }
+ }
+
+ SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
+ std::this_thread::sleep_for( std::chrono::seconds(1) );
+ }
+ } while( schedule() );
+}
+
+static void AddToDispatchList(
+ std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
+ std::optional< OUString > const & cwdUrl,
+ std::vector< OUString > const & aRequestList,
+ DispatchWatcher::RequestType nType,
+ const OUString& aParam,
+ const OUString& aFactory )
+{
+ for (auto const& request : aRequestList)
+ {
+ rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
+ }
+}
+
+static void AddConversionsToDispatchList(
+ std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
+ std::optional< OUString > const & cwdUrl,
+ std::vector< OUString > const & rRequestList,
+ const OUString& rParam,
+ const OUString& rPrinterName,
+ const OUString& rFactory,
+ const OUString& rParamOut,
+ std::u16string_view rImgOut,
+ const bool isTextCat,
+ const bool isScriptCat )
+{
+ DispatchWatcher::RequestType nType;
+ OUString aParam( rParam );
+
+ if( !rParam.isEmpty() )
+ {
+ if ( isTextCat )
+ nType = DispatchWatcher::REQUEST_CAT;
+ else
+ nType = DispatchWatcher::REQUEST_CONVERSION;
+ }
+ else
+ {
+ if ( isScriptCat )
+ nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
+ else
+ {
+ nType = DispatchWatcher::REQUEST_BATCHPRINT;
+ aParam = rPrinterName;
+ }
+ }
+
+ OUString aPWD;
+ if (cwdUrl)
+ {
+ aPWD = *cwdUrl;
+ }
+ else
+ {
+ utl::Bootstrap::getProcessWorkingDir( aPWD );
+ }
+
+ if (OUString aOutDir(rParamOut.trim()); !aOutDir.isEmpty())
+ {
+ if (osl::FileBase::getAbsoluteFileURL(aPWD, rParamOut, aOutDir) == osl::FileBase::E_None)
+ osl::FileBase::getSystemPathFromFileURL(aOutDir, aOutDir);
+ aParam += ";" + aOutDir;
+ }
+ else
+ {
+ ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
+ aParam += ";" + aPWD;
+ }
+
+ if( !rImgOut.empty() )
+ aParam += OUString::Concat("|") + o3tl::trim(rImgOut);
+
+ for (auto const& request : rRequestList)
+ {
+ rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
+ }
+}
+
+namespace {
+
+struct ConditionSetGuard
+{
+ osl::Condition* m_pCondition;
+ ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
+ ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
+};
+
+}
+
+bool RequestHandler::ExecuteCmdLineRequests(
+ ProcessDocumentsRequest& aRequest, bool noTerminate)
+{
+ // protect the dispatch list
+ osl::ClearableMutexGuard aGuard( GetMutex() );
+
+ // ensure that Processed flag (if exists) is signaled in any outcome
+ ConditionSetGuard aSetGuard(aRequest.pcProcessed);
+
+ static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
+
+ // Create dispatch list for dispatch watcher
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, "", aRequest.aModule );
+ AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
+ bool bShutdown( false );
+
+ if ( pGlobal.is() )
+ {
+ if( ! pGlobal->AreRequestsEnabled() )
+ {
+ // Either starting, or downing - do not process the request, just try to bring Office to front
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::Appear);
+ ImplPostForeignAppEvent(pAppEvent);
+ return bShutdown;
+ }
+
+ pGlobal->mnPendingRequests += aDispatchList.size();
+ if ( !pGlobal->mpDispatchWatcher.is() )
+ {
+ pGlobal->mpDispatchWatcher = new DispatchWatcher;
+ }
+ rtl::Reference<DispatchWatcher> dispatchWatcher(
+ pGlobal->mpDispatchWatcher);
+
+ // copy for execute
+ std::vector<DispatchWatcher::DispatchRequest> aTempList;
+ aTempList.swap( aDispatchList );
+
+ aGuard.clear();
+
+ // Execute dispatch requests
+ bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
+ if (aRequest.mpbSuccess)
+ *aRequest.mpbSuccess = true; // signal that we have actually succeeded
+ }
+
+ return bShutdown;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/officeipcthread.hxx b/desktop/source/app/officeipcthread.hxx
new file mode 100644
index 0000000000..a233c18e01
--- /dev/null
+++ b/desktop/source/app/officeipcthread.hxx
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <utility>
+#include <vector>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <osl/signal.h>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <osl/conditn.hxx>
+#include <optional>
+
+namespace desktop
+{
+
+oslSignalAction SalMainPipeExchangeSignal_impl(void* /*pData*/, oslSignalInfo* pInfo);
+
+// A request for the current office
+// that was given by command line or by IPC pipe communication.
+struct ProcessDocumentsRequest
+{
+ explicit ProcessDocumentsRequest(std::optional< OUString > cwdUrl):
+ aCwdUrl(std::move(cwdUrl)), pcProcessed( nullptr ), bTextCat( false ), bScriptCat( false ) {}
+
+ std::optional< OUString > aCwdUrl;
+ OUString aModule;
+ std::vector< OUString > aOpenList; // Documents that should be opened in the default way
+ std::vector< OUString > aViewList; // Documents that should be opened in viewmode
+ std::vector< OUString > aStartList; // Documents/Presentations that should be started
+ std::vector< OUString > aPrintList; // Documents that should be printed on default printer
+ std::vector< OUString > aForceOpenList; // Documents that should be forced to open for editing (even templates)
+ std::vector< OUString > aForceNewList; // Documents that should be forced to create a new document
+ OUString aPrinterName; // The printer name that should be used for printing
+ std::vector< OUString > aPrintToList; // Documents that should be printed on the given printer
+ std::vector< OUString > aConversionList;
+ OUString aConversionParams;
+ OUString aConversionOut;
+ OUString aImageConversionType;
+ std::vector< OUString > aInFilter;
+ ::osl::Condition *pcProcessed; // pointer condition to be set when the request has been processed
+ bool* mpbSuccess = nullptr; // pointer to boolean receiving if the processing was successful
+ bool bTextCat; // boolean flag indicating whether to dump text content to console
+ bool bScriptCat; // boolean flag indicating whether to dump script content to console
+};
+
+class DispatchWatcher;
+class IpcThread;
+class PipeIpcThread;
+class DbusIpcThread;
+
+class RequestHandler: public salhelper::SimpleReferenceObject
+{
+ friend IpcThread;
+ friend PipeIpcThread;
+ friend DbusIpcThread;
+
+ private:
+ static rtl::Reference< RequestHandler > pGlobal;
+
+ enum class State { Starting, RequestsEnabled, Downing };
+
+ State mState;
+ int mnPendingRequests;
+ rtl::Reference<DispatchWatcher> mpDispatchWatcher;
+ rtl::Reference<IpcThread> mIpcThread;
+
+ /* condition to be set when the request has been processed */
+ ::osl::Condition cProcessed;
+ /* receives if the processing was successful (may be false e.g. when shutting down) */
+ bool mbSuccess = false;
+
+ /* condition to be set when the main event loop is ready
+ otherwise an error dialogs event loop could eat away
+ requests from a 2nd office */
+ ::osl::Condition cReady;
+
+ static ::osl::Mutex& GetMutex();
+
+ RequestHandler();
+
+ virtual ~RequestHandler() override;
+
+ public:
+ enum Status
+ {
+ IPC_STATUS_OK,
+ IPC_STATUS_2ND_OFFICE,
+ IPC_STATUS_PIPE_ERROR,
+ IPC_STATUS_BOOTSTRAP_ERROR
+ };
+
+ // controlling pipe communication during shutdown
+ static void SetDowning();
+ static void EnableRequests();
+ static bool AreRequestsPending();
+ static void RequestsCompleted();
+ static bool ExecuteCmdLineRequests(
+ ProcessDocumentsRequest&, bool noTerminate);
+
+ // return sal_False if second office
+ static Status Enable(bool ipc);
+ static void Disable();
+ // start dispatching events...
+ static void SetReady(bool bIsReady);
+ static void WaitForReady();
+
+ bool AreRequestsEnabled() const { return mState == State::RequestsEnabled; }
+};
+
+
+class RequestHandlerController : public ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::frame::XTerminateListener >
+{
+ public:
+ RequestHandlerController() {}
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/opencl.cxx b/desktop/source/app/opencl.cxx
new file mode 100644
index 0000000000..ad3df6bf3f
--- /dev/null
+++ b/desktop/source/app/opencl.cxx
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/*
+ * This module exists to validate the OpenCL implementation,
+ * where necessary during startup; and before we load or
+ * calculate using OpenCL.
+ */
+
+#include <app.hxx>
+
+#include <config_version.h>
+#include <config_feature_opencl.h>
+#include <config_folders.h>
+
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Calc.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <comphelper/propertyvalue.hxx>
+#include <svl/documentlockfile.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <com/sun/star/table/XCell2.hpp>
+#include <com/sun/star/sheet/XCalculatable.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <com/sun/star/sheet/XSpreadsheets.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+
+#if HAVE_FEATURE_OPENCL
+#include <opencl/openclwrapper.hxx>
+#endif
+#include <opencl/OpenCLZone.hxx>
+
+#include <osl/file.hxx>
+#include <osl/process.h>
+
+using namespace ::osl;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::frame;
+
+namespace desktop {
+
+#if HAVE_FEATURE_OPENCL
+
+static bool testOpenCLDriver()
+{
+ // A simple OpenCL test run in a separate process in order to test
+ // whether the driver crashes (asserts,etc.) when trying to use OpenCL.
+ SAL_INFO("opencl", "Starting CL driver test");
+
+ OUString testerURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest");
+ rtl::Bootstrap::expandMacros(testerURL); //TODO: detect failure
+
+ OUString deviceName, platformName;
+ openclwrapper::getOpenCLDeviceName( deviceName, platformName );
+ rtl_uString* args[] = { deviceName.pData, platformName.pData };
+ sal_Int32 numArgs = 2;
+
+ oslProcess process;
+ oslSecurity security = osl_getCurrentSecurity();
+ oslProcessError error = osl_executeProcess(testerURL.pData, args, numArgs,
+ osl_Process_SEARCHPATH | osl_Process_HIDDEN, security,
+ nullptr, nullptr, 0, &process );
+ osl_freeSecurityHandle( security );
+ if( error != osl_Process_E_None )
+ {
+ SAL_WARN( "opencl", "failed to start CL driver test: " << error );
+ return false;
+ }
+ // If the driver takes more than 10 seconds, it's probably broken/useless.
+ TimeValue timeout( 10, 0 );
+ error = osl_joinProcessWithTimeout( process, &timeout );
+ if( error == osl_Process_E_None )
+ {
+ oslProcessInfo info;
+ info.Size = sizeof( info );
+ error = osl_getProcessInfo( process, osl_Process_EXITCODE, &info );
+ if( error == osl_Process_E_None )
+ {
+ if( info.Code == 0 )
+ {
+ SAL_INFO( "opencl", "CL driver test passed" );
+ osl_freeProcessHandle( process );
+ return true;
+ }
+ else
+ {
+ SAL_WARN( "opencl", "CL driver test failed - disabling: " << info.Code );
+ osl_freeProcessHandle( process );
+ return false;
+ }
+ }
+ }
+ SAL_WARN( "opencl", "CL driver test did not finish - disabling: " << error );
+ osl_terminateProcess( process );
+ osl_freeProcessHandle( process );
+ return false;
+}
+
+static bool testOpenCLCompute(const Reference< XDesktop2 > &xDesktop, const OUString &rURL)
+{
+ bool bSuccess = false;
+ css::uno::Reference< css::lang::XComponent > xComponent;
+
+ sal_uInt64 nKernelFailures = openclwrapper::kernelFailures;
+
+ SAL_INFO("opencl", "Starting CL test spreadsheet");
+
+ // A stale lock file would make the loading fail, so make sure to remove it.
+ try {
+ ::svt::DocumentLockFile lockFile( rURL );
+ lockFile.RemoveFileDirectly();
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+
+ try {
+ css::uno::Reference< css::frame::XComponentLoader > xLoader(xDesktop, css::uno::UNO_QUERY_THROW);
+
+ css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue("Hidden",
+ true) };
+
+ xComponent.set(xLoader->loadComponentFromURL(rURL, "_blank", 0, aArgs));
+
+ // What an unpleasant API to use.
+ css::uno::Reference< css::sheet::XCalculatable > xCalculatable( xComponent, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::sheet::XSpreadsheetDocument > xSpreadDoc( xComponent, css::uno::UNO_QUERY_THROW );
+ css::uno::Reference< css::sheet::XSpreadsheets > xSheets( xSpreadDoc->getSheets(), css::uno::UNO_SET_THROW );
+ css::uno::Reference< css::container::XIndexAccess > xIndex( xSheets, css::uno::UNO_QUERY_THROW );
+ css::uno::Reference< css::sheet::XSpreadsheet > xSheet( xIndex->getByIndex(0), css::uno::UNO_QUERY_THROW);
+
+ // So we insert our MAX call at the end on a named range.
+ css::uno::Reference< css::table::XCell2 > xThresh( xSheet->getCellByPosition(1,1), css::uno::UNO_QUERY_THROW ); // B2
+ double fThreshold = xThresh->getValue();
+
+ // We need pure OCL formulae all the way through the
+ // dependency chain, or we fall-back.
+ xCalculatable->calculateAll();
+
+ // So we insert our MAX call at the end on a named range.
+ css::uno::Reference< css::table::XCell2 > xCell( xSheet->getCellByPosition(1,0), css::uno::UNO_QUERY_THROW );
+ xCell->setFormula("=MAX(results)");
+ double fResult = xCell->getValue();
+
+ // Ensure the maximum variance is below our tolerance.
+ if (fResult > fThreshold)
+ {
+ SAL_WARN("opencl", "OpenCL results unstable - disabling; result: "
+ << fResult << " vs. " << fThreshold);
+ }
+ else
+ {
+ SAL_INFO("opencl", "calculating smoothly; result: " << fResult);
+ bSuccess = true;
+ }
+ }
+ catch (const css::uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("opencl", "OpenCL testing failed - disabling");
+ }
+
+ if (nKernelFailures != openclwrapper::kernelFailures)
+ {
+ // tdf#100883 - defeat SEH exception handling fallbacks.
+ SAL_WARN("opencl", "OpenCL kernels failed to compile, "
+ "or took SEH exceptions "
+ << nKernelFailures << " != " << openclwrapper::kernelFailures);
+ bSuccess = false;
+ }
+
+ if (!bSuccess)
+ OpenCLZone::hardDisable();
+ if (xComponent.is())
+ xComponent->dispose();
+
+
+ return bSuccess;
+}
+
+void Desktop::CheckOpenCLCompute(const Reference< XDesktop2 > &xDesktop)
+{
+ if (!openclwrapper::canUseOpenCL() || Application::IsSafeModeEnabled())
+ return;
+
+ SAL_INFO("opencl", "Initiating test of OpenCL device");
+ OpenCLZone aZone;
+ OpenCLInitialZone aInitialZone;
+
+ OUString aDevice = officecfg::Office::Calc::Formula::Calculation::OpenCLDevice::get();
+ OUString aSelectedCLDeviceVersionID;
+ if (!openclwrapper::switchOpenCLDevice(
+ aDevice,
+ officecfg::Office::Calc::Formula::Calculation::OpenCLAutoSelect::get(),
+ false /* bForceEvaluation */,
+ aSelectedCLDeviceVersionID))
+ {
+ SAL_WARN("opencl", "Failed to initialize OpenCL for test");
+ OpenCLZone::hardDisable();
+ return;
+ }
+
+ // Append our app version as well.
+ aSelectedCLDeviceVersionID += "--" LIBO_VERSION_DOTTED;
+
+ // Append timestamp of the file.
+ OUString aURL("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/opencl/cl-test.ods");
+ rtl::Bootstrap::expandMacros(aURL);
+
+ DirectoryItem aItem;
+ (void)DirectoryItem::get( aURL, aItem );
+ FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
+ (void)aItem.getFileStatus( aFileStatus );
+ TimeValue aTimeVal = aFileStatus.getModifyTime();
+ aSelectedCLDeviceVersionID += "--" +
+ OUString::number(aTimeVal.Seconds);
+
+ if (aSelectedCLDeviceVersionID == officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::get())
+ return;
+
+ // OpenCL device changed - sanity check it and disable if bad.
+
+ sal_Int32 nOrigMinimumSize = officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::get();
+ { // set the minimum group size to something small for quick testing.
+ std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(3 /* small */, xBatch);
+ xBatch->commit();
+ }
+
+ // Hopefully at least basic functionality always works and broken OpenCL implementations break
+ // only when they are used to compute something. If this assumptions turns out to be not true,
+ // the driver check needs to be moved sooner.
+ bool bSucceeded = testOpenCLDriver() && testOpenCLCompute(xDesktop, aURL);
+
+ { // restore the minimum group size
+ std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(nOrigMinimumSize, xBatch);
+ officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::set(aSelectedCLDeviceVersionID, xBatch);
+ xBatch->commit();
+ }
+
+ if (!bSucceeded)
+ OpenCLZone::hardDisable();
+}
+#endif // HAVE_FEATURE_OPENCL
+
+} // end namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/desktop/source/app/sofficemain.cxx b/desktop/source/app/sofficemain.cxx
new file mode 100644
index 0000000000..0b02155f59
--- /dev/null
+++ b/desktop/source/app/sofficemain.cxx
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <config_features.h>
+
+#include <desktop/dllapi.h>
+
+#include <app.hxx>
+#include "cmdlineargs.hxx"
+#include "cmdlinehelp.hxx"
+
+// needed before sal/main.h to avoid redefinition of macros
+#include <prewin.h>
+
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <sal/main.h>
+#include <tools/extendapplicationenvironment.hxx>
+#include <vcl/svmain.hxx>
+
+#if HAVE_FEATURE_BREAKPAD
+#include <desktop/crashreport.hxx>
+#endif
+
+#include <postwin.h>
+
+#ifdef ANDROID
+# include <jni.h>
+# include <android/log.h>
+# include <salhelper/thread.hxx>
+
+# define LOGTAG "LibreOffice/sofficemain"
+# define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOGTAG, __VA_ARGS__))
+#endif
+
+extern "C" int DESKTOP_DLLPUBLIC soffice_main()
+{
+ sal_detail_initialize(sal::detail::InitializeSoffice, nullptr);
+
+#if HAVE_FEATURE_BREAKPAD
+ CrashReporter::installExceptionHandler();
+#endif
+
+#if defined ANDROID
+ try {
+ rtl::Bootstrap::setIniFilename("file:///assets/program/lofficerc");
+#endif
+ tools::extendApplicationEnvironment();
+
+ desktop::Desktop aDesktop;
+ // This string is used during initialization of the Gtk+ VCL module
+ Application::SetAppName( "soffice" );
+
+ // handle --version and --help already here, otherwise they would be handled
+ // after VCL initialization that might fail if $DISPLAY is not set
+ const desktop::CommandLineArgs& rCmdLineArgs = desktop::Desktop::GetCommandLineArgs();
+ const OUString& aUnknown( rCmdLineArgs.GetUnknown() );
+ if ( !aUnknown.isEmpty() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayCmdlineHelp( aUnknown );
+ return EXIT_FAILURE;
+ }
+ if ( rCmdLineArgs.IsHelp() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayCmdlineHelp( OUString() );
+ return EXIT_SUCCESS;
+ }
+ if ( rCmdLineArgs.IsVersion() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayVersion();
+ return EXIT_SUCCESS;
+ }
+
+ return SVMain();
+#if defined ANDROID
+ } catch (const css::uno::Exception &e) {
+ LOGI("Unhandled UNO exception: '%s'",
+ OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr());
+ throw; // to get exception type printed
+ }
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/sofficemain.h b/desktop/source/app/sofficemain.h
new file mode 100644
index 0000000000..9bc6dafee3
--- /dev/null
+++ b/desktop/source/app/sofficemain.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <desktop/dllapi.h>
+
+#if defined __cplusplus
+extern "C" {
+#endif
+
+int DESKTOP_DLLPUBLIC soffice_main(void);
+
+#if defined __cplusplus
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/updater.cxx b/desktop/source/app/updater.cxx
new file mode 100644
index 0000000000..12bb4969a6
--- /dev/null
+++ b/desktop/source/app/updater.cxx
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "updater.hxx"
+
+#if UNX
+#include <unistd.h>
+#include <errno.h>
+
+#endif
+
+#ifdef _WIN32
+#include <comphelper/windowsStart.hxx>
+#endif
+
+#include <fstream>
+#include <config_folders.h>
+#include <rtl/bootstrap.hxx>
+
+#include <officecfg/Office/Update.hxx>
+
+#include <rtl/ustring.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/configmgr.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <osl/file.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <curl/curl.h>
+
+#include <orcus/json_document_tree.hpp>
+#include <orcus/config.hpp>
+
+#include <systools/curlinit.hxx>
+#include <comphelper/hash.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+
+#include <officecfg/Setup.hxx>
+
+#include <functional>
+#include <memory>
+#include <set>
+#include <string_view>
+
+namespace {
+
+class error_updater : public std::exception
+{
+ OString maStr;
+public:
+
+ error_updater(const OString& rStr):
+ maStr(rStr)
+ {
+ }
+
+ virtual const char* what() const throw() override
+ {
+ return maStr.getStr();
+ }
+};
+
+#ifdef UNX
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (Linux)";
+#else
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (unknown platform)";
+#endif
+
+#ifdef UNX
+const char* const pUpdaterName = "updater";
+const char* const pSofficeExeName = "soffice";
+#elif defined(_WIN32)
+const char* pUpdaterName = "updater.exe";
+const char* pSofficeExeName = "soffice.exe";
+#else
+#error "Need implementation"
+#endif
+
+OUString normalizePath(const OUString& rPath)
+{
+ OUString aPath = rPath;
+#if defined WNT
+ aPath = aPath.replace('\\', '/');
+#endif
+
+ aPath = aPath.replaceAll("//", "/");
+
+ // remove final /
+ if (aPath.endsWith("/"))
+ {
+ aPath = aPath.copy(0, aPath.getLength() - 1);
+ }
+
+ while (aPath.indexOf("/..") != -1)
+ {
+ sal_Int32 nIndex = aPath.indexOf("/..");
+ sal_Int32 i = nIndex - 1;
+ for (; i > 0; --i)
+ {
+ if (aPath[i] == '/')
+ break;
+ }
+
+ OUString aTempPath = aPath;
+ aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3);
+ }
+
+#if defined WNT
+ aPath = aPath.replace('/', '\\');
+#endif
+ return aPath;
+}
+
+void CopyFileToDir(const OUString& rTempDirURL, const OUString & rFileName, const OUString& rOldDir)
+{
+ OUString aSourceURL = rOldDir + "/" + rFileName;
+ OUString aDestURL = rTempDirURL + "/" + rFileName;
+
+ osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL);
+ if (eError != osl::File::E_None)
+ {
+ SAL_WARN("desktop.updater", "could not copy the file to a temp directory: " << rFileName);
+ throw std::exception();
+ }
+}
+
+OUString getPathFromURL(const OUString& rURL)
+{
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(rURL, aPath);
+
+ return normalizePath(aPath);
+}
+
+void CopyUpdaterToTempDir(const OUString& rInstallDirURL, const OUString& rTempDirURL)
+{
+ OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
+ CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL);
+ CopyFileToDir(rTempDirURL, u"updater.ini"_ustr, rInstallDirURL);
+}
+
+#ifdef UNX
+typedef char CharT;
+#define tstrncpy std::strncpy
+char const * toStream(char const * s) { return s; }
+#elif defined(_WIN32)
+typedef wchar_t CharT;
+#define tstrncpy std::wcsncpy
+OUString toStream(wchar_t const * s) { return OUString(o3tl::toU(s)); }
+#else
+#error "Need an implementation"
+#endif
+
+void createStr(const OUString& rStr, CharT** pArgs, size_t i)
+{
+#ifdef UNX
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+#elif defined(_WIN32)
+ OUString aStr = rStr;
+#else
+#error "Need an implementation"
+#endif
+ CharT* pStr = new CharT[aStr.getLength() + 1];
+ tstrncpy(pStr, (CharT*)aStr.getStr(), aStr.getLength());
+ pStr[aStr.getLength()] = '\0';
+ pArgs[i] = pStr;
+}
+
+CharT** createCommandLine(OUString const & argv0, int * argc)
+{
+ OUString aInstallDir = Updater::getInstallationPath();
+
+ size_t nCommandLineArgs = rtl_getAppCommandArgCount();
+ size_t nArgs = 8 + nCommandLineArgs;
+ CharT** pArgs = new CharT*[nArgs];
+ createStr(argv0, pArgs, 0);
+ {
+ // directory with the patch log
+ OUString aPatchDir = Updater::getPatchDirURL();
+ rtl::Bootstrap::expandMacros(aPatchDir);
+ OUString aTempDirPath = getPathFromURL(aPatchDir);
+ Updater::log("Patch Dir: " + aTempDirPath);
+ createStr(aTempDirPath, pArgs, 1);
+ }
+ {
+ // the actual update directory
+ Updater::log("Install Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 2);
+ }
+ {
+ // the temporary updated build
+ Updater::log("Working Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 3);
+ }
+ {
+#ifdef UNX
+ OUString aPID("0");
+#elif defined(_WIN32)
+ oslProcessInfo aInfo;
+ aInfo.Size = sizeof(oslProcessInfo);
+ osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aInfo);
+ OUString aPID = OUString::number(aInfo.Ident);
+#else
+#error "Need an implementation"
+#endif
+ createStr(aPID, pArgs, 4);
+ }
+ {
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePath = getPathFromURL(aExeDir);
+ Updater::log("soffice Path: " + aSofficePath);
+ createStr(aSofficePath, pArgs, 5);
+ }
+ {
+ // the executable to start after the successful update
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName);
+ OUString aSofficePath = getPathFromURL(aSofficePathURL);
+ createStr(aSofficePath, pArgs, 6);
+ }
+
+ // add the command line arguments from the soffice list
+ for (size_t i = 0; i < nCommandLineArgs; ++i)
+ {
+ OUString aCommandLineArg;
+ rtl_getAppCommandArg(i, &aCommandLineArg.pData);
+ createStr(aCommandLineArg, pArgs, 7 + i);
+ }
+
+ pArgs[nArgs - 1] = nullptr;
+
+ *argc = nArgs - 1;
+ return pArgs;
+}
+
+struct update_file
+{
+ OUString aURL;
+ OUString aHash;
+ size_t nSize;
+};
+
+struct language_file
+{
+ update_file aUpdateFile;
+ OUString aLangCode;
+};
+
+struct update_info
+{
+ OUString aFromBuildID;
+ OUString aSeeAlsoURL;
+ OUString aMessage;
+
+ update_file aUpdateFile;
+ std::vector<language_file> aLanguageFiles;
+};
+
+bool isUserWritable(const OUString& rFileURL)
+{
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ osl::DirectoryItem aDirectoryItem;
+
+ osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem);
+ if (eRes != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the directory item for: " + rFileURL);
+ return false;
+ }
+
+ osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus);
+ if (eResult != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the file status for: " + rFileURL);
+ return false;
+ }
+
+ bool bReadOnly = (aStatus.getAttributes() & static_cast<sal_uInt64>(osl_File_Attribute_ReadOnly)) != 0;
+ if (bReadOnly)
+ {
+ Updater::log("Update location as determined by: " + rFileURL + " is read-only.");
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+bool update()
+{
+ utl::TempFileNamed aTempDir(nullptr, true);
+ OUString aTempDirURL = aTempDir.GetURL();
+ CopyUpdaterToTempDir(Updater::getExecutableDirURL(), aTempDirURL);
+
+ OUString aUpdaterPath = getPathFromURL(aTempDirURL + "/" + OUString::fromUtf8(pUpdaterName));
+
+ Updater::log("Calling the updater with parameters: ");
+ int argc;
+ CharT** pArgs = createCommandLine(aUpdaterPath, &argc);
+
+ bool bSuccess = true;
+ const char* pUpdaterTestReplace = std::getenv("LIBO_UPDATER_TEST_REPLACE");
+ if (!pUpdaterTestReplace)
+ {
+#if UNX
+ OString aPath = OUStringToOString(aUpdaterPath, RTL_TEXTENCODING_UTF8);
+ if (execv(aPath.getStr(), pArgs))
+ {
+ printf("execv failed with error %d %s\n",errno,strerror(errno));
+ bSuccess = false;
+ }
+#elif defined(_WIN32)
+ bSuccess = WinLaunchChild((wchar_t*)aUpdaterPath.getStr(), argc, pArgs);
+#endif
+ }
+ else
+ {
+ SAL_WARN("desktop.updater", "Updater executable path: " << aUpdaterPath);
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ SAL_WARN("desktop.updater", toStream(pArgs[i]));
+ }
+ bSuccess = false;
+ }
+
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ delete[] pArgs[i];
+ }
+ delete[] pArgs;
+
+ return bSuccess;
+}
+
+namespace {
+
+// Callback to get the response data from server.
+size_t WriteCallback(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ std::string* response = static_cast<std::string *>(userp);
+ size_t real_size = size * nmemb;
+ response->append(static_cast<char *>(ptr), real_size);
+ return real_size;
+}
+
+
+
+class invalid_update_info : public std::exception
+{
+};
+
+class invalid_hash : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_hash(const OUString& rExpectedHash, const OUString& rReceivedHash)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid hash found.\nExpected: " + rExpectedHash + ";\nReceived: " + rReceivedHash),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+class invalid_size : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_size(const size_t nExpectedSize, const size_t nReceivedSize)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) + ";\nReceived: " + OUString::number(nReceivedSize)),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+OUString toOUString(const std::string_view& rStr)
+{
+ return OUString::fromUtf8(rStr);
+}
+
+update_file parse_update_file(orcus::json::node& rNode)
+{
+ if (rNode.type() != orcus::json::node_t::object)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ if (rNode.child_count() < 4)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aURLNode = rNode.child("url");
+ orcus::json::node aHashNode = rNode.child("hash");
+ orcus::json::node aHashTypeNode = rNode.child("hash_function");
+ orcus::json::node aSizeNode = rNode.child("size");
+
+ if (aHashTypeNode.string_value() != "sha512")
+ {
+ SAL_WARN("desktop.updater", "invalid hash type");
+ throw invalid_update_info();
+ }
+
+ update_file aUpdateFile;
+ aUpdateFile.aURL = toOUString(aURLNode.string_value());
+
+ if (aUpdateFile.aURL.isEmpty())
+ throw invalid_update_info();
+
+ aUpdateFile.aHash = toOUString(aHashNode.string_value());
+ aUpdateFile.nSize = static_cast<sal_uInt32>(aSizeNode.numeric_value());
+ return aUpdateFile;
+}
+
+update_info parse_response(const std::string& rResponse)
+{
+ orcus::json::document_tree aJsonDoc;
+ orcus::json_config aConfig;
+ aJsonDoc.load(rResponse, aConfig);
+
+ auto aDocumentRoot = aJsonDoc.get_document_root();
+ if (aDocumentRoot.type() != orcus::json::node_t::object)
+ {
+ SAL_WARN("desktop.updater", "invalid root entries: " << rResponse);
+ throw invalid_update_info();
+ }
+
+ auto aRootKeys = aDocumentRoot.keys();
+ if (std::find(aRootKeys.begin(), aRootKeys.end(), "error") != aRootKeys.end())
+ {
+ throw invalid_update_info();
+ }
+ else if (std::find(aRootKeys.begin(), aRootKeys.end(), "response") != aRootKeys.end())
+ {
+ update_info aUpdateInfo;
+ auto aMsgNode = aDocumentRoot.child("response");
+ aUpdateInfo.aMessage = toOUString(aMsgNode.string_value());
+ return aUpdateInfo;
+ }
+
+ orcus::json::node aFromNode = aDocumentRoot.child("from");
+ if (aFromNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aSeeAlsoNode = aDocumentRoot.child("see also");
+ if (aSeeAlsoNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aUpdateNode = aDocumentRoot.child("update");
+ if (aUpdateNode.type() != orcus::json::node_t::object)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aLanguageNode = aDocumentRoot.child("languages");
+ if (aLanguageNode.type() != orcus::json::node_t::object)
+ {
+ throw invalid_update_info();
+ }
+
+ update_info aUpdateInfo;
+ aUpdateInfo.aFromBuildID = toOUString(aFromNode.string_value());
+ aUpdateInfo.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value());
+
+ aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode);
+
+ std::vector<std::string_view> aLanguages = aLanguageNode.keys();
+ for (auto const& language : aLanguages)
+ {
+ language_file aLanguageFile;
+ auto aLangEntry = aLanguageNode.child(language);
+ aLanguageFile.aLangCode = toOUString(language);
+ aLanguageFile.aUpdateFile = parse_update_file(aLangEntry);
+ aUpdateInfo.aLanguageFiles.push_back(aLanguageFile);
+ }
+
+ return aUpdateInfo;
+}
+
+struct WriteDataFile
+{
+ comphelper::Hash maHash;
+ SvStream* mpStream;
+
+ WriteDataFile(SvStream* pStream):
+ maHash(comphelper::HashType::SHA512),
+ mpStream(pStream)
+ {
+ }
+
+ OUString getHash()
+ {
+ auto final_hash = maHash.finalize();
+ std::stringstream aStrm;
+ for (auto& i: final_hash)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << (int)i;
+ }
+
+ return toOUString(aStrm.str());
+ }
+};
+
+// Callback to get the response data from server to a file.
+size_t WriteCallbackFile(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ WriteDataFile* response = static_cast<WriteDataFile *>(userp);
+ size_t real_size = size * nmemb;
+ response->mpStream->WriteBytes(ptr, real_size);
+ response->maHash.update(static_cast<const unsigned char*>(ptr), real_size);
+ return real_size;
+}
+
+std::string download_content(const OString& rURL, bool bFile, OUString& rHash)
+{
+ Updater::log("Download: " + rURL);
+ std::unique_ptr<CURL, std::function<void(CURL *)>> curl(
+ curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); });
+
+ if (!curl)
+ return std::string();
+
+ ::InitCurl_easy(curl.get());
+
+ curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr());
+ curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, kUserAgent);
+ bool bUseProxy = false;
+ if (bUseProxy)
+ {
+ /*
+ curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
+ */
+ }
+
+ char buf[] = "Expect:";
+ curl_slist* headerlist = nullptr;
+ headerlist = curl_slist_append(headerlist, buf);
+ curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist);
+ curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); // follow redirects
+ // only allow redirect to https://
+#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR, "https");
+#else
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+
+ std::string response_body;
+ utl::TempFileNamed aTempFile;
+ WriteDataFile aFile(aTempFile.GetStream(StreamMode::WRITE));
+ if (!bFile)
+ {
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
+ static_cast<void *>(&response_body));
+
+ aTempFile.EnableKillingFile(true);
+ }
+ else
+ {
+ OUString aTempFileURL = aTempFile.GetURL();
+ OString aTempFileURLOString = OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8);
+ response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength());
+
+ aTempFile.EnableKillingFile(false);
+
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile);
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
+ static_cast<void *>(&aFile));
+ }
+
+ // Fail if 400+ is returned from the web server.
+ curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1);
+
+ CURLcode cc = curl_easy_perform(curl.get());
+ long http_code = 0;
+ curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 200)
+ {
+ SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code);
+ throw error_updater("download did not succeed");
+ }
+
+ if (cc != CURLE_OK)
+ {
+ SAL_WARN("desktop.updater", "curl error: " << cc);
+ throw error_updater("curl error");
+ }
+
+ if (bFile)
+ rHash = aFile.getHash();
+
+ return response_body;
+}
+
+void handle_file_error(osl::FileBase::RC eError, const OUString& rMsg)
+{
+ switch (eError)
+ {
+ case osl::FileBase::E_None:
+ break;
+ default:
+ SAL_WARN("desktop.updater", "file error code: " << eError << ", " << rMsg);
+ throw error_updater(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8));
+ }
+}
+
+void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName)
+{
+ Updater::log("Download File: " + rURL + "; FileName: " + aFileName);
+ OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ OUString aHash;
+ std::string temp_file = download_content(aURL, true, aHash);
+ if (temp_file.empty())
+ throw error_updater("empty temp file string");
+
+ OUString aTempFile = OUString::fromUtf8(temp_file.c_str());
+ Updater::log("TempFile: " + aTempFile);
+ osl::File aDownloadedFile(aTempFile);
+ osl::FileBase::RC eError = aDownloadedFile.open(1);
+ handle_file_error(eError, "Could not open the download file: " + aTempFile);
+
+ sal_uInt64 nSize = 0;
+ eError = aDownloadedFile.getSize(nSize);
+ handle_file_error(eError, "Could not get the file size of the downloaded file: " + aTempFile);
+ if (nSize != nFileSize)
+ {
+ SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted.");
+ throw invalid_size(nFileSize, nSize);
+ }
+
+ if (aHash != rHash)
+ {
+ SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted.");
+ throw invalid_hash(rHash, aHash);
+ }
+
+ OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/");
+ rtl::Bootstrap::expandMacros(aPatchDirURL);
+ osl::Directory::create(aPatchDirURL);
+ aPatchDirURL += "0/";
+ osl::Directory::create(aPatchDirURL);
+
+ OUString aDestFile = aPatchDirURL + aFileName;
+ Updater::log("Destination File: " + aDestFile);
+ aDownloadedFile.close();
+ eError = osl::File::move(aTempFile, aDestFile);
+ handle_file_error(eError, "Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile + "; DestFile: " + aDestFile);
+}
+
+}
+
+void update_checker()
+{
+ OUString aBrandBaseDir("${BRAND_BASE_DIR}");
+ rtl::Bootstrap::expandMacros(aBrandBaseDir);
+ bool bUserWritable = isUserWritable(aBrandBaseDir);
+ if (!bUserWritable)
+ {
+ Updater::log("Can't update as the update location is not user writable");
+ return;
+ }
+
+ OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get();
+ static const char* pDownloadCheckBaseURLEnv = std::getenv("LIBO_UPDATER_URL");
+ if (pDownloadCheckBaseURLEnv)
+ {
+ aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv);
+ }
+
+ OUString aProductName = utl::ConfigManager::getProductName();
+ OUString aBuildID = Updater::getBuildID();
+
+ static const char* pBuildIdEnv = std::getenv("LIBO_UPDATER_BUILD");
+ if (pBuildIdEnv)
+ {
+ aBuildID = OUString::createFromAscii(pBuildIdEnv);
+ }
+
+ OUString aBuildTarget = "${_OS}_${_ARCH}";
+ rtl::Bootstrap::expandMacros(aBuildTarget);
+ OUString aChannel = Updater::getUpdateChannel();
+ static const char* pUpdateChannelEnv = std::getenv("LIBO_UPDATER_CHANNEL");
+ if (pUpdateChannelEnv)
+ {
+ aChannel = OUString::createFromAscii(pUpdateChannelEnv);
+ }
+
+ OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/check/1/" + aProductName +
+ "/" + aBuildID + "/" + aBuildTarget + "/" + aChannel;
+ OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8);
+ Updater::log("Update check: " + aURL);
+
+ try
+ {
+ OUString aHash;
+ std::string response_body = download_content(aURL, false, aHash);
+ if (!response_body.empty())
+ {
+
+ update_info aUpdateInfo = parse_response(response_body);
+ if (aUpdateInfo.aUpdateFile.aURL.isEmpty())
+ {
+ // No update currently available
+ // add entry to updating.log with the message
+ SAL_WARN("desktop.updater", "Message received from the updater: " << aUpdateInfo.aMessage);
+ Updater::log("Server response: " + aUpdateInfo.aMessage);
+ }
+ else
+ {
+ css::uno::Sequence<OUString> aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ std::set<OUString> aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages));
+ download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar");
+ for (auto& lang_update : aUpdateInfo.aLanguageFiles)
+ {
+ // only download the language packs for installed languages
+ if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end())
+ {
+ OUString aFileName = "update_" + lang_update.aLangCode + ".mar";
+ download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName);
+ }
+ }
+ OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL;
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch);
+ batch->commit();
+ OUString const statUrl = Updater::getPatchDirURL() + "update.status";
+ SvFileStream stat(statUrl, StreamMode::WRITE | StreamMode::TRUNC);
+ stat.WriteOString("pending-service");
+ stat.Flush();
+ if (auto const e = stat.GetError()) {
+ Updater::log("Writing <" + statUrl + "> failed with " + e.toString());
+ }
+ stat.Close();
+ }
+ }
+ }
+ catch (const invalid_update_info&)
+ {
+ SAL_WARN("desktop.updater", "invalid update information");
+ Updater::log(OString("warning: invalid update info"));
+ }
+ catch (const error_updater& e)
+ {
+ SAL_WARN("desktop.updater", "error during the update check: " << e.what());
+ Updater::log(OString("warning: error by the updater") + e.what());
+ }
+ catch (const invalid_size& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid size"));
+ }
+ catch (const invalid_hash& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid hash"));
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.updater", "unknown error during the update check");
+ Updater::log(OString("warning: unknown exception"));
+ }
+}
+
+OUString Updater::getUpdateInfoLog()
+{
+ OUString aUpdateInfoURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/updating.log");
+ rtl::Bootstrap::expandMacros(aUpdateInfoURL);
+
+ return aUpdateInfoURL;
+}
+
+OUString Updater::getPatchDirURL()
+{
+ OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/0/");
+ rtl::Bootstrap::expandMacros(aPatchDirURL);
+
+ return aPatchDirURL;
+}
+
+OUString Updater::getUpdateFileURL()
+{
+ return getPatchDirURL() + "update.mar";
+}
+
+OUString Updater::getInstallationPath()
+{
+ OUString aInstallDir( "$BRAND_BASE_DIR/");
+ rtl::Bootstrap::expandMacros(aInstallDir);
+
+ return getPathFromURL(aInstallDir);
+}
+
+OUString Updater::getExecutableDirURL()
+{
+ OUString aExeDir( "$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/" );
+ rtl::Bootstrap::expandMacros(aExeDir);
+
+ return aExeDir;
+}
+
+void Updater::log(const OUString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(OUStringToOString(rMessage, RTL_TEXTENCODING_UTF8));
+}
+
+void Updater::log(const OString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(rMessage);
+}
+
+void Updater::log(const char* pMessage)
+{
+ SAL_INFO("desktop.updater", pMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteOString(pMessage);
+}
+
+OUString Updater::getBuildID()
+{
+ OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(aBuildID);
+
+ return aBuildID;
+}
+
+OUString Updater::getUpdateChannel()
+{
+ OUString aUpdateChannel("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateChannel}");
+ rtl::Bootstrap::expandMacros(aUpdateChannel);
+
+ return aUpdateChannel;
+}
+
+void Updater::removeUpdateFiles()
+{
+ Updater::log("Removing: " + getUpdateFileURL());
+ osl::File::remove(getUpdateFileURL());
+
+ OUString aPatchDirURL = getPatchDirURL();
+ osl::Directory aDir(aPatchDirURL);
+ aDir.open();
+
+ osl::FileBase::RC eRC;
+ do
+ {
+ osl::DirectoryItem aItem;
+ eRC = aDir.getNextItem(aItem);
+ if (eRC == osl::FileBase::E_None)
+ {
+ osl::FileStatus aStatus(osl_FileStatus_Mask_All);
+ if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None)
+ continue;
+
+ if (!aStatus.isRegular())
+ continue;
+
+ OUString aURL = aStatus.getFileURL();
+ if (!aURL.endsWith(".mar"))
+ continue;
+
+ Updater::log("Removing. " + aURL);
+ osl::File::remove(aURL);
+ }
+ }
+ while (eRC == osl::FileBase::E_None);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/updater.hxx b/desktop/source/app/updater.hxx
new file mode 100644
index 0000000000..7f1ea920fb
--- /dev/null
+++ b/desktop/source/app/updater.hxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+bool update();
+
+void update_checker();
+
+class Updater
+{
+public:
+ static OUString getUpdateInfoLog();
+ static OUString getPatchDirURL();
+ static OUString getUpdateFileURL();
+ static OUString getExecutableDirURL();
+ static OUString getInstallationPath();
+
+ static OUString getBuildID();
+ static OUString getUpdateChannel();
+
+ static void log(const OUString& rMessage);
+ static void log(const OString& rMessage);
+ static void log(const char* pMessage);
+
+ static void removeUpdateFiles();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/userinstall.cxx b/desktop/source/app/userinstall.cxx
new file mode 100644
index 0000000000..7243db43e6
--- /dev/null
+++ b/desktop/source/app/userinstall.cxx
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <comphelper/configuration.hxx>
+#include <config_folders.h>
+#include <officecfg/Setup.hxx>
+#include <osl/file.h>
+#include <osl/file.hxx>
+#if defined ANDROID || defined IOS || defined EMSCRIPTEN
+#include <rtl/bootstrap.hxx>
+#endif
+#include <rtl/ustring.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/bootstrap.hxx>
+
+#include "userinstall.hxx"
+
+namespace desktop::userinstall {
+
+namespace {
+
+#if !(defined ANDROID || defined IOS || defined EMSCRIPTEN)
+osl::FileBase::RC copyRecursive(
+ OUString const & srcUri, OUString const & dstUri)
+{
+ osl::DirectoryItem item;
+ osl::FileBase::RC e = osl::DirectoryItem::get(srcUri, item);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ osl::FileStatus stat1(osl_FileStatus_Mask_Type);
+ e = item.getFileStatus(stat1);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ if (stat1.getFileType() == osl::FileStatus::Directory) {
+ e = osl::Directory::create(dstUri);
+ if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) {
+ return e;
+ }
+ osl::Directory dir(srcUri);
+ e = dir.open();
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ for (;;) {
+ e = dir.getNextItem(item);
+ if (e == osl::FileBase::E_NOENT) {
+ break;
+ }
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ osl::FileStatus stat2(
+ osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_FileURL);
+ e = item.getFileStatus(stat2);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ assert(!dstUri.endsWith("/"));
+ e = copyRecursive(
+ stat2.getFileURL(), dstUri + "/" + stat2.getFileName());
+ // assumes that all files under presets/ have names that can be
+ // copied unencoded into file URLs
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ }
+ e = dir.close();
+ } else {
+ e = osl::File::copy(srcUri, dstUri);
+ if (e == osl::FileBase::E_EXIST) {
+ // Assume an earlier attempt failed half-way through:
+ e = osl::FileBase::E_None;
+ }
+ }
+ return e;
+}
+#endif
+
+Status create(OUString const & uri) {
+ osl::FileBase::RC e = osl::Directory::createPath(uri);
+ if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) {
+ return ERROR_OTHER;
+ }
+#if !(defined ANDROID || defined IOS || defined EMSCRIPTEN)
+#if defined UNIX
+ // Set safer permissions for the user directory by default:
+ osl::File::setAttributes(
+ uri,
+ (osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead
+ | osl_File_Attribute_OwnExe));
+#endif
+ // As of now osl_copyFile does not work on Android => don't do this:
+ OUString baseUri;
+ if (utl::Bootstrap::locateBaseInstallation(baseUri)
+ != utl::Bootstrap::PATH_EXISTS)
+ {
+ return ERROR_OTHER;
+ }
+ switch (copyRecursive(
+ baseUri + "/" LIBO_SHARE_PRESETS_FOLDER, uri + "/user"))
+ {
+ case osl::FileBase::E_None:
+ break;
+ case osl::FileBase::E_ACCES:
+ return ERROR_CANT_WRITE;
+ case osl::FileBase::E_NOSPC:
+ return ERROR_NO_SPACE;
+ default:
+ return ERROR_OTHER;
+ }
+#else
+ // On (Android and) iOS, just create the user directory. Later code fails mysteriously if it
+ // doesn't exist.
+ OUString userDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/user");
+ rtl::Bootstrap::expandMacros(userDir);
+ osl::Directory::createPath(userDir);
+#endif
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::ooSetupInstCompleted::set(true, batch);
+ batch->commit();
+ return CREATED;
+}
+
+bool isCreated() {
+ try {
+ return officecfg::Setup::Office::ooSetupInstCompleted::get();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ return false;
+ }
+}
+
+}
+
+Status finalize() {
+ OUString uri;
+ switch (utl::Bootstrap::locateUserInstallation(uri)) {
+ case utl::Bootstrap::PATH_EXISTS:
+ if (isCreated()) {
+ return EXISTED;
+ }
+ [[fallthrough]];
+ case utl::Bootstrap::PATH_VALID:
+ return create(uri);
+ default:
+ return ERROR_OTHER;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/userinstall.hxx b/desktop/source/app/userinstall.hxx
new file mode 100644
index 0000000000..527df2dcce
--- /dev/null
+++ b/desktop/source/app/userinstall.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+namespace desktop::userinstall
+{
+enum Status
+{
+ EXISTED,
+ CREATED,
+ ERROR_NO_SPACE,
+ ERROR_CANT_WRITE,
+ ERROR_OTHER
+};
+
+Status finalize();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/deployment.component b/desktop/source/deployment/deployment.component
new file mode 100644
index 0000000000..f7a481bf43
--- /dev/null
+++ b/desktop/source/deployment/deployment.component
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.deployment.ExtensionManager"
+ constructor="com_sun_star_comp_deployment_ExtensionManager_get_implementation">
+ <service name="com.sun.star.comp.deployment.ExtensionManager"/>
+ <singleton name="com.sun.star.deployment.ExtensionManager"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.PackageInformationProvider"
+ constructor="com_sun_star_comp_deployment_PackageInformationProvider_get_implementation">
+ <service name="com.sun.star.comp.deployment.PackageInformationProvider"/>
+ <singleton name="com.sun.star.deployment.PackageInformationProvider"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.PackageManagerFactory"
+ constructor="com_sun_star_comp_deployment_PackageManagerFactory_get_implementation">
+ <service name="com.sun.star.comp.deployment.PackageManagerFactory"/>
+ <singleton name="com.sun.star.deployment.thePackageManagerFactory"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.ProgressLog"
+ constructor="com_sun_star_comp_deployment_ProgressLog_get_implementation">
+ <service name="com.sun.star.comp.deployment.ProgressLog"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.component.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.configuration.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.executable.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.help.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.script.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.sfwk.PackageRegistryBackend"
+ constructor="com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation">
+ <service name="com.sun.star.deployment.PackageRegistryBackend"/>
+ </implementation>
+</component>
diff --git a/desktop/source/deployment/dp_log.cxx b/desktop/source/deployment/dp_log.cxx
new file mode 100644
index 0000000000..5d75422cf0
--- /dev/null
+++ b/desktop/source/deployment/dp_log.cxx
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/anytostring.hxx>
+#include <comphelper/logging.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <com/sun/star/logging/LogLevel.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::logging;
+
+namespace dp_log {
+
+typedef ::cppu::WeakComponentImplHelper<ucb::XProgressHandler, lang::XServiceInfo> t_log_helper;
+
+namespace {
+
+class ProgressLogImpl : public cppu::BaseMutex, public t_log_helper
+{
+ comphelper::EventLogger m_logger;
+
+protected:
+ virtual void SAL_CALL disposing() override;
+ virtual ~ProgressLogImpl() override;
+
+public:
+ ProgressLogImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( Any const & Status ) override;
+ virtual void SAL_CALL update( Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+}
+
+ProgressLogImpl::~ProgressLogImpl()
+{
+}
+
+
+void ProgressLogImpl::disposing()
+{
+}
+
+
+ProgressLogImpl::ProgressLogImpl(
+ Sequence<Any> const & /* args */,
+ Reference<XComponentContext> const & xContext )
+ : t_log_helper( m_aMutex )
+ // Use the logger created by unopkg app
+ , m_logger(xContext, "unopkg")
+{
+}
+
+// XServiceInfo
+OUString ProgressLogImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.ProgressLog";
+}
+
+sal_Bool ProgressLogImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > ProgressLogImpl::getSupportedServiceNames()
+{
+ // a private one
+ return { "com.sun.star.comp.deployment.ProgressLog" };
+}
+
+// XProgressHandler
+
+void ProgressLogImpl::push( Any const & Status )
+{
+ update( Status );
+}
+
+void ProgressLogImpl::update( Any const & Status )
+{
+ if (! Status.hasValue())
+ return;
+
+ OUStringBuffer buf;
+
+ OUString msg;
+ sal_Int32 logLevel = LogLevel::INFO;
+ if (Status >>= msg) {
+ buf.append( msg );
+ }
+ else {
+ logLevel = LogLevel::SEVERE;
+ buf.append( ::comphelper::anyToString(Status) );
+ }
+ m_logger.log(logLevel, buf.makeStringAndClear());
+}
+
+
+void ProgressLogImpl::pop()
+{
+}
+
+} // namespace dp_log
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_ProgressLog_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_log::ProgressLogImpl(args, context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/dp_persmap.cxx b/desktop/source/deployment/dp_persmap.cxx
new file mode 100644
index 0000000000..8b032fffb9
--- /dev/null
+++ b/desktop/source/deployment/dp_persmap.cxx
@@ -0,0 +1,312 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstddef>
+
+#include <dp_misc.h>
+#include <dp_persmap.h>
+#include <o3tl/safeint.hxx>
+#include <rtl/byteseq.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+using namespace ::rtl;
+
+// the persistent map is used to manage a handful of key-value string pairs
+// this implementation replaces a rather heavy-weight berkeleydb integration
+
+// the file backing up a persistent map consists of line pairs with
+// - a key string (encoded with chars 0x00..0x0F being escaped)
+// - a value string (encoded with chars 0x00..0x0F being escaped)
+
+namespace dp_misc
+{
+
+const char PmapMagic[4] = {'P','m','p','1'};
+
+PersistentMap::PersistentMap( OUString const & url_ )
+: m_MapFile( expandUnoRcUrl(url_) )
+, m_bIsOpen( false )
+, m_bToBeCreated( true )
+, m_bIsDirty( false )
+{
+ open();
+}
+
+PersistentMap::PersistentMap()
+: m_MapFile( OUString() )
+, m_bIsOpen( false )
+, m_bToBeCreated( false )
+, m_bIsDirty( false )
+{}
+
+PersistentMap::~PersistentMap()
+{
+ if( m_bIsDirty )
+ flush();
+ if( m_bIsOpen )
+ m_MapFile.close();
+}
+
+
+// replace 0x00..0x0F with "%0".."%F"
+// replace "%" with "%%"
+static OString encodeString( const OString& rStr)
+{
+ const char* pChar = rStr.getStr();
+ const sal_Int32 nLen = rStr.getLength();
+ sal_Int32 i = nLen;
+ // short circuit for the simple non-encoded case
+ while( --i >= 0)
+ {
+ const unsigned char c = static_cast<unsigned char>(*(pChar++));
+ if( c <= 0x0F )
+ break;
+ if( c == '%')
+ break;
+ }
+ if( i < 0)
+ return rStr;
+
+ // escape chars 0x00..0x0F with "%0".."%F"
+ OStringBuffer aEncStr( nLen + 32);
+ aEncStr.append( pChar - (nLen-i), nLen - i);
+ while( --i >= 0)
+ {
+ unsigned char c = static_cast<unsigned char>(*(pChar++));
+ if( c <= 0x0F )
+ {
+ aEncStr.append( '%');
+ c += (c <= 0x09) ? '0' : 'A'-10;
+ } else if( c == '%')
+ aEncStr.append( '%');
+ aEncStr.append( char(c) );
+ }
+
+ return aEncStr.makeStringAndClear();
+}
+
+// replace "%0".."%F" with 0x00..0x0F
+// replace "%%" with "%"
+static OString decodeString( const char* pEncChars, int nLen)
+{
+ const char* pChar = pEncChars;
+ sal_Int32 i = nLen;
+ // short circuit for the simple non-encoded case
+ while( --i >= 0)
+ if( *(pChar++) == '%')
+ break;
+ if( i < 0)
+ return OString( pEncChars, nLen);
+
+ // replace escaped chars with their decoded counterparts
+ OStringBuffer aDecStr( nLen);
+ pChar = pEncChars;
+ for( i = nLen; --i >= 0;)
+ {
+ char c = *(pChar++);
+ // handle escaped character
+ if( c == '%')
+ {
+ --i;
+ OSL_ASSERT( i >= 0);
+ c = *(pChar++);
+ if( ('0' <= c) && (c <= '9'))
+ c -= '0';
+ else
+ {
+ OSL_ASSERT( ('A' <= c) && (c <= 'F'));
+ c -= ('A'-10);
+ }
+ }
+ aDecStr.append( c);
+ }
+
+ return aDecStr.makeStringAndClear();
+}
+
+void PersistentMap::open()
+{
+ // open the existing file
+ sal_uInt32 const nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write;
+
+ const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
+ m_bIsOpen = (rcOpen == osl::File::E_None);
+
+ // or create later if needed
+ m_bToBeCreated &= (rcOpen == osl::File::E_NOENT) && !m_bIsOpen;
+
+ if( !m_bIsOpen)
+ return;
+
+ readAll();
+}
+
+
+void PersistentMap::readAll()
+{
+ // prepare for re-reading the map-file
+ m_entries.clear();
+ const osl::FileBase::RC nRes = m_MapFile.setPos( osl_Pos_Absolut, 0);
+ if (nRes != osl::FileBase::E_None)
+ {
+ SAL_WARN("desktop.deployment", "setPos failed with " << +nRes);
+ return;
+ }
+
+ // read header and check magic
+ char aHeaderBytes[ sizeof(PmapMagic)];
+ sal_uInt64 nBytesRead = 0;
+ m_MapFile.read( aHeaderBytes, sizeof(aHeaderBytes), nBytesRead);
+ OSL_ASSERT( nBytesRead == sizeof(aHeaderBytes));
+ if( nBytesRead != sizeof(aHeaderBytes))
+ return;
+ // check header magic
+ for( std::size_t i = 0; i < sizeof(PmapMagic); ++i)
+ if( aHeaderBytes[i] != PmapMagic[i])
+ return;
+
+ // read key value pairs and add them to the map
+ ByteSequence aKeyLine;
+ ByteSequence aValLine;
+ for(;;)
+ {
+ // read key-value line pair
+ // an empty key name indicates the end of the line pairs
+ if( m_MapFile.readLine( aKeyLine) != osl::File::E_None)
+ return;
+ if( !aKeyLine.getLength())
+ break;
+ if( m_MapFile.readLine( aValLine) != osl::File::E_None)
+ return;
+ // decode key and value strings
+ const OString aKeyName = decodeString( reinterpret_cast<char const *>(aKeyLine.getConstArray()), aKeyLine.getLength());
+ const OString aValName = decodeString( reinterpret_cast<char const *>(aValLine.getConstArray()), aValLine.getLength());
+ // insert key-value pair into map
+ add( aKeyName, aValName );
+ // check end-of-file status
+ sal_Bool bIsEOF = true;
+ if( m_MapFile.isEndOfFile( &bIsEOF) != osl::File::E_None )
+ return;
+ if( bIsEOF )
+ break;
+ }
+
+ m_bIsDirty = false;
+}
+
+void PersistentMap::flush()
+{
+ if( !m_bIsDirty)
+ return;
+ if( m_bToBeCreated && !m_entries.empty())
+ {
+ const sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create;
+ const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
+ m_bIsOpen = (rcOpen == osl::File::E_None);
+ m_bToBeCreated = !m_bIsOpen;
+ }
+ if( !m_bIsOpen)
+ return;
+
+ // write header magic
+ const osl::FileBase::RC nRes = m_MapFile.setPos( osl_Pos_Absolut, 0);
+ if (nRes != osl::FileBase::E_None)
+ {
+ SAL_WARN("desktop.deployment", "setPos failed with " << +nRes);
+ return;
+ }
+ sal_uInt64 nBytesWritten = 0;
+ m_MapFile.write( PmapMagic, sizeof(PmapMagic), nBytesWritten);
+
+ // write key value pairs
+ for (auto const& entry : m_entries)
+ {
+ // write line for key
+ const OString aKeyString = encodeString( entry.first);
+ const sal_Int32 nKeyLen = aKeyString.getLength();
+ m_MapFile.write( aKeyString.getStr(), nKeyLen, nBytesWritten);
+ OSL_ASSERT( o3tl::make_unsigned(nKeyLen) == nBytesWritten);
+ m_MapFile.write( "\n", 1, nBytesWritten);
+ // write line for value
+ const OString& rValString = encodeString( entry.second);
+ const sal_Int32 nValLen = rValString.getLength();
+ m_MapFile.write( rValString.getStr(), nValLen, nBytesWritten);
+ OSL_ASSERT( o3tl::make_unsigned(nValLen) == nBytesWritten);
+ m_MapFile.write( "\n", 1, nBytesWritten);
+ }
+
+ // write a file delimiter (an empty key-string)
+ m_MapFile.write( "\n", 1, nBytesWritten);
+ // truncate file here
+ sal_uInt64 nNewFileSize;
+ if( m_MapFile.getPos( nNewFileSize) == osl::File::E_None)
+ m_MapFile.setSize( nNewFileSize);
+ // flush to disk
+ m_MapFile.sync();
+ // the in-memory map now matches to the file on disk
+ m_bIsDirty = false;
+}
+
+bool PersistentMap::has( OString const & key ) const
+{
+ return get( nullptr, key );
+}
+
+bool PersistentMap::get( OString * value, OString const & key ) const
+{
+ t_string2string_map::const_iterator it = m_entries.find( key);
+ if( it == m_entries.end())
+ return false;
+ if( value)
+ *value = it->second;
+ return true;
+}
+
+void PersistentMap::add( OString const & key, OString const & value )
+{
+ auto r = m_entries.emplace(key,value);
+ m_bIsDirty = r.second;
+}
+
+
+void PersistentMap::put( OString const & key, OString const & value )
+{
+ add( key, value);
+ // HACK: flush now as the extension manager does not seem
+ // to properly destruct this object in some situations
+ if(m_bIsDirty)
+ flush();
+}
+
+bool PersistentMap::erase( OString const & key )
+{
+ size_t nCount = m_entries.erase( key);
+ if( !nCount)
+ return false;
+ m_bIsDirty = true;
+ flush();
+ return true;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/dp_xml.cxx b/desktop/source/deployment/dp_xml.cxx
new file mode 100644
index 0000000000..d61267e2fa
--- /dev/null
+++ b/desktop/source/deployment/dp_xml.cxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <dp_xml.h>
+#include <ucbhelper/content.hxx>
+#include <com/sun/star/xml/sax/Parser.hpp>
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_misc
+{
+
+
+void xml_parse(
+ Reference<xml::sax::XDocumentHandler> const & xDocHandler,
+ ::ucbhelper::Content & ucb_content,
+ Reference<XComponentContext> const & xContext )
+{
+ // raise sax parser:
+ Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(xContext);
+
+ // error handler, entity resolver omitted
+ xParser->setDocumentHandler( xDocHandler );
+ xml::sax::InputSource source;
+ source.aInputStream = ucb_content.openStream();
+ source.sSystemId = ucb_content.getURL();
+ xParser->parseStream( source );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/deploymentgui.component b/desktop/source/deployment/gui/deploymentgui.component
new file mode 100644
index 0000000000..3b56863c13
--- /dev/null
+++ b/desktop/source/deployment/gui/deploymentgui.component
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.deployment.ui.LicenseDialog"
+ constructor="desktop_LicenseDialog_get_implementation">
+ <service name="com.sun.star.deployment.ui.LicenseDialog"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.ui.PackageManagerDialog"
+ constructor="desktop_ServiceImpl_get_implementation">
+ <service name="com.sun.star.deployment.ui.PackageManagerDialog"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.deployment.ui.UpdateRequiredDialog"
+ constructor="desktop_UpdateRequiredDialogService_get_implementation">
+ <service name="com.sun.star.deployment.ui.UpdateRequiredDialog"/>
+ </implementation>
+</component>
diff --git a/desktop/source/deployment/gui/dp_gui.h b/desktop/source/deployment/gui/dp_gui.h
new file mode 100644
index 0000000000..cb2932ca4f
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+namespace dp_gui {
+
+enum PackageState { REGISTERED, NOT_REGISTERED, AMBIGUOUS, NOT_AVAILABLE };
+
+} // namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx
new file mode 100644
index 0000000000..f94b298917
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <rtl/ustring.hxx>
+#include <vcl/weld.hxx>
+
+#include "dp_gui_dependencydialog.hxx"
+
+using dp_gui::DependencyDialog;
+
+DependencyDialog::DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies)
+ : GenericDialogController(parent, "desktop/ui/dependenciesdialog.ui", "Dependencies")
+ , m_xList(m_xBuilder->weld_tree_view("depListTreeview"))
+{
+ m_xList->set_size_request(-1, m_xList->get_height_rows(10));
+ for (auto const& dependency : dependencies)
+ {
+ m_xList->append_text(dependency);
+ }
+}
+
+DependencyDialog::~DependencyDialog() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx
new file mode 100644
index 0000000000..bdbab41d19
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/weld.hxx>
+
+#include <vector>
+
+namespace vcl
+{
+class Window;
+}
+
+namespace dp_gui
+{
+class DependencyDialog : public weld::GenericDialogController
+{
+public:
+ DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies);
+ virtual ~DependencyDialog() override;
+
+private:
+ DependencyDialog(DependencyDialog const&) = delete;
+ DependencyDialog& operator=(DependencyDialog const&) = delete;
+
+ std::unique_ptr<weld::TreeView> m_xList;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.cxx b/desktop/source/deployment/gui/dp_gui_dialog2.cxx
new file mode 100644
index 0000000000..03885b1619
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_dialog2.cxx
@@ -0,0 +1,1403 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_extensions.h>
+
+#include <strings.hrc>
+#include <helpids.h>
+
+#include "dp_gui.h"
+#include "dp_gui_dialog2.hxx"
+#include "dp_gui_extlistbox.hxx"
+#include <dp_shared.hxx>
+#include "dp_gui_theextmgr.hxx"
+#include "dp_gui_extensioncmdqueue.hxx"
+#include <dp_misc.h>
+#include <dp_update.hxx>
+#include <dp_identifier.hxx>
+
+#include <fpicker/strings.hrc>
+
+#include <utility>
+#include <vcl/commandevent.hxx>
+#include <vcl/svapp.hxx>
+
+#include <osl/mutex.hxx>
+#include <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <svtools/restartdialog.hxx>
+
+#include <sfx2/filedlghelper.hxx>
+#include <sfx2/sfxdlg.hxx>
+
+#include <comphelper/anytostring.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+
+#include <officecfg/Office/ExtensionManager.hxx>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::system;
+
+
+namespace dp_gui {
+
+constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user";
+constexpr OUString SHARED_PACKAGE_MANAGER = u"shared"_ustr;
+constexpr OUStringLiteral BUNDLED_PACKAGE_MANAGER = u"bundled";
+
+// ExtBoxWithBtns_Impl
+class ExtBoxWithBtns_Impl : public ExtensionBox_Impl
+{
+ bool m_bInterfaceLocked;
+
+ ExtMgrDialog* m_pParent;
+
+ void SetButtonStatus( const TEntry_Impl& rEntry );
+ OUString ShowPopupMenu( const Point &rPos, const tools::Long nPos );
+
+public:
+ explicit ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll);
+
+ void InitFromDialog(ExtMgrDialog *pParentDialog);
+
+ virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual bool Command( const CommandEvent& rCEvt ) override;
+
+ virtual void RecalcAll() override;
+ virtual void selectEntry( const tools::Long nPos ) override;
+
+ void enableButtons( bool bEnable );
+};
+
+ExtBoxWithBtns_Impl::ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll)
+ : ExtensionBox_Impl(std::move(xScroll))
+ , m_bInterfaceLocked(false)
+ , m_pParent(nullptr)
+{
+}
+
+void ExtBoxWithBtns_Impl::InitFromDialog(ExtMgrDialog *pParentDialog)
+{
+ setExtensionManager(pParentDialog->getExtensionManager());
+
+ m_pParent = pParentDialog;
+}
+
+void ExtBoxWithBtns_Impl::RecalcAll()
+{
+ const sal_Int32 nActive = getSelIndex();
+
+ if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND )
+ {
+ SetButtonStatus( GetEntryData( nActive) );
+ }
+ else
+ {
+ m_pParent->enableOptionsButton( false );
+ m_pParent->enableRemoveButton( false );
+ m_pParent->enableEnableButton( false );
+ }
+
+ ExtensionBox_Impl::RecalcAll();
+}
+
+
+//This function may be called with nPos < 0
+void ExtBoxWithBtns_Impl::selectEntry( const tools::Long nPos )
+{
+ if ( HasActive() && ( nPos == getSelIndex() ) )
+ return;
+
+ ExtensionBox_Impl::selectEntry( nPos );
+}
+
+void ExtBoxWithBtns_Impl::SetButtonStatus(const TEntry_Impl& rEntry)
+{
+ bool bShowOptionBtn = true;
+
+ rEntry->m_bHasButtons = false;
+ if ( ( rEntry->m_eState == REGISTERED ) || ( rEntry->m_eState == NOT_AVAILABLE ) )
+ {
+ m_pParent->enableButtontoEnable( false );
+ }
+ else
+ {
+ m_pParent->enableButtontoEnable( true );
+ bShowOptionBtn = false;
+ }
+
+ if ( ( !rEntry->m_bUser || ( rEntry->m_eState == NOT_AVAILABLE ) || rEntry->m_bMissingDeps )
+ && !rEntry->m_bMissingLic )
+ {
+ m_pParent->enableEnableButton( false );
+ }
+ else
+ {
+ m_pParent->enableEnableButton( !rEntry->m_bLocked );
+ rEntry->m_bHasButtons = true;
+ }
+
+ if ( rEntry->m_bHasOptions && bShowOptionBtn )
+ {
+ m_pParent->enableOptionsButton( true );
+ rEntry->m_bHasButtons = true;
+ }
+ else
+ {
+ m_pParent->enableOptionsButton( false );
+ }
+
+ if ( rEntry->m_bUser || rEntry->m_bShared )
+ {
+ m_pParent->enableRemoveButton( !rEntry->m_bLocked );
+ rEntry->m_bHasButtons = true;
+ }
+ else
+ {
+ m_pParent->enableRemoveButton( false );
+ }
+}
+
+bool ExtBoxWithBtns_Impl::Command(const CommandEvent& rCEvt)
+{
+ if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
+ return ExtensionBox_Impl::Command(rCEvt);
+
+ const Point aMousePos(rCEvt.GetMousePosPixel());
+ const auto nPos = PointToPos(aMousePos);
+ OUString sCommand = ShowPopupMenu(aMousePos, nPos);
+
+ if (sCommand == "CMD_ENABLE")
+ m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, true );
+ else if (sCommand == "CMD_DISABLE")
+ m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, false );
+ else if (sCommand == "CMD_UPDATE")
+ m_pParent->updatePackage( GetEntryData( nPos )->m_xPackage );
+ else if (sCommand == "CMD_REMOVE")
+ m_pParent->removePackage( GetEntryData( nPos )->m_xPackage );
+ else if (sCommand == "CMD_SHOW_LICENSE")
+ {
+ m_pParent->incBusy();
+ ShowLicenseDialog aLicenseDlg(m_pParent->getDialog(), GetEntryData(nPos)->m_xPackage);
+ aLicenseDlg.run();
+ m_pParent->decBusy();
+ }
+
+ return true;
+}
+
+OUString ExtBoxWithBtns_Impl::ShowPopupMenu( const Point & rPos, const tools::Long nPos )
+{
+ if ( nPos >= static_cast<tools::Long>(getItemCount()) )
+ return "CMD_NONE";
+
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "desktop/ui/extensionmenu.ui"));
+ std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu("menu"));
+
+#if ENABLE_EXTENSION_UPDATE
+ xPopup->append("CMD_UPDATE", DpResId( RID_CTX_ITEM_CHECK_UPDATE ) );
+#endif
+
+ if ( ! GetEntryData( nPos )->m_bLocked )
+ {
+ if ( GetEntryData( nPos )->m_bUser )
+ {
+ if ( GetEntryData( nPos )->m_eState == REGISTERED )
+ xPopup->append("CMD_DISABLE", DpResId(RID_CTX_ITEM_DISABLE));
+ else if ( GetEntryData( nPos )->m_eState != NOT_AVAILABLE )
+ xPopup->append("CMD_ENABLE", DpResId(RID_CTX_ITEM_ENABLE));
+ }
+ if (!officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get())
+ {
+ xPopup->append("CMD_REMOVE", DpResId(RID_CTX_ITEM_REMOVE));
+ }
+ }
+
+ if ( !GetEntryData( nPos )->m_sLicenseText.isEmpty() )
+ xPopup->append("CMD_SHOW_LICENSE", DpResId(RID_STR_SHOW_LICENSE_CMD));
+
+ return xPopup->popup_at_rect(GetDrawingArea(), tools::Rectangle(rPos, Size(1, 1)));
+}
+
+bool ExtBoxWithBtns_Impl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (m_bInterfaceLocked)
+ return false;
+ return ExtensionBox_Impl::MouseButtonDown(rMEvt);
+}
+
+void ExtBoxWithBtns_Impl::enableButtons( bool bEnable )
+{
+ m_bInterfaceLocked = ! bEnable;
+
+ if ( bEnable )
+ {
+ sal_Int32 nIndex = getSelIndex();
+ if ( nIndex != ExtensionBox_Impl::ENTRY_NOTFOUND )
+ SetButtonStatus( GetEntryData( nIndex ) );
+ }
+ else
+ {
+ m_pParent->enableEnableButton( false );
+ m_pParent->enableOptionsButton( false );
+ m_pParent->enableRemoveButton( false );
+ }
+}
+
+// DialogHelper
+
+DialogHelper::DialogHelper(const uno::Reference< uno::XComponentContext > &xContext,
+ weld::Window* pWindow)
+ : m_pWindow(pWindow)
+ , m_nEventID(nullptr)
+{
+ m_xContext = xContext;
+}
+
+DialogHelper::~DialogHelper()
+{
+ if ( m_nEventID )
+ Application::RemoveUserEvent( m_nEventID );
+}
+
+
+bool DialogHelper::IsSharedPkgMgr( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ return xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER;
+}
+
+bool DialogHelper::continueOnSharedExtension( const uno::Reference< deployment::XPackage > &xPackage,
+ weld::Widget* pParent,
+ TranslateId pResID,
+ bool &bHadWarning )
+{
+ if ( !bHadWarning && IsSharedPkgMgr( xPackage ) )
+ {
+ const SolarMutexGuard guard;
+ incBusy();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(pResID)));
+ bHadWarning = true;
+
+ bool bRet = RET_OK == xBox->run();
+ xBox.reset();
+ decBusy();
+ return bRet;
+ }
+ else
+ return true;
+}
+
+void DialogHelper::openWebBrowser(const OUString& sURL, const OUString& sTitle)
+{
+ if ( sURL.isEmpty() ) // Nothing to do, when the URL is empty
+ return;
+
+ try
+ {
+ uno::Reference< XSystemShellExecute > xSystemShellExecute(
+ SystemShellExecute::create(m_xContext));
+ //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
+ xSystemShellExecute->execute( sURL, OUString(), SystemShellExecuteFlags::URIS_ONLY );
+ }
+ catch ( const uno::Exception& )
+ {
+ uno::Any exc( ::cppu::getCaughtException() );
+ OUString msg( ::comphelper::anyToString( exc ) );
+ const SolarMutexGuard guard;
+ incBusy();
+ std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(getFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok, msg));
+ xErrorBox->set_title(sTitle);
+ xErrorBox->run();
+ xErrorBox.reset();
+ decBusy();
+ }
+}
+
+bool DialogHelper::installExtensionWarn(std::u16string_view rExtensionName)
+{
+ const SolarMutexGuard guard;
+
+ // Check if extension installation is disabled in the expert configurations
+ if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get())
+ {
+ incBusy();
+ std::unique_ptr<weld::MessageDialog> xWarnBox(Application::CreateMessageDialog(getFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED)));
+ xWarnBox->run();
+ xWarnBox.reset();
+ decBusy();
+
+ return false;
+ }
+
+ incBusy();
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(getFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::OkCancel,
+ DpResId(RID_STR_WARNING_INSTALL_EXTENSION)));
+ OUString sText(xInfoBox->get_primary_text());
+ sText = sText.replaceAll("%NAME", rExtensionName);
+ xInfoBox->set_primary_text(sText);
+
+ bool bRet = RET_OK == xInfoBox->run();
+ xInfoBox.reset();
+ decBusy();
+ return bRet;
+}
+
+bool DialogHelper::installForAllUsers(bool &bInstallForAll)
+{
+ const SolarMutexGuard guard;
+ incBusy();
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(getFrameWeld(), "desktop/ui/installforalldialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("InstallForAllDialog"));
+ short nRet = xQuery->run();
+ xQuery.reset();
+ decBusy();
+ if (nRet == RET_CANCEL)
+ return false;
+
+ bInstallForAll = ( nRet == RET_NO );
+ return true;
+}
+
+void DialogHelper::PostUserEvent( const Link<void*,void>& rLink, void* pCaller )
+{
+ if ( m_nEventID )
+ Application::RemoveUserEvent( m_nEventID );
+
+ m_nEventID = Application::PostUserEvent(rLink, pCaller);
+}
+
+// ExtMgrDialog
+ExtMgrDialog::ExtMgrDialog(weld::Window *pParent, TheExtensionManager *pManager)
+ : GenericDialogController(pParent, "desktop/ui/extensionmanager.ui", "ExtensionManagerDialog")
+ , DialogHelper(pManager->getContext(), m_xDialog.get())
+ , m_sAddPackages(DpResId(RID_STR_ADD_PACKAGES))
+ , m_bHasProgress(false)
+ , m_bProgressChanged(false)
+ , m_bStartProgress(false)
+ , m_bStopProgress(false)
+ , m_bEnableWarning(false)
+ , m_bDisableWarning(false)
+ , m_bDeleteWarning(false)
+ , m_bClosed(false)
+ , m_nProgress(0)
+ , m_aIdle( "ExtMgrDialog m_aIdle TimeOutHdl" )
+ , m_pManager(pManager)
+ , m_xExtensionBox(new ExtBoxWithBtns_Impl(m_xBuilder->weld_scrolled_window("scroll", true)))
+ , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox))
+ , m_xOptionsBtn(m_xBuilder->weld_button("optionsbtn"))
+ , m_xAddBtn(m_xBuilder->weld_button("addbtn"))
+ , m_xRemoveBtn(m_xBuilder->weld_button("removebtn"))
+ , m_xEnableBtn(m_xBuilder->weld_button("enablebtn"))
+ , m_xUpdateBtn(m_xBuilder->weld_button("updatebtn"))
+ , m_xCloseBtn(m_xBuilder->weld_button("close"))
+ , m_xBundledCbx(m_xBuilder->weld_check_button("bundled"))
+ , m_xSharedCbx(m_xBuilder->weld_check_button("shared"))
+ , m_xUserCbx(m_xBuilder->weld_check_button("user"))
+ , m_xGetExtensions(m_xBuilder->weld_link_button("getextensions"))
+ , m_xProgressText(m_xBuilder->weld_label("progressft"))
+ , m_xProgressBar(m_xBuilder->weld_progress_bar("progressbar"))
+ , m_xCancelBtn(m_xBuilder->weld_button("cancel"))
+ , m_xSearchEntry(m_xBuilder->weld_entry("search"))
+{
+ m_xExtensionBox->InitFromDialog(this);
+
+ m_xEnableBtn->set_help_id(HID_EXTENSION_MANAGER_LISTBOX_ENABLE);
+
+ m_xOptionsBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleOptionsBtn ) );
+ m_xAddBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleAddBtn ) );
+ m_xRemoveBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleRemoveBtn ) );
+ m_xEnableBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleEnableBtn ) );
+ m_xCloseBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCloseBtn ) );
+
+ m_xCancelBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCancelBtn ) );
+
+ m_xBundledCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) );
+ m_xSharedCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) );
+ m_xUserCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) );
+
+ m_xSearchEntry->connect_changed( LINK( this, ExtMgrDialog, HandleSearch ) );
+
+ m_xBundledCbx->set_active(true);
+ m_xSharedCbx->set_active(true);
+ m_xUserCbx->set_active(true);
+
+ m_xProgressBar->hide();
+
+#if ENABLE_EXTENSION_UPDATE
+ m_xUpdateBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleUpdateBtn ) );
+ m_xUpdateBtn->set_sensitive(false);
+#else
+ m_xUpdateBtn->hide();
+#endif
+
+ if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get())
+ {
+ m_xAddBtn->set_sensitive(false);
+ m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED));
+ }
+ if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get())
+ {
+ m_xRemoveBtn->set_sensitive(false);
+ m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED));
+ }
+
+ m_aIdle.SetPriority(TaskPriority::LOWEST);
+ m_aIdle.SetInvokeHandler( LINK( this, ExtMgrDialog, TimeOutHdl ) );
+}
+
+ExtMgrDialog::~ExtMgrDialog()
+{
+ m_aIdle.Stop();
+}
+
+void ExtMgrDialog::setGetExtensionsURL( const OUString &rURL )
+{
+ m_xGetExtensions->set_uri( rURL );
+}
+
+void ExtMgrDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage,
+ bool bLicenseMissing )
+{
+ const SolarMutexGuard aGuard;
+ m_xUpdateBtn->set_sensitive(true);
+
+ bool bSearchMatch = m_xSearchEntry->get_text().isEmpty();
+ if (!m_xSearchEntry->get_text().isEmpty()
+ && xPackage->getDisplayName().toAsciiLowerCase().indexOf(
+ m_xSearchEntry->get_text().toAsciiLowerCase())
+ >= 0)
+ {
+ bSearchMatch = true;
+ }
+
+ if (!bSearchMatch)
+ return;
+
+ if (m_xBundledCbx->get_active() && (xPackage->getRepositoryName() == BUNDLED_PACKAGE_MANAGER) )
+ {
+ m_xExtensionBox->addEntry( xPackage, bLicenseMissing );
+ }
+ else if (m_xSharedCbx->get_active() && (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER) )
+ {
+ m_xExtensionBox->addEntry( xPackage, bLicenseMissing );
+ }
+ else if (m_xUserCbx->get_active() && (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER ))
+ {
+ m_xExtensionBox->addEntry( xPackage, bLicenseMissing );
+ }
+}
+
+void ExtMgrDialog::updateList()
+{
+ // re-creates the list of packages with addEntry selecting the packages
+ prepareChecking();
+ m_pManager->createPackageList();
+ checkEntries();
+}
+
+void ExtMgrDialog::prepareChecking()
+{
+ m_xExtensionBox->prepareChecking();
+}
+
+void ExtMgrDialog::checkEntries()
+{
+ const SolarMutexGuard guard;
+ m_xExtensionBox->checkEntries();
+}
+
+bool ExtMgrDialog::removeExtensionWarn(std::u16string_view rExtensionName)
+{
+ const SolarMutexGuard guard;
+ incBusy();
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::OkCancel,
+ DpResId(RID_STR_WARNING_REMOVE_EXTENSION)));
+
+ OUString sText(xInfoBox->get_primary_text());
+ sText = sText.replaceAll("%NAME", rExtensionName);
+ xInfoBox->set_primary_text(sText);
+
+ bool bRet = RET_OK == xInfoBox->run();
+ xInfoBox.reset();
+ decBusy();
+
+ return bRet;
+}
+
+void ExtMgrDialog::enablePackage( const uno::Reference< deployment::XPackage > &xPackage,
+ bool bEnable )
+{
+ if ( !xPackage.is() )
+ return;
+
+ if ( bEnable )
+ {
+ if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_ENABLE_SHARED_EXTENSION, m_bEnableWarning))
+ return;
+ }
+ else
+ {
+ if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_DISABLE_SHARED_EXTENSION, m_bDisableWarning))
+ return;
+ }
+
+ m_pManager->getCmdQueue()->enableExtension( xPackage, bEnable );
+}
+
+
+void ExtMgrDialog::removePackage( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return;
+
+ if ( !IsSharedPkgMgr( xPackage ) || m_bDeleteWarning )
+ {
+ if ( ! removeExtensionWarn( xPackage->getDisplayName() ) )
+ return;
+ }
+
+ if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_REMOVE_SHARED_EXTENSION, m_bDeleteWarning))
+ return;
+
+ m_pManager->getCmdQueue()->removeExtension( xPackage );
+}
+
+
+void ExtMgrDialog::updatePackage( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return;
+
+ // get the extension with highest version
+ uno::Sequence<uno::Reference<deployment::XPackage> > seqExtensions =
+ m_pManager->getExtensionManager()->getExtensionsWithSameIdentifier(
+ dp_misc::getIdentifier(xPackage), xPackage->getName(), uno::Reference<ucb::XCommandEnvironment>());
+ uno::Reference<deployment::XPackage> extension =
+ dp_misc::getExtensionWithHighestVersion(seqExtensions);
+ OSL_ASSERT(extension.is());
+ std::vector< css::uno::Reference< css::deployment::XPackage > > vEntries { extension };
+
+ m_pManager->getCmdQueue()->checkForUpdates( std::move(vEntries) );
+}
+
+
+bool ExtMgrDialog::acceptLicense( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return false;
+
+ m_pManager->getCmdQueue()->acceptLicense( xPackage );
+
+ return true;
+}
+
+
+uno::Sequence< OUString > ExtMgrDialog::raiseAddPicker()
+{
+ sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, m_xDialog.get());
+ aDlgHelper.SetContext(sfx2::FileDialogHelper::ExtensionManager);
+ const uno::Reference<ui::dialogs::XFilePicker3>& xFilePicker = aDlgHelper.GetFilePicker();
+ xFilePicker->setTitle( m_sAddPackages );
+
+ // collect and set filter list:
+ typedef std::map< OUString, OUString > t_string2string;
+ t_string2string title2filter;
+ OUStringBuffer supportedFilters;
+
+ const uno::Sequence< uno::Reference< deployment::XPackageTypeInfo > > packageTypes(
+ m_pManager->getExtensionManager()->getSupportedPackageTypes() );
+
+ for ( uno::Reference< deployment::XPackageTypeInfo > const & xPackageType : packageTypes )
+ {
+ const OUString filter( xPackageType->getFileFilter() );
+ if (!filter.isEmpty())
+ {
+ const OUString title( xPackageType->getShortDescription() );
+ const std::pair< t_string2string::iterator, bool > insertion(
+ title2filter.emplace( title, filter ) );
+ if (!supportedFilters.isEmpty())
+ supportedFilters.append(';');
+ supportedFilters.append(filter);
+ if ( ! insertion.second )
+ { // already existing, append extensions:
+ insertion.first->second = insertion.first->second +
+ ";" + filter;
+ }
+ }
+ }
+
+ static const OUString StrAllFiles = []()
+ {
+ const SolarMutexGuard guard;
+ std::locale loc = Translate::Create("fps");
+ return Translate::get(STR_FILTERNAME_ALL, loc);
+ }();
+
+ // All files at top:
+ xFilePicker->appendFilter( StrAllFiles, "*.*" );
+ xFilePicker->appendFilter( DpResId(RID_STR_ALL_SUPPORTED), supportedFilters.makeStringAndClear() );
+ // then supported ones:
+ for (auto const& elem : title2filter)
+ {
+ try
+ {
+ xFilePicker->appendFilter( elem.first, elem.second );
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ }
+ xFilePicker->setCurrentFilter( DpResId(RID_STR_ALL_SUPPORTED) );
+
+ if ( xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK )
+ return uno::Sequence<OUString>(); // cancelled
+
+ uno::Sequence< OUString > files( xFilePicker->getSelectedFiles() );
+ OSL_ASSERT( files.hasElements() );
+ return files;
+}
+
+void ExtMgrDialog::enableOptionsButton( bool bEnable )
+{
+ m_xOptionsBtn->set_sensitive( bEnable );
+}
+
+void ExtMgrDialog::enableRemoveButton( bool bEnable )
+{
+ m_xRemoveBtn->set_sensitive( bEnable && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get());
+
+ if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get())
+ {
+ m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED));
+ }
+ else
+ {
+ m_xRemoveBtn->set_tooltip_text("");
+ }
+}
+
+void ExtMgrDialog::enableEnableButton( bool bEnable )
+{
+ m_xEnableBtn->set_sensitive( bEnable );
+}
+
+void ExtMgrDialog::enableButtontoEnable( bool bEnable )
+{
+ if (bEnable)
+ {
+ m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_ENABLE ) );
+ m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_ENABLE );
+ }
+ else
+ {
+ m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_DISABLE ) );
+ m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_DISABLE );
+ }
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleCancelBtn, weld::Button&, void)
+{
+ if ( m_xAbortChannel.is() )
+ {
+ try
+ {
+ m_xAbortChannel->sendAbort();
+ }
+ catch ( const uno::RuntimeException & )
+ {
+ TOOLS_WARN_EXCEPTION( "dbaccess", "" );
+ }
+ }
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleCloseBtn, weld::Button&, void)
+{
+ bool bCallClose = true;
+
+ //only suggest restart if modified and this is the first close attempt
+ if (!m_bClosed && m_pManager->isModified())
+ {
+ m_pManager->clearModified();
+
+ //only suggest restart if we're actually running, e.g. not from standalone unopkg gui
+ if (dp_misc::office_is_running())
+ {
+ SolarMutexGuard aGuard;
+ bCallClose = !::svtools::executeRestartDialog(comphelper::getProcessComponentContext(),
+ m_xDialog.get(),
+ svtools::RESTART_REASON_EXTENSION_INSTALL);
+ }
+ }
+
+ if (bCallClose)
+ m_xDialog->response(RET_CANCEL);
+}
+
+IMPL_LINK( ExtMgrDialog, startProgress, void*, _bLockInterface, void )
+{
+ std::unique_lock aGuard( m_aMutex );
+ bool bLockInterface = static_cast<bool>(_bLockInterface);
+
+ if ( m_bStartProgress && !m_bHasProgress )
+ m_aIdle.Start();
+
+ if ( m_bStopProgress )
+ {
+ if ( m_xProgressBar->get_visible() )
+ m_xProgressBar->set_percentage( 100 );
+ m_xAbortChannel.clear();
+
+ SAL_INFO( "desktop.deployment", " startProgress handler: stop" );
+ }
+ else
+ {
+ SAL_INFO( "desktop.deployment", " startProgress handler: start" );
+ }
+
+ m_xCancelBtn->set_sensitive( bLockInterface );
+ m_xAddBtn->set_sensitive( !bLockInterface && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get());
+ if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get())
+ {
+ m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED));
+ }
+ else
+ {
+ m_xAddBtn->set_tooltip_text("");
+ }
+
+ m_xUpdateBtn->set_sensitive( !bLockInterface && m_xExtensionBox->getItemCount() );
+ m_xExtensionBox->enableButtons( !bLockInterface );
+
+ clearEventID();
+}
+
+
+void ExtMgrDialog::showProgress( bool _bStart )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ bool bStart = _bStart;
+
+ if ( bStart )
+ {
+ m_nProgress = 0;
+ m_bStartProgress = true;
+ SAL_INFO( "desktop.deployment", "showProgress start" );
+ }
+ else
+ {
+ m_nProgress = 100;
+ m_bStopProgress = true;
+ SAL_INFO( "desktop.deployment", "showProgress stop!" );
+ }
+
+ DialogHelper::PostUserEvent( LINK( this, ExtMgrDialog, startProgress ), reinterpret_cast<void*>(bStart) );
+ m_aIdle.Start();
+}
+
+
+void ExtMgrDialog::updateProgress( const tools::Long nProgress )
+{
+ if ( m_nProgress != nProgress )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_nProgress = nProgress;
+ m_aIdle.Start();
+ }
+}
+
+
+void ExtMgrDialog::updateProgress( const OUString &rText,
+ const uno::Reference< task::XAbortChannel > &xAbortChannel)
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_xAbortChannel = xAbortChannel;
+ m_sProgressText = rText;
+ m_bProgressChanged = true;
+ m_aIdle.Start();
+}
+
+
+void ExtMgrDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ const SolarMutexGuard aGuard;
+ m_xExtensionBox->updateEntry( xPackage );
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleOptionsBtn, weld::Button&, void)
+{
+ const sal_Int32 nActive = m_xExtensionBox->getSelIndex();
+
+ if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND )
+ {
+ SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
+
+ OUString sExtensionId = m_xExtensionBox->GetEntryData( nActive )->m_xPackage->getIdentifier().Value;
+ ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateOptionsDialog(m_xDialog.get(), sExtensionId));
+
+ pDlg->Execute();
+ }
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleAddBtn, weld::Button&, void)
+{
+ incBusy();
+
+ uno::Sequence< OUString > aFileList = raiseAddPicker();
+
+ if ( aFileList.hasElements() )
+ {
+ m_pManager->installPackage( aFileList[0] );
+ }
+
+ decBusy();
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleRemoveBtn, weld::Button&, void)
+{
+ const sal_Int32 nActive = m_xExtensionBox->getSelIndex();
+
+ if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND )
+ {
+ TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive );
+ removePackage( pEntry->m_xPackage );
+ }
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleEnableBtn, weld::Button&, void)
+{
+ const sal_Int32 nActive = m_xExtensionBox->getSelIndex();
+
+ if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND )
+ {
+ TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive );
+
+ if ( pEntry->m_bMissingLic )
+ acceptLicense( pEntry->m_xPackage );
+ else
+ {
+ const bool bEnable( pEntry->m_eState != REGISTERED );
+ enablePackage( pEntry->m_xPackage, bEnable );
+ }
+ }
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleExtTypeCbx, weld::Toggleable&, void)
+{
+ updateList();
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleSearch, weld::Entry&, void)
+{
+ updateList();
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, HandleUpdateBtn, weld::Button&, void)
+{
+#if ENABLE_EXTENSION_UPDATE
+ m_pManager->checkUpdates();
+#else
+ (void) this;
+#endif
+}
+
+IMPL_LINK_NOARG(ExtMgrDialog, TimeOutHdl, Timer *, void)
+{
+ if ( m_bStopProgress )
+ {
+ m_bHasProgress = false;
+ m_bStopProgress = false;
+ m_xProgressText->hide();
+ m_xProgressBar->hide();
+ m_xCancelBtn->hide();
+ }
+ else
+ {
+ if ( m_bProgressChanged )
+ {
+ m_bProgressChanged = false;
+ m_xProgressText->set_label(m_sProgressText);
+ }
+
+ if ( m_bStartProgress )
+ {
+ m_bStartProgress = false;
+ m_bHasProgress = true;
+ m_xProgressBar->show();
+ m_xProgressText->show();
+ m_xCancelBtn->set_sensitive(true);
+ m_xCancelBtn->show();
+ }
+
+ if ( m_xProgressBar->get_visible() )
+ m_xProgressBar->set_percentage( static_cast<sal_uInt16>(m_nProgress) );
+ }
+}
+
+void ExtMgrDialog::Close()
+{
+ m_pManager->terminateDialog();
+ m_bClosed = true;
+}
+
+//UpdateRequiredDialog
+UpdateRequiredDialog::UpdateRequiredDialog(weld::Window *pParent, TheExtensionManager *pManager)
+ : GenericDialogController(pParent, "desktop/ui/updaterequireddialog.ui", "UpdateRequiredDialog")
+ , DialogHelper(pManager->getContext(), m_xDialog.get())
+ , m_sCloseText(DpResId(RID_STR_CLOSE_BTN))
+ , m_bHasProgress(false)
+ , m_bProgressChanged(false)
+ , m_bStartProgress(false)
+ , m_bStopProgress(false)
+ , m_bHasLockedEntries(false)
+ , m_nProgress(0)
+ , m_aIdle( "UpdateRequiredDialog m_aIdle TimeOutHdl" )
+ , m_pManager(pManager)
+ , m_xExtensionBox(new ExtensionBox_Impl(m_xBuilder->weld_scrolled_window("scroll", true)))
+ , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox))
+ , m_xUpdateNeeded(m_xBuilder->weld_label("updatelabel"))
+ , m_xUpdateBtn(m_xBuilder->weld_button("ok"))
+ , m_xCloseBtn(m_xBuilder->weld_button("disable"))
+ , m_xCancelBtn(m_xBuilder->weld_button("cancel"))
+ , m_xProgressText(m_xBuilder->weld_label("progresslabel"))
+ , m_xProgressBar(m_xBuilder->weld_progress_bar("progress"))
+{
+ m_xExtensionBox->setExtensionManager(pManager);
+
+ m_xUpdateBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleUpdateBtn ) );
+ m_xCloseBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCloseBtn ) );
+ m_xCancelBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCancelBtn ) );
+
+ OUString aText = m_xUpdateNeeded->get_label();
+ aText = aText.replaceAll(
+ "%PRODUCTNAME", utl::ConfigManager::getProductName());
+ m_xUpdateNeeded->set_label(aText);
+
+ m_xProgressBar->hide();
+ m_xUpdateBtn->set_sensitive( false );
+ m_xCloseBtn->grab_focus();
+
+ m_aIdle.SetPriority( TaskPriority::LOWEST );
+ m_aIdle.SetInvokeHandler( LINK( this, UpdateRequiredDialog, TimeOutHdl ) );
+}
+
+UpdateRequiredDialog::~UpdateRequiredDialog()
+{
+ m_aIdle.Stop();
+}
+
+void UpdateRequiredDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage,
+ bool bLicenseMissing )
+{
+ // We will only add entries to the list with unsatisfied dependencies
+ if ( !bLicenseMissing && !checkDependencies( xPackage ) )
+ {
+ m_bHasLockedEntries |= m_pManager->isReadOnly( xPackage );
+ const SolarMutexGuard aGuard;
+ m_xUpdateBtn->set_sensitive(true);
+ m_xExtensionBox->addEntry( xPackage );
+ }
+}
+
+
+void UpdateRequiredDialog::prepareChecking()
+{
+ m_xExtensionBox->prepareChecking();
+}
+
+
+void UpdateRequiredDialog::checkEntries()
+{
+ const SolarMutexGuard guard;
+ m_xExtensionBox->checkEntries();
+
+ if ( ! hasActiveEntries() )
+ {
+ m_xCloseBtn->set_label( m_sCloseText );
+ m_xCloseBtn->grab_focus();
+ }
+}
+
+
+IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCancelBtn, weld::Button&, void)
+{
+ if ( m_xAbortChannel.is() )
+ {
+ try
+ {
+ m_xAbortChannel->sendAbort();
+ }
+ catch ( const uno::RuntimeException & )
+ {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ }
+}
+
+
+IMPL_LINK( UpdateRequiredDialog, startProgress, void*, _bLockInterface, void )
+{
+ std::unique_lock aGuard( m_aMutex );
+ bool bLockInterface = static_cast<bool>(_bLockInterface);
+
+ if ( m_bStartProgress && !m_bHasProgress )
+ m_aIdle.Start();
+
+ if ( m_bStopProgress )
+ {
+ if ( m_xProgressBar->get_visible() )
+ m_xProgressBar->set_percentage( 100 );
+ m_xAbortChannel.clear();
+ SAL_INFO( "desktop.deployment", " startProgress handler: stop" );
+ }
+ else
+ {
+ SAL_INFO( "desktop.deployment", " startProgress handler: start" );
+ }
+
+ m_xCancelBtn->set_sensitive( bLockInterface );
+ m_xUpdateBtn->set_sensitive( false );
+ clearEventID();
+}
+
+
+void UpdateRequiredDialog::showProgress( bool _bStart )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ bool bStart = _bStart;
+
+ if ( bStart )
+ {
+ m_nProgress = 0;
+ m_bStartProgress = true;
+ SAL_INFO( "desktop.deployment", "showProgress start" );
+ }
+ else
+ {
+ m_nProgress = 100;
+ m_bStopProgress = true;
+ SAL_INFO( "desktop.deployment", "showProgress stop!" );
+ }
+
+ DialogHelper::PostUserEvent( LINK( this, UpdateRequiredDialog, startProgress ), reinterpret_cast<void*>(bStart) );
+ m_aIdle.Start();
+}
+
+
+void UpdateRequiredDialog::updateProgress( const tools::Long nProgress )
+{
+ if ( m_nProgress != nProgress )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_nProgress = nProgress;
+ m_aIdle.Start();
+ }
+}
+
+
+void UpdateRequiredDialog::updateProgress( const OUString &rText,
+ const uno::Reference< task::XAbortChannel > &xAbortChannel)
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_xAbortChannel = xAbortChannel;
+ m_sProgressText = rText;
+ m_bProgressChanged = true;
+ m_aIdle.Start();
+}
+
+
+void UpdateRequiredDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ // We will remove all updated packages with satisfied dependencies, but
+ // we will show all disabled entries so the user sees the result
+ // of the 'disable all' button
+ const SolarMutexGuard aGuard;
+ if ( isEnabled( xPackage ) && checkDependencies( xPackage ) )
+ m_xExtensionBox->removeEntry( xPackage );
+ else
+ m_xExtensionBox->updateEntry( xPackage );
+
+ if ( ! hasActiveEntries() )
+ {
+ m_xCloseBtn->set_label( m_sCloseText );
+ m_xCloseBtn->grab_focus();
+ }
+}
+
+
+IMPL_LINK_NOARG(UpdateRequiredDialog, HandleUpdateBtn, weld::Button&, void)
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ std::vector< uno::Reference< deployment::XPackage > > vUpdateEntries;
+ sal_Int32 nCount = m_xExtensionBox->GetEntryCount();
+
+ for ( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( i );
+ vUpdateEntries.push_back( pEntry->m_xPackage );
+ }
+
+ aGuard.unlock();
+
+ m_pManager->getCmdQueue()->checkForUpdates( std::move(vUpdateEntries) );
+}
+
+
+IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCloseBtn, weld::Button&, void)
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !isBusy() )
+ {
+ if ( m_bHasLockedEntries )
+ m_xDialog->response(-1);
+ else if ( hasActiveEntries() )
+ disableAllEntries();
+ else
+ m_xDialog->response(RET_CANCEL);
+ }
+}
+
+
+IMPL_LINK_NOARG(UpdateRequiredDialog, TimeOutHdl, Timer *, void)
+{
+ if ( m_bStopProgress )
+ {
+ m_bHasProgress = false;
+ m_bStopProgress = false;
+ m_xProgressText->hide();
+ m_xProgressBar->hide();
+ m_xCancelBtn->hide();
+ }
+ else
+ {
+ if ( m_bProgressChanged )
+ {
+ m_bProgressChanged = false;
+ m_xProgressText->set_label( m_sProgressText );
+ }
+
+ if ( m_bStartProgress )
+ {
+ m_bStartProgress = false;
+ m_bHasProgress = true;
+ m_xProgressBar->show();
+ m_xProgressText->show();
+ m_xCancelBtn->set_sensitive(true);
+ m_xCancelBtn->show();
+ }
+
+ if (m_xProgressBar->get_visible())
+ m_xProgressBar->set_percentage(m_nProgress);
+ }
+}
+
+// VCL::Dialog
+short UpdateRequiredDialog::run()
+{
+ //ToDo
+ //I believe m_bHasLockedEntries was used to prevent showing extensions which cannot
+ //be disabled because they are in a read only repository. However, disabling extensions
+ //is now always possible because the registration data of all repositories
+ //are in the user installation.
+ //Therefore all extensions could be displayed and all the handling around m_bHasLockedEntries
+ //could be removed.
+ if ( m_bHasLockedEntries )
+ {
+ // Set other text, disable update btn, remove not shared entries from list;
+ m_xUpdateNeeded->set_label( DpResId( RID_STR_NO_ADMIN_PRIVILEGE ) );
+ m_xCloseBtn->set_label( DpResId( RID_STR_EXIT_BTN ) );
+ m_xUpdateBtn->set_sensitive( false );
+ m_xExtensionBox->RemoveUnlocked();
+ }
+
+ return GenericDialogController::run();
+}
+
+// Check dependencies of all packages
+
+bool UpdateRequiredDialog::isEnabled( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ bool bRegistered = false;
+ try {
+ beans::Optional< beans::Ambiguous< sal_Bool > > option( xPackage->isRegistered( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() ) );
+ if ( option.IsPresent )
+ {
+ ::beans::Ambiguous< sal_Bool > const & reg = option.Value;
+ if ( reg.IsAmbiguous )
+ bRegistered = false;
+ else
+ bRegistered = reg.Value;
+ }
+ else
+ bRegistered = false;
+ }
+ catch ( const uno::RuntimeException & ) { throw; }
+ catch (const uno::Exception & ) {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ bRegistered = false;
+ }
+
+ return bRegistered;
+}
+
+// Checks the dependencies no matter if the extension is enabled or disabled!
+bool UpdateRequiredDialog::checkDependencies( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ bool bDependenciesValid = false;
+ try {
+ bDependenciesValid = xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException & ) {}
+ return bDependenciesValid;
+}
+
+
+bool UpdateRequiredDialog::hasActiveEntries()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ bool bRet = false;
+ tools::Long nCount = m_xExtensionBox->GetEntryCount();
+ for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex );
+
+ if ( isEnabled(pEntry->m_xPackage) && !checkDependencies( pEntry->m_xPackage ) )
+ {
+ bRet = true;
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+
+void UpdateRequiredDialog::disableAllEntries()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ incBusy();
+
+ tools::Long nCount = m_xExtensionBox->GetEntryCount();
+ for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex );
+ m_pManager->getCmdQueue()->enableExtension( pEntry->m_xPackage, false );
+ }
+
+ decBusy();
+
+ if ( ! hasActiveEntries() )
+ m_xCloseBtn->set_label( m_sCloseText );
+}
+
+// ShowLicenseDialog
+ShowLicenseDialog::ShowLicenseDialog(weld::Window* pParent,
+ const uno::Reference< deployment::XPackage> &xPackage)
+ : GenericDialogController(pParent, "desktop/ui/showlicensedialog.ui", "ShowLicenseDialog")
+ , m_xLicenseText(m_xBuilder->weld_text_view("textview"))
+{
+ m_xLicenseText->set_size_request(m_xLicenseText->get_approximate_digit_width() * 72,
+ m_xLicenseText->get_height_rows(21));
+ m_xLicenseText->set_text(xPackage->getLicenseText());
+}
+
+ShowLicenseDialog::~ShowLicenseDialog()
+{
+}
+
+// UpdateRequiredDialogService
+
+UpdateRequiredDialogService::UpdateRequiredDialogService( SAL_UNUSED_PARAMETER uno::Sequence< uno::Any > const&,
+ uno::Reference< uno::XComponentContext > xComponentContext )
+ : m_xComponentContext(std::move( xComponentContext ))
+{
+}
+
+// XServiceInfo
+OUString UpdateRequiredDialogService::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.ui.UpdateRequiredDialog";
+}
+
+sal_Bool UpdateRequiredDialogService::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > UpdateRequiredDialogService::getSupportedServiceNames()
+{
+ return { "com.sun.star.deployment.ui.UpdateRequiredDialog" };
+}
+
+
+// XExecutableDialog
+
+void UpdateRequiredDialogService::setTitle( OUString const & )
+{
+}
+
+
+sal_Int16 UpdateRequiredDialogService::execute()
+{
+ ::rtl::Reference< ::dp_gui::TheExtensionManager > xManager( TheExtensionManager::get(
+ m_xComponentContext) );
+ xManager->createDialog( true );
+ sal_Int16 nRet = xManager->execute();
+
+ return nRet;
+}
+
+
+} //namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.hxx b/desktop/source/deployment/gui/dp_gui_dialog2.hxx
new file mode 100644
index 0000000000..b235aed185
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_dialog2.hxx
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/locktoplevels.hxx>
+#include <vcl/customweld.hxx>
+#include <vcl/weld.hxx>
+
+#include <mutex>
+
+#include <rtl/ustring.hxx>
+
+#include <cppuhelper/implbase.hxx>
+#include <unotools/resmgr.hxx>
+
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+struct ImplSVEvent;
+
+namespace dp_gui {
+
+class ExtBoxWithBtns_Impl;
+class ExtensionBox_Impl;
+class TheExtensionManager;
+
+class DialogHelper
+{
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ weld::Window* m_pWindow;
+ ImplSVEvent * m_nEventID;
+ TopLevelWindowLocker m_aBusy;
+
+public:
+ DialogHelper(const css::uno::Reference< css::uno::XComponentContext > &,
+ weld::Window* pWindow);
+ virtual ~DialogHelper();
+
+ void openWebBrowser(const OUString& rURL, const OUString& rTitle);
+ weld::Window* getFrameWeld() const { return m_pWindow; }
+ void PostUserEvent( const Link<void*,void>& rLink, void* pCaller );
+ void clearEventID() { m_nEventID = nullptr; }
+
+ virtual void showProgress( bool bStart ) = 0;
+ virtual void updateProgress( const OUString &rText,
+ const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) = 0;
+ virtual void updateProgress( const tools::Long nProgress ) = 0;
+
+ virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) = 0;
+ virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &xPackage,
+ bool bLicenseMissing = false ) = 0;
+
+ virtual void prepareChecking() = 0;
+ virtual void checkEntries() = 0;
+
+ static bool IsSharedPkgMgr( const css::uno::Reference< css::deployment::XPackage > &);
+ bool continueOnSharedExtension( const css::uno::Reference< css::deployment::XPackage > &,
+ weld::Widget* pParent,
+ TranslateId pResID,
+ bool &bHadWarning );
+
+ void incBusy() { m_aBusy.incBusy(m_pWindow); }
+ void decBusy() { m_aBusy.decBusy(); }
+ bool isBusy() const { return m_aBusy.isBusy(); }
+ bool installExtensionWarn(std::u16string_view rExtensionURL);
+ bool installForAllUsers(bool &bInstallForAll);
+};
+
+class ExtMgrDialog : public weld::GenericDialogController
+ , public DialogHelper
+{
+ const OUString m_sAddPackages;
+ OUString m_sProgressText;
+ std::mutex m_aMutex;
+ bool m_bHasProgress;
+ bool m_bProgressChanged;
+ bool m_bStartProgress;
+ bool m_bStopProgress;
+ bool m_bEnableWarning;
+ bool m_bDisableWarning;
+ bool m_bDeleteWarning;
+ bool m_bClosed;
+ tools::Long m_nProgress;
+ Idle m_aIdle;
+ TheExtensionManager *m_pManager;
+
+ css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel;
+
+ std::unique_ptr<ExtBoxWithBtns_Impl> m_xExtensionBox;
+ std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd;
+ std::unique_ptr<weld::Button> m_xOptionsBtn;
+ std::unique_ptr<weld::Button> m_xAddBtn;
+ std::unique_ptr<weld::Button> m_xRemoveBtn;
+ std::unique_ptr<weld::Button> m_xEnableBtn;
+ std::unique_ptr<weld::Button> m_xUpdateBtn;
+ std::unique_ptr<weld::Button> m_xCloseBtn;
+ std::unique_ptr<weld::CheckButton> m_xBundledCbx;
+ std::unique_ptr<weld::CheckButton> m_xSharedCbx;
+ std::unique_ptr<weld::CheckButton> m_xUserCbx;
+ std::unique_ptr<weld::LinkButton> m_xGetExtensions;
+ std::unique_ptr<weld::Label> m_xProgressText;
+ std::unique_ptr<weld::ProgressBar> m_xProgressBar;
+ std::unique_ptr<weld::Button> m_xCancelBtn;
+ std::unique_ptr<weld::Entry> m_xSearchEntry;
+
+ bool removeExtensionWarn(std::u16string_view rExtensionTitle);
+
+ DECL_LINK( HandleOptionsBtn, weld::Button&, void );
+ DECL_LINK( HandleAddBtn, weld::Button&, void );
+ DECL_LINK( HandleRemoveBtn, weld::Button&, void );
+ DECL_LINK( HandleEnableBtn, weld::Button&, void );
+ DECL_LINK( HandleUpdateBtn, weld::Button&, void );
+ DECL_LINK( HandleCancelBtn, weld::Button&, void );
+ DECL_LINK( HandleCloseBtn, weld::Button&, void );
+ DECL_LINK( HandleExtTypeCbx, weld::Toggleable&, void );
+ DECL_LINK( HandleSearch, weld::Entry&, void );
+ DECL_LINK( TimeOutHdl, Timer *, void );
+ DECL_LINK( startProgress, void *, void );
+
+public:
+ ExtMgrDialog(weld::Window * pParent, TheExtensionManager *pManager);
+ virtual ~ExtMgrDialog() override;
+
+ virtual void showProgress( bool bStart ) override;
+ virtual void updateProgress( const OUString &rText,
+ const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override;
+ virtual void updateProgress( const tools::Long nProgress ) override;
+
+ virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override;
+
+ void setGetExtensionsURL( const OUString &rURL );
+ virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &,
+ bool bLicenseMissing = false ) override;
+ void enablePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage,
+ bool bEnable );
+ void removePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage );
+ void updatePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage );
+ bool acceptLicense(const css::uno::Reference< css::deployment::XPackage > &xPackage );
+
+ void Close();
+
+ TheExtensionManager* getExtensionManager() const { return m_pManager; }
+
+ void updateList();
+ virtual void prepareChecking() override;
+ virtual void checkEntries() override;
+
+ css::uno::Sequence< OUString > raiseAddPicker();
+
+ void enableOptionsButton( bool bEnable );
+ void enableRemoveButton( bool bEnable );
+ void enableEnableButton( bool bEnable );
+ /*
+ * Transform the button to "Enable", or to "Disable"
+ * based on the value of bEnable.
+ */
+ void enableButtontoEnable( bool bEnable );
+};
+
+
+class UpdateRequiredDialog : public weld::GenericDialogController
+ , public DialogHelper
+{
+ const OUString m_sCloseText;
+ OUString m_sProgressText;
+ std::mutex m_aMutex;
+ bool m_bHasProgress;
+ bool m_bProgressChanged;
+ bool m_bStartProgress;
+ bool m_bStopProgress;
+ bool m_bHasLockedEntries;
+ tools::Long m_nProgress;
+ Idle m_aIdle;
+ TheExtensionManager *m_pManager;
+
+ css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel;
+
+ std::unique_ptr<ExtensionBox_Impl> m_xExtensionBox;
+ std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd;
+ std::unique_ptr<weld::Label> m_xUpdateNeeded;
+ std::unique_ptr<weld::Button> m_xUpdateBtn;
+ std::unique_ptr<weld::Button> m_xCloseBtn;
+ std::unique_ptr<weld::Button> m_xCancelBtn;
+ std::unique_ptr<weld::Label> m_xProgressText;
+ std::unique_ptr<weld::ProgressBar> m_xProgressBar;
+
+ DECL_LINK( HandleUpdateBtn, weld::Button&, void );
+ DECL_LINK( HandleCloseBtn, weld::Button&, void );
+ DECL_LINK( HandleCancelBtn, weld::Button&, void );
+ DECL_LINK( TimeOutHdl, Timer *, void );
+ DECL_LINK( startProgress, void *, void );
+
+ static bool isEnabled( const css::uno::Reference< css::deployment::XPackage > &xPackage );
+ static bool checkDependencies( const css::uno::Reference< css::deployment::XPackage > &xPackage );
+ bool hasActiveEntries();
+ void disableAllEntries();
+
+public:
+ UpdateRequiredDialog(weld::Window * pParent, TheExtensionManager *pManager);
+ virtual ~UpdateRequiredDialog() override;
+
+ virtual short run() override;
+
+ virtual void showProgress( bool bStart ) override;
+ virtual void updateProgress( const OUString &rText,
+ const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override;
+ virtual void updateProgress( const tools::Long nProgress ) override;
+
+ virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override;
+
+ virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &,
+ bool bLicenseMissing = false ) override;
+
+ virtual void prepareChecking() override;
+ virtual void checkEntries() override;
+};
+
+
+class ShowLicenseDialog : public weld::GenericDialogController
+{
+ std::unique_ptr<weld::TextView> m_xLicenseText;
+public:
+ ShowLicenseDialog(weld::Window * pParent, const css::uno::Reference< css::deployment::XPackage > &xPackage);
+ virtual ~ShowLicenseDialog() override;
+};
+
+class UpdateRequiredDialogService : public ::cppu::WeakImplHelper< css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo >
+{
+ css::uno::Reference< css::uno::XComponentContext > const m_xComponentContext;
+public:
+ UpdateRequiredDialogService( css::uno::Sequence< css::uno::Any > const & args,
+ css::uno::Reference< css::uno::XComponentContext> xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XExecutableDialog
+ virtual void SAL_CALL setTitle( OUString const & title ) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+};
+
+} // namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx
new file mode 100644
index 0000000000..fd70b79822
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx
@@ -0,0 +1,1115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+
+#include <com/sun/star/deployment/DependencyException.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/VersionException.hpp>
+#include <com/sun/star/deployment/InstallException.hpp>
+#include <com/sun/star/deployment/PlatformException.hpp>
+
+#include <com/sun/star/deployment/ui/LicenseDialog.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/task/XAbortChannel.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/TypeClass.hpp>
+#include <o3tl/any.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <salhelper/thread.hxx>
+#include <ucbhelper/content.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/anytostring.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include "dp_gui_extensioncmdqueue.hxx"
+#include "dp_gui_dependencydialog.hxx"
+#include "dp_gui_dialog2.hxx"
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include "dp_gui_theextmgr.hxx"
+#include "dp_gui_updatedialog.hxx"
+#include "dp_gui_updateinstalldialog.hxx"
+#include <dp_dependencies.hxx>
+#include <dp_misc.h>
+#include <dp_identifier.hxx>
+#include <dp_version.hxx>
+
+#include <condition_variable>
+#include <queue>
+#include <memory>
+#include <mutex>
+
+#ifdef _WIN32
+#include <o3tl/safeCoInitUninit.hxx>
+#endif
+
+
+using namespace ::com::sun::star;
+
+namespace {
+
+OUString getVersion( OUString const & sVersion )
+{
+ return ( sVersion.isEmpty() ) ? OUString( "0" ) : sVersion;
+}
+
+OUString getVersion( const uno::Reference< deployment::XPackage > &rPackage )
+{
+ return getVersion( rPackage->getVersion());
+}
+}
+
+
+namespace dp_gui {
+
+namespace {
+
+class ProgressCmdEnv
+ : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment,
+ task::XInteractionHandler,
+ ucb::XProgressHandler >
+{
+ uno::Reference< task::XInteractionHandler2> m_xHandler;
+ uno::Reference< uno::XComponentContext > m_xContext;
+
+ DialogHelper* m_pDialogHelper;
+ OUString m_sTitle;
+ bool m_bWarnUser;
+ sal_Int32 m_nCurrentProgress;
+
+ void updateProgress();
+
+ /// @throws uno::RuntimeException
+ void update_( uno::Any const & Status );
+
+public:
+ /** When param bAskWhenInstalling = true, then the user is asked if he
+ agrees to install this extension. In case this extension is already installed
+ then the user is also notified and asked if he wants to replace that existing
+ extension. In first case an interaction request with an InstallException
+ will be handled and in the second case a VersionException will be handled.
+ */
+
+ ProgressCmdEnv( uno::Reference< uno::XComponentContext > xContext,
+ DialogHelper* pDialogHelper,
+ OUString aTitle )
+ : m_xContext(std::move( xContext ))
+ , m_pDialogHelper( pDialogHelper )
+ , m_sTitle(std::move( aTitle ))
+ , m_bWarnUser( false )
+ , m_nCurrentProgress(0)
+ {}
+
+ weld::Window* activeDialog() { return m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr; }
+
+ void startProgress();
+ void stopProgress();
+ void progressSection( const OUString &rText,
+ const uno::Reference< task::XAbortChannel > &xAbortChannel );
+ void setWarnUser( bool bNewVal ) { m_bWarnUser = bNewVal; }
+
+ // XCommandEnvironment
+ virtual uno::Reference< task::XInteractionHandler > SAL_CALL getInteractionHandler() override;
+ virtual uno::Reference< ucb::XProgressHandler > SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle( uno::Reference< task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( uno::Any const & Status ) override;
+ virtual void SAL_CALL update( uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+
+struct ExtensionCmd
+{
+ enum E_CMD_TYPE { ADD, ENABLE, DISABLE, REMOVE, CHECK_FOR_UPDATES, ACCEPT_LICENSE };
+
+ E_CMD_TYPE m_eCmdType;
+ bool m_bWarnUser;
+ OUString m_sExtensionURL;
+ OUString m_sRepository;
+ uno::Reference< deployment::XPackage > m_xPackage;
+ std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList;
+
+ ExtensionCmd( const E_CMD_TYPE eCommand,
+ OUString aExtensionURL,
+ OUString aRepository,
+ const bool bWarnUser )
+ : m_eCmdType( eCommand ),
+ m_bWarnUser( bWarnUser ),
+ m_sExtensionURL(std::move( aExtensionURL )),
+ m_sRepository(std::move( aRepository )) {};
+ ExtensionCmd( const E_CMD_TYPE eCommand,
+ uno::Reference< deployment::XPackage > xPackage )
+ : m_eCmdType( eCommand ),
+ m_bWarnUser( false ),
+ m_xPackage(std::move( xPackage )) {};
+ ExtensionCmd( const E_CMD_TYPE eCommand,
+ std::vector<uno::Reference<deployment::XPackage > >&&vExtensionList )
+ : m_eCmdType( eCommand ),
+ m_bWarnUser( false ),
+ m_vExtensionList( std::move(vExtensionList) ) {};
+};
+
+}
+
+typedef std::shared_ptr< ExtensionCmd > TExtensionCmd;
+
+
+class ExtensionCmdQueue::Thread: public salhelper::Thread
+{
+public:
+ Thread( DialogHelper *pDialogHelper,
+ TheExtensionManager *pManager,
+ uno::Reference< uno::XComponentContext > xContext );
+
+ void addExtension( const OUString &rExtensionURL,
+ const OUString &rRepository,
+ const bool bWarnUser );
+ void removeExtension( const uno::Reference< deployment::XPackage > &rPackage );
+ void enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
+ const bool bEnable );
+ void checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList );
+ void acceptLicense( const uno::Reference< deployment::XPackage > &rPackage );
+ void stop();
+ bool isBusy();
+
+private:
+ virtual ~Thread() override;
+
+ virtual void execute() override;
+
+ void _insert(const TExtensionCmd& rExtCmd);
+
+ void _addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const OUString &rPackageURL,
+ const OUString &rRepository,
+ const bool bWarnUser );
+ void _removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage );
+ void _enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage );
+ void _disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage );
+ void _checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > &&vExtensionList );
+ void _acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage );
+
+ enum Input { NONE, START, STOP };
+
+ uno::Reference< uno::XComponentContext > m_xContext;
+ std::queue< TExtensionCmd > m_queue;
+
+ DialogHelper *m_pDialogHelper;
+ TheExtensionManager *m_pManager;
+
+ const OUString m_sEnablingPackages;
+ const OUString m_sDisablingPackages;
+ const OUString m_sAddingPackages;
+ const OUString m_sRemovingPackages;
+ const OUString m_sDefaultCmd;
+ const OUString m_sAcceptLicense;
+ std::condition_variable m_wakeup;
+ std::mutex m_mutex;
+ Input m_eInput;
+ bool m_bStopped;
+ bool m_bWorking;
+};
+
+
+void ProgressCmdEnv::startProgress()
+{
+ m_nCurrentProgress = 0;
+
+ if ( m_pDialogHelper )
+ m_pDialogHelper->showProgress( true );
+}
+
+
+void ProgressCmdEnv::stopProgress()
+{
+ if ( m_pDialogHelper )
+ m_pDialogHelper->showProgress( false );
+}
+
+
+void ProgressCmdEnv::progressSection( const OUString &rText,
+ const uno::Reference< task::XAbortChannel > &xAbortChannel )
+{
+ m_nCurrentProgress = 0;
+ if ( m_pDialogHelper )
+ {
+ m_pDialogHelper->updateProgress( rText, xAbortChannel );
+ m_pDialogHelper->updateProgress( 5 );
+ }
+}
+
+
+void ProgressCmdEnv::updateProgress()
+{
+ tools::Long nProgress = ((m_nCurrentProgress*5) % 100) + 5;
+ if ( m_pDialogHelper )
+ m_pDialogHelper->updateProgress( nProgress );
+}
+
+// XCommandEnvironment
+
+uno::Reference< task::XInteractionHandler > ProgressCmdEnv::getInteractionHandler()
+{
+ return this;
+}
+
+
+uno::Reference< ucb::XProgressHandler > ProgressCmdEnv::getProgressHandler()
+{
+ return this;
+}
+
+
+// XInteractionHandler
+
+void ProgressCmdEnv::handle( uno::Reference< task::XInteractionRequest > const & xRequest )
+{
+ uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
+ dp_misc::TRACE( "[dp_gui_cmdenv.cxx] incoming request:\n"
+ + ::comphelper::anyToString(request) + "\n");
+
+ lang::WrappedTargetException wtExc;
+ deployment::DependencyException depExc;
+ deployment::LicenseException licExc;
+ deployment::VersionException verExc;
+ deployment::InstallException instExc;
+ deployment::PlatformException platExc;
+
+ // selections:
+ bool approve = false;
+ bool abort = false;
+
+ if (request >>= wtExc) {
+ // handable deployment error signalled, e.g.
+ // bundle item registration failed, notify cause only:
+ uno::Any cause;
+ deployment::DeploymentException dpExc;
+ if (wtExc.TargetException >>= dpExc)
+ cause = dpExc.Cause;
+ else {
+ ucb::CommandFailedException cfExc;
+ if (wtExc.TargetException >>= cfExc)
+ cause = cfExc.Reason;
+ else
+ cause = wtExc.TargetException;
+ }
+ update_( cause );
+
+ // ignore intermediate errors of legacy packages, i.e.
+ // former pkgchk behaviour:
+ const uno::Reference< deployment::XPackage > xPackage( wtExc.Context, uno::UNO_QUERY );
+ OSL_ASSERT( xPackage.is() );
+ if ( xPackage.is() )
+ {
+ const uno::Reference< deployment::XPackageTypeInfo > xPackageType( xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is())
+ {
+ approve = ( xPackage->isBundle() &&
+ xPackageType->getMediaType().match(
+ "application/vnd.sun.star.legacy-package-bundle" ));
+ }
+ }
+ abort = !approve;
+ }
+ else if (request >>= depExc)
+ {
+ std::vector< OUString > deps;
+ deps.reserve(depExc.UnsatisfiedDependencies.getLength());
+ for (auto const & i : std::as_const(depExc.UnsatisfiedDependencies))
+ {
+ deps.push_back( dp_misc::Dependencies::getErrorText(i) );
+ }
+ {
+ SolarMutexGuard guard;
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+ DependencyDialog aDlg(activeDialog(), deps);
+ short n = aDlg.run();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ // Distinguish between closing the dialog and programmatically
+ // canceling the dialog (headless VCL):
+ approve = n == RET_OK
+ || (n == RET_CANCEL && !Application::IsDialogCancelEnabled());
+ }
+ }
+ else if (request >>= licExc)
+ {
+ SolarMutexGuard guard;
+
+ weld::Window *pTopLevel = activeDialog();
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+ uno::Reference< ui::dialogs::XExecutableDialog > xDialog(
+ deployment::ui::LicenseDialog::create(
+ m_xContext, pTopLevel ? pTopLevel->GetXWindow() : nullptr,
+ licExc.ExtensionName, licExc.Text ) );
+ sal_Int16 res = xDialog->execute();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ if ( res == ui::dialogs::ExecutableDialogResults::CANCEL )
+ abort = true;
+ else if ( res == ui::dialogs::ExecutableDialogResults::OK )
+ approve = true;
+ else
+ {
+ OSL_ASSERT(false);
+ }
+ }
+ else if (request >>= verExc)
+ {
+ TranslateId id;
+ switch (dp_misc::compareVersions(
+ verExc.NewVersion, verExc.Deployed->getVersion() ))
+ {
+ case dp_misc::LESS:
+ id = RID_STR_WARNING_VERSION_LESS;
+ break;
+ case dp_misc::EQUAL:
+ id = RID_STR_WARNING_VERSION_EQUAL;
+ break;
+ default: // dp_misc::GREATER
+ id = RID_STR_WARNING_VERSION_GREATER;
+ break;
+ }
+ OSL_ASSERT( verExc.Deployed.is() );
+ bool bEqualNames = verExc.NewDisplayName ==
+ verExc.Deployed->getDisplayName();
+ {
+ SolarMutexGuard guard;
+
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
+ VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(id)));
+ OUString s;
+ if (bEqualNames)
+ {
+ s = xBox->get_primary_text();
+ }
+ else if (id != RID_STR_WARNING_VERSION_EQUAL)
+ {
+ //hypothetical: requires two instances of an extension with the same
+ //version to have different display names. Probably the developer forgot
+ //to change the version.
+ s = DpResId(RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES);
+ }
+ else if (id != RID_STR_WARNING_VERSION_LESS)
+ {
+ s = DpResId(RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES);
+ }
+ else if (id != RID_STR_WARNING_VERSION_GREATER)
+ {
+ s = DpResId(RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES);
+ }
+ s = s.replaceAll("$NAME", verExc.NewDisplayName);
+ s = s.replaceAll("$OLDNAME", verExc.Deployed->getDisplayName());
+ s = s.replaceAll("$NEW", getVersion(verExc.NewVersion));
+ s = s.replaceAll("$DEPLOYED", getVersion(verExc.Deployed));
+ xBox->set_primary_text(s);
+ approve = xBox->run() == RET_OK;
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ abort = !approve;
+ }
+ }
+ else if (request >>= instExc)
+ {
+ if ( ! m_bWarnUser )
+ {
+ approve = true;
+ }
+ else
+ {
+ if ( m_pDialogHelper )
+ {
+ SolarMutexGuard guard;
+
+ approve = m_pDialogHelper->installExtensionWarn( instExc.displayName );
+ }
+ else
+ approve = false;
+ abort = !approve;
+ }
+ }
+ else if (request >>= platExc)
+ {
+ SolarMutexGuard guard;
+ OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM));
+ sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName());
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
+ VclMessageType::Warning, VclButtonsType::Ok, sMsg));
+ xBox->run();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ approve = true;
+ }
+
+ if (!approve && !abort)
+ {
+ // forward to UUI handler:
+ if (! m_xHandler.is()) {
+ // late init:
+ m_xHandler = task::InteractionHandler::createWithParentAndContext(m_xContext, nullptr, m_sTitle);
+ }
+ m_xHandler->handle( xRequest );
+ }
+ else
+ {
+ // select:
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > conts(
+ xRequest->getContinuations() );
+ uno::Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if (approve) {
+ uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY );
+ if (xInteractionApprove.is()) {
+ xInteractionApprove->select();
+ // don't query again for ongoing continuations:
+ approve = false;
+ }
+ }
+ else if (abort) {
+ uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY );
+ if (xInteractionAbort.is()) {
+ xInteractionAbort->select();
+ // don't query again for ongoing continuations:
+ abort = false;
+ }
+ }
+ }
+ }
+}
+
+
+// XProgressHandler
+
+void ProgressCmdEnv::push( uno::Any const & rStatus )
+{
+ update_( rStatus );
+}
+
+
+void ProgressCmdEnv::update_( uno::Any const & rStatus )
+{
+ OUString text;
+ if ( rStatus.hasValue() && !( rStatus >>= text) )
+ {
+ if ( auto e = o3tl::tryAccess<uno::Exception>(rStatus) )
+ text = e->Message;
+ if ( text.isEmpty() )
+ text = ::comphelper::anyToString( rStatus ); // fallback
+
+ const SolarMutexGuard aGuard;
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
+ VclMessageType::Warning, VclButtonsType::Ok, text));
+ xBox->run();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ }
+ ++m_nCurrentProgress;
+ updateProgress();
+}
+
+
+void ProgressCmdEnv::update( uno::Any const & rStatus )
+{
+ update_( rStatus );
+}
+
+
+void ProgressCmdEnv::pop()
+{
+ update_( uno::Any() ); // no message
+}
+
+
+ExtensionCmdQueue::Thread::Thread( DialogHelper *pDialogHelper,
+ TheExtensionManager *pManager,
+ uno::Reference< uno::XComponentContext > xContext ) :
+ salhelper::Thread( "dp_gui_extensioncmdqueue" ),
+ m_xContext(std::move( xContext )),
+ m_pDialogHelper( pDialogHelper ),
+ m_pManager( pManager ),
+ m_sEnablingPackages( DpResId( RID_STR_ENABLING_PACKAGES ) ),
+ m_sDisablingPackages( DpResId( RID_STR_DISABLING_PACKAGES ) ),
+ m_sAddingPackages( DpResId( RID_STR_ADDING_PACKAGES ) ),
+ m_sRemovingPackages( DpResId( RID_STR_REMOVING_PACKAGES ) ),
+ m_sDefaultCmd( DpResId( RID_STR_ADD_PACKAGES ) ),
+ m_sAcceptLicense( DpResId( RID_STR_ACCEPT_LICENSE ) ),
+ m_eInput( NONE ),
+ m_bStopped( false ),
+ m_bWorking( false )
+{
+ OSL_ASSERT( pDialogHelper );
+}
+
+
+void ExtensionCmdQueue::Thread::addExtension( const OUString &rExtensionURL,
+ const OUString &rRepository,
+ const bool bWarnUser )
+{
+ if ( !rExtensionURL.isEmpty() )
+ {
+ TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ADD, rExtensionURL, rRepository, bWarnUser );
+ _insert( pEntry );
+ }
+}
+
+
+void ExtensionCmdQueue::Thread::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
+{
+ if ( rPackage.is() )
+ {
+ TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::REMOVE, rPackage );
+ _insert( pEntry );
+ }
+}
+
+
+void ExtensionCmdQueue::Thread::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
+{
+ if ( rPackage.is() )
+ {
+ TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ACCEPT_LICENSE, rPackage );
+ _insert( pEntry );
+ }
+}
+
+
+void ExtensionCmdQueue::Thread::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
+ const bool bEnable )
+{
+ if ( rPackage.is() )
+ {
+ TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( bEnable ? ExtensionCmd::ENABLE :
+ ExtensionCmd::DISABLE,
+ rPackage );
+ _insert( pEntry );
+ }
+}
+
+
+void ExtensionCmdQueue::Thread::checkForUpdates(
+ std::vector<uno::Reference<deployment::XPackage > > && vExtensionList )
+{
+ TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::CHECK_FOR_UPDATES, std::move(vExtensionList) );
+ _insert( pEntry );
+}
+
+
+//Stopping this thread will not abort the installation of extensions.
+void ExtensionCmdQueue::Thread::stop()
+{
+ std::scoped_lock aGuard( m_mutex );
+ m_bStopped = true;
+ m_eInput = STOP;
+ m_wakeup.notify_all();
+}
+
+
+bool ExtensionCmdQueue::Thread::isBusy()
+{
+ std::scoped_lock aGuard( m_mutex );
+ return m_bWorking;
+}
+
+
+ExtensionCmdQueue::Thread::~Thread() {}
+
+
+void ExtensionCmdQueue::Thread::execute()
+{
+#ifdef _WIN32
+ //Needed for use of the service "com.sun.star.system.SystemShellExecute" in
+ //DialogHelper::openWebBrowser
+ int nNbCallCoInitializeExForReinit = 0;
+ o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit);
+#endif
+ for (;;)
+ {
+ int nSize;
+ Input eInput;
+ {
+ std::unique_lock aGuard( m_mutex );
+ while (m_eInput == NONE) {
+ m_wakeup.wait(aGuard);
+ }
+ eInput = m_eInput;
+ m_eInput = NONE;
+ nSize = m_queue.size();
+ // coverity[missing_lock: FALSE] - maybe due to (by-design) unique_lock vs. scoped_lock?
+ m_bWorking = false;
+ }
+
+ if ( eInput == STOP )
+ break;
+
+ // We only install the extension which are currently in the queue.
+ // The progressbar will be set to show the progress of the current number
+ // of extensions. If we allowed to add extensions now then the progressbar may
+ // have reached the end while we still install newly added extensions.
+ if ( nSize == 0 )
+ continue;
+
+ ::rtl::Reference< ProgressCmdEnv > currentCmdEnv( new ProgressCmdEnv( m_xContext, m_pDialogHelper, m_sDefaultCmd ) );
+
+ // Do not lock the following part with addExtension. addExtension may be called in the main thread.
+ // If the message box "Do you want to install the extension (or similar)" is shown and then
+ // addExtension is called, which then blocks the main thread, then we deadlock.
+ bool bStartProgress = true;
+
+ while ( --nSize >= 0 )
+ {
+ {
+ std::scoped_lock aGuard( m_mutex );
+ m_bWorking = true;
+ }
+
+ try
+ {
+ TExtensionCmd pEntry;
+ {
+ std::scoped_lock queueGuard( m_mutex );
+ pEntry = m_queue.front();
+ m_queue.pop();
+ }
+
+ if ( bStartProgress && ( pEntry->m_eCmdType != ExtensionCmd::CHECK_FOR_UPDATES ) )
+ {
+ currentCmdEnv->startProgress();
+ bStartProgress = false;
+ }
+
+ switch ( pEntry->m_eCmdType ) {
+ case ExtensionCmd::ADD :
+ _addExtension( currentCmdEnv, pEntry->m_sExtensionURL, pEntry->m_sRepository, pEntry->m_bWarnUser );
+ break;
+ case ExtensionCmd::REMOVE :
+ _removeExtension( currentCmdEnv, pEntry->m_xPackage );
+ break;
+ case ExtensionCmd::ENABLE :
+ _enableExtension( currentCmdEnv, pEntry->m_xPackage );
+ break;
+ case ExtensionCmd::DISABLE :
+ _disableExtension( currentCmdEnv, pEntry->m_xPackage );
+ break;
+ case ExtensionCmd::CHECK_FOR_UPDATES :
+ _checkForUpdates( std::vector(pEntry->m_vExtensionList) );
+ break;
+ case ExtensionCmd::ACCEPT_LICENSE :
+ _acceptLicense( currentCmdEnv, pEntry->m_xPackage );
+ break;
+ }
+ }
+ catch ( const ucb::CommandAbortedException & )
+ {
+ //This exception is thrown when the user clicks cancel on the progressbar.
+ //Then we cancel the installation of all extensions and remove them from
+ //the queue.
+ {
+ std::scoped_lock queueGuard2(m_mutex);
+ while ( --nSize >= 0 )
+ m_queue.pop();
+ }
+ break;
+ }
+ catch ( const ucb::CommandFailedException & )
+ {
+ //This exception is thrown when a user clicked cancel in the messagebox which was
+ //started by the interaction handler. For example the user will be asked if he/she
+ //really wants to install the extension.
+ //These interactions run for exactly one extension at a time. Therefore we continue
+ //with installing the remaining extensions.
+ continue;
+ }
+ catch ( const uno::Exception & )
+ {
+ //Todo display the user an error
+ //see also DialogImpl::SyncPushButton::Click()
+ uno::Any exc( ::cppu::getCaughtException() );
+ OUString msg;
+ deployment::DeploymentException dpExc;
+ if (exc >>= dpExc)
+ {
+ if (auto e = o3tl::tryAccess<uno::Exception>(dpExc.Cause))
+ {
+ // notify error cause only:
+ msg = e->Message;
+ }
+ }
+ if (msg.isEmpty()) // fallback for debugging purposes
+ msg = ::comphelper::anyToString(exc);
+
+ const SolarMutexGuard guard;
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(currentCmdEnv->activeDialog(),
+ VclMessageType::Warning, VclButtonsType::Ok, msg));
+ if (m_pDialogHelper)
+ xBox->set_title(m_pDialogHelper->getFrameWeld()->get_title());
+ xBox->run();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ //Continue with installation of the remaining extensions
+ }
+ {
+ std::scoped_lock aGuard( m_mutex );
+ m_bWorking = false;
+ }
+ }
+
+ {
+ // when leaving the while loop with break, we should set working to false, too
+ std::scoped_lock aGuard( m_mutex );
+ m_bWorking = false;
+ }
+
+ if ( !bStartProgress )
+ currentCmdEnv->stopProgress();
+ }
+ //end for
+#ifdef _WIN32
+ o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit);
+#endif
+}
+
+
+void ExtensionCmdQueue::Thread::_addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const OUString &rPackageURL,
+ const OUString &rRepository,
+ const bool bWarnUser )
+{
+ //check if we have a string in anyTitle. For example "unopkg gui \" caused anyTitle to be void
+ //and anyTitle.get<OUString> throws as RuntimeException.
+ uno::Any anyTitle;
+ try
+ {
+ anyTitle = ::ucbhelper::Content( rPackageURL, rCmdEnv, m_xContext ).getPropertyValue( "Title" );
+ }
+ catch ( const uno::Exception & )
+ {
+ return;
+ }
+
+ OUString sName;
+ if ( ! (anyTitle >>= sName) )
+ {
+ OSL_FAIL("Could not get file name for extension.");
+ return;
+ }
+
+ rCmdEnv->setWarnUser( bWarnUser );
+ uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
+ uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
+ OUString sTitle(
+ m_sAddingPackages.replaceAll("%EXTENSION_NAME", sName));
+ rCmdEnv->progressSection( sTitle, xAbortChannel );
+
+ try
+ {
+ xExtMgr->addExtension(rPackageURL, uno::Sequence<beans::NamedValue>(),
+ rRepository, xAbortChannel, rCmdEnv );
+ }
+ catch ( const ucb::CommandFailedException & )
+ {
+ // When the extension is already installed we'll get a dialog asking if we want to overwrite. If we then press
+ // cancel this exception is thrown.
+ }
+ catch ( const ucb::CommandAbortedException & )
+ {
+ // User clicked the cancel button
+ // TODO: handle cancel
+ }
+ rCmdEnv->setWarnUser( false );
+}
+
+
+void ExtensionCmdQueue::Thread::_removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage )
+{
+ uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
+ uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
+ OUString sTitle(
+ m_sRemovingPackages.replaceAll("%EXTENSION_NAME",
+ xPackage->getDisplayName()));
+ rCmdEnv->progressSection( sTitle, xAbortChannel );
+
+ OUString id( dp_misc::getIdentifier( xPackage ) );
+ try
+ {
+ xExtMgr->removeExtension( id, xPackage->getName(), xPackage->getRepositoryName(), xAbortChannel, rCmdEnv );
+ }
+ catch ( const deployment::DeploymentException & )
+ {}
+ catch ( const ucb::CommandFailedException & )
+ {}
+ catch ( const ucb::CommandAbortedException & )
+ {}
+
+ // Check, if there are still updates to be notified via menu bar icon
+ uno::Sequence< uno::Sequence< OUString > > aItemList;
+ UpdateDialog::createNotifyJob( false, aItemList );
+}
+
+
+void ExtensionCmdQueue::Thread::_checkForUpdates(
+ std::vector<uno::Reference<deployment::XPackage > > && vExtensionList )
+{
+ const SolarMutexGuard guard;
+
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+
+ std::vector< UpdateData > vData;
+ UpdateDialog aUpdateDialog(m_xContext, m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, std::move(vExtensionList), &vData);
+
+ aUpdateDialog.notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon
+
+ bool bOk = aUpdateDialog.run() == RET_OK;
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+
+ if (bOk && !vData.empty())
+ {
+ // If there is at least one directly downloadable extension then we
+ // open the install dialog.
+ std::vector< UpdateData > dataDownload;
+
+ for (auto const& data : vData)
+ {
+ if ( data.sWebsiteURL.isEmpty() )
+ dataDownload.push_back(data);
+ }
+
+ short nDialogResult = RET_OK;
+ if ( !dataDownload.empty() )
+ {
+ if (m_pDialogHelper)
+ m_pDialogHelper->incBusy();
+ UpdateInstallDialog aDlg(m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, dataDownload, m_xContext);
+ nDialogResult = aDlg.run();
+ if (m_pDialogHelper)
+ m_pDialogHelper->decBusy();
+ aUpdateDialog.notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon
+ }
+ else
+ aUpdateDialog.notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon
+
+ //Now start the webbrowser and navigate to the websites where we get the updates
+ if ( RET_OK == nDialogResult )
+ {
+ for (auto const& data : vData)
+ {
+ if ( m_pDialogHelper && ( !data.sWebsiteURL.isEmpty() ) )
+ m_pDialogHelper->openWebBrowser( data.sWebsiteURL, m_pDialogHelper->getFrameWeld()->get_title() );
+ }
+ }
+ }
+ else
+ aUpdateDialog.notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon
+}
+
+
+void ExtensionCmdQueue::Thread::_enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return;
+
+ uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
+ uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
+ OUString sTitle(
+ m_sEnablingPackages.replaceAll("%EXTENSION_NAME",
+ xPackage->getDisplayName()));
+ rCmdEnv->progressSection( sTitle, xAbortChannel );
+
+ try
+ {
+ xExtMgr->enableExtension( xPackage, xAbortChannel, rCmdEnv );
+ if ( m_pDialogHelper )
+ m_pDialogHelper->updatePackageInfo( xPackage );
+ }
+ catch ( const ::ucb::CommandAbortedException & )
+ {}
+}
+
+
+void ExtensionCmdQueue::Thread::_disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return;
+
+ uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
+ uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
+ OUString sTitle(
+ m_sDisablingPackages.replaceAll("%EXTENSION_NAME",
+ xPackage->getDisplayName()));
+ rCmdEnv->progressSection( sTitle, xAbortChannel );
+
+ try
+ {
+ xExtMgr->disableExtension( xPackage, xAbortChannel, rCmdEnv );
+ if ( m_pDialogHelper )
+ m_pDialogHelper->updatePackageInfo( xPackage );
+ }
+ catch ( const ::ucb::CommandAbortedException & )
+ {}
+}
+
+
+void ExtensionCmdQueue::Thread::_acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
+ const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( !xPackage.is() )
+ return;
+
+ uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
+ uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
+ OUString sTitle(
+ m_sAcceptLicense.replaceAll("%EXTENSION_NAME",
+ xPackage->getDisplayName()));
+ rCmdEnv->progressSection( sTitle, xAbortChannel );
+
+ try
+ {
+ xExtMgr->checkPrerequisitesAndEnable( xPackage, xAbortChannel, rCmdEnv );
+ if ( m_pDialogHelper )
+ m_pDialogHelper->updatePackageInfo( xPackage );
+ }
+ catch ( const ::ucb::CommandAbortedException & )
+ {}
+}
+
+void ExtensionCmdQueue::Thread::_insert(const TExtensionCmd& rExtCmd)
+{
+ std::scoped_lock aGuard( m_mutex );
+
+ // If someone called stop then we do not process the command -> game over!
+ if ( m_bStopped )
+ return;
+
+ m_queue.push( rExtCmd );
+ m_eInput = START;
+ m_wakeup.notify_all();
+}
+
+
+ExtensionCmdQueue::ExtensionCmdQueue( DialogHelper * pDialogHelper,
+ TheExtensionManager *pManager,
+ const uno::Reference< uno::XComponentContext > &rContext )
+ : m_thread( new Thread( pDialogHelper, pManager, rContext ) )
+{
+ m_thread->launch();
+}
+
+ExtensionCmdQueue::~ExtensionCmdQueue() {
+ m_thread->stop();
+ m_thread->join();
+}
+
+void ExtensionCmdQueue::addExtension( const OUString & extensionURL,
+ const OUString & repository,
+ const bool bWarnUser )
+{
+ m_thread->addExtension( extensionURL, repository, bWarnUser );
+}
+
+void ExtensionCmdQueue::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
+{
+ m_thread->removeExtension( rPackage );
+}
+
+void ExtensionCmdQueue::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
+ const bool bEnable )
+{
+ m_thread->enableExtension( rPackage, bEnable );
+}
+
+void ExtensionCmdQueue::checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList )
+{
+ m_thread->checkForUpdates( std::move(vExtensionList) );
+}
+
+void ExtensionCmdQueue::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
+{
+ m_thread->acceptLicense( rPackage );
+}
+
+void ExtensionCmdQueue::syncRepositories( const uno::Reference< uno::XComponentContext > &xContext )
+{
+ dp_misc::syncRepositories( false, new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) );
+}
+
+bool ExtensionCmdQueue::isBusy()
+{
+ return m_thread->isBusy();
+}
+
+void handleInteractionRequest( const uno::Reference< uno::XComponentContext > & xContext,
+ const uno::Reference< task::XInteractionRequest > & xRequest )
+{
+ ::rtl::Reference< ProgressCmdEnv > xCmdEnv( new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) );
+ xCmdEnv->handle( xRequest );
+}
+
+} //namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx
new file mode 100644
index 0000000000..3703d1e8c5
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ref.hxx>
+
+#include <vector>
+
+#include "dp_gui_updatedata.hxx"
+
+/// @HTML
+
+namespace com::sun::star {
+ namespace task { class XInteractionRequest; }
+ namespace uno { class XComponentContext; }
+}
+
+namespace dp_gui {
+
+class DialogHelper;
+class TheExtensionManager;
+
+/**
+ Manages installing of extensions in the GUI mode. Requests for installing
+ Extensions can be asynchronous. For example, the Extension Manager is running
+ in an office process and someone uses the system integration to install an Extension.
+ That is, the user double clicks an extension symbol in a file browser, which then
+ causes an invocation of "unopkg gui ext". When at that time the Extension Manager
+ already performs a task, triggered by the user (for example, add, update, disable,
+ enable) then adding of the extension will be postponed until the user has finished
+ the task.
+
+ This class also ensures that the extensions are not installed in the main thread.
+ Doing so would cause a deadlock because of the progress bar which needs to be constantly
+ updated.
+*/
+class ExtensionCmdQueue {
+
+public:
+ /**
+ Create an instance.
+ */
+ ExtensionCmdQueue( DialogHelper * pDialogHelper,
+ TheExtensionManager *pManager,
+ const css::uno::Reference< css::uno::XComponentContext > & rContext);
+
+ ~ExtensionCmdQueue();
+
+ void addExtension( const OUString &rExtensionURL,
+ const OUString &rRepository,
+ const bool bWarnUser );
+ void removeExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage );
+ void enableExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage,
+ const bool bEnable );
+ void checkForUpdates( std::vector< css::uno::Reference< css::deployment::XPackage > > && vList );
+ void acceptLicense( const css::uno::Reference< css::deployment::XPackage > &rPackage );
+ static void syncRepositories( const css::uno::Reference< css::uno::XComponentContext > & xContext );
+
+ bool isBusy();
+private:
+ ExtensionCmdQueue(ExtensionCmdQueue const &) = delete;
+ ExtensionCmdQueue& operator =(ExtensionCmdQueue const &) = delete;
+
+ class Thread;
+
+ rtl::Reference< Thread > m_thread;
+};
+
+void handleInteractionRequest( const css::uno::Reference< css::uno::XComponentContext > & xContext,
+ const css::uno::Reference< css::task::XInteractionRequest > & xRequest );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.cxx b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx
new file mode 100644
index 0000000000..e7f91f44ab
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx
@@ -0,0 +1,1143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include "dp_gui.h"
+#include "dp_gui_extlistbox.hxx"
+#include "dp_gui_theextmgr.hxx"
+#include <dp_dependencies.hxx>
+#include <bitmaps.hlst>
+
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/i18n/CollatorOptions.hpp>
+#include <com/sun/star/deployment/DependencyException.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <cppuhelper/weakref.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <utility>
+#include <vcl/event.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weldutils.hxx>
+#include <algorithm>
+
+constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user";
+constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared";
+
+using namespace ::com::sun::star;
+
+namespace dp_gui {
+
+namespace {
+
+struct FindWeakRef
+{
+ const uno::Reference<deployment::XPackage> m_extension;
+
+ explicit FindWeakRef( uno::Reference<deployment::XPackage> ext): m_extension(std::move(ext)) {}
+ bool operator () (uno::WeakReference< deployment::XPackage > const & ref);
+};
+
+bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref)
+{
+ const uno::Reference<deployment::XPackage> ext(ref);
+ return ext == m_extension;
+}
+
+} // end namespace
+
+// struct Entry_Impl
+
+Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage,
+ const PackageState eState, const bool bReadOnly ) :
+ m_bActive( false ),
+ m_bLocked( bReadOnly ),
+ m_bHasOptions( false ),
+ m_bUser( false ),
+ m_bShared( false ),
+ m_bNew( false ),
+ m_bChecked( false ),
+ m_bMissingDeps( false ),
+ m_bHasButtons( false ),
+ m_bMissingLic( false ),
+ m_eState( eState ),
+ m_xPackage( xPackage )
+{
+ try
+ {
+ m_sTitle = xPackage->getDisplayName();
+ m_sVersion = xPackage->getVersion();
+ m_sDescription = xPackage->getDescription();
+ m_sLicenseText = xPackage->getLicenseText();
+
+ beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
+ m_sPublisher = aInfo.First;
+ m_sPublisherURL = aInfo.Second;
+
+ // get the icons for the package if there are any
+ uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false );
+ if ( xGraphic.is() )
+ m_aIcon = Image( xGraphic );
+
+ if ( eState == AMBIGUOUS )
+ m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
+ else if ( eState == NOT_REGISTERED )
+ checkDependencies();
+ }
+ catch (const deployment::ExtensionRemovedException &) {}
+ catch (const uno::RuntimeException &) {}
+}
+
+
+Entry_Impl::~Entry_Impl()
+{}
+
+
+sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const
+{
+ sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle );
+ if ( eCompare == 0 )
+ {
+ eCompare = m_sVersion.compareTo( rEntry->m_sVersion );
+ if ( eCompare == 0 )
+ {
+ sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() );
+ if ( nCompare < 0 )
+ eCompare = -1;
+ else if ( nCompare > 0 )
+ eCompare = 1;
+ }
+ }
+ return eCompare;
+}
+
+
+void Entry_Impl::checkDependencies()
+{
+ try {
+ m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException &e )
+ {
+ deployment::DependencyException depExc;
+ if ( e.Cause >>= depExc )
+ {
+ OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
+ for ( const auto& i : std::as_const(depExc.UnsatisfiedDependencies) )
+ {
+ aMissingDep.append("\n"
+ + dp_misc::Dependencies::getErrorText(i));
+ }
+ aMissingDep.append("\n");
+ m_sErrorText = aMissingDep.makeStringAndClear();
+ m_bMissingDeps = true;
+ }
+ }
+}
+
+// ExtensionRemovedListener
+
+void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
+{
+ uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
+
+ if ( xPackage.is() )
+ {
+ m_pParent->removeEntry( xPackage );
+ }
+}
+
+
+ExtensionRemovedListener::~ExtensionRemovedListener()
+{
+}
+
+
+// ExtensionBox_Impl
+ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll)
+ : m_bHasScrollBar( false )
+ , m_bHasActive( false )
+ , m_bNeedsRecalc( true )
+ , m_bInCheckMode( false )
+ , m_bAdjustActive( false )
+ , m_bInDelete( false )
+ , m_nActive( 0 )
+ , m_nTopIndex( 0 )
+ , m_nStdHeight( 0 )
+ , m_nActiveHeight( 0 )
+ , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED)
+ , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED)
+ , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING)
+ , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION)
+ , m_pManager( nullptr )
+ , m_xScrollBar(std::move(xScroll))
+{
+}
+
+void ExtensionBox_Impl::Init()
+{
+ m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) );
+
+ auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
+ auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
+ if ( nIconHeight < nTitleHeight )
+ m_nStdHeight = nTitleHeight;
+ else
+ m_nStdHeight = nIconHeight;
+ m_nStdHeight += GetTextHeight() + TOP_OFFSET;
+
+ nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
+ if ( m_nStdHeight < nIconHeight )
+ m_nStdHeight = nIconHeight;
+
+ m_nActiveHeight = m_nStdHeight;
+
+ m_xRemoveListener = new ExtensionRemovedListener( this );
+
+ m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) );
+ m_oCollator.emplace( ::comphelper::getProcessComponentContext() );
+ m_oCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
+}
+
+ExtensionBox_Impl::~ExtensionBox_Impl()
+{
+ if ( ! m_bInDelete )
+ DeleteRemoved();
+
+ m_bInDelete = true;
+
+ for (auto const& entry : m_vEntries)
+ {
+ entry->m_xPackage->removeEventListener( m_xRemoveListener );
+ }
+
+ m_vEntries.clear();
+
+ m_xRemoveListener.clear();
+
+ m_pLocale.reset();
+ m_oCollator.reset();
+}
+
+sal_Int32 ExtensionBox_Impl::getItemCount() const
+{
+ return static_cast< sal_Int32 >( m_vEntries.size() );
+}
+
+
+sal_Int32 ExtensionBox_Impl::getSelIndex() const
+{
+ if ( m_bHasActive )
+ {
+ OSL_ASSERT( m_nActive >= -1);
+ return static_cast< sal_Int32 >( m_nActive );
+ }
+ else
+ return ENTRY_NOTFOUND;
+}
+
+
+// Title + description
+void ExtensionBox_Impl::CalcActiveHeight( const tools::Long nPos )
+{
+ const ::osl::MutexGuard aGuard( m_entriesMutex );
+
+ // get title height
+ tools::Long aTextHeight;
+ tools::Long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
+ tools::Long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
+ if ( nIconHeight < nTitleHeight )
+ aTextHeight = nTitleHeight;
+ else
+ aTextHeight = nIconHeight;
+
+ // calc description height
+ Size aSize = GetOutputSizePixel();
+
+ aSize.AdjustWidth( -(ICON_OFFSET) );
+ aSize.setHeight( 10000 );
+
+ OUString aText( m_vEntries[ nPos ]->m_sErrorText );
+ if ( !aText.isEmpty() )
+ aText += "\n";
+ aText += m_vEntries[ nPos ]->m_sDescription;
+
+ tools::Rectangle aRect = GetDrawingArea()->get_ref_device().GetTextRect(tools::Rectangle( Point(), aSize ), aText,
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ aTextHeight += aRect.GetHeight();
+
+ if ( aTextHeight < m_nStdHeight )
+ aTextHeight = m_nStdHeight;
+
+ m_nActiveHeight = aTextHeight;
+
+ if ( m_vEntries[ nPos ]->m_bHasButtons )
+ m_nActiveHeight += 2;
+}
+
+tools::Rectangle ExtensionBox_Impl::GetEntryRect( const tools::Long nPos ) const
+{
+ const ::osl::MutexGuard aGuard( m_entriesMutex );
+
+ Size aSize( GetOutputSizePixel() );
+
+ if ( m_vEntries[ nPos ]->m_bActive )
+ aSize.setHeight( m_nActiveHeight );
+ else
+ aSize.setHeight( m_nStdHeight );
+
+ Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
+ if ( m_bHasActive && ( nPos < m_nActive ) )
+ aPos.AdjustY(m_nActiveHeight - m_nStdHeight );
+
+ return tools::Rectangle( aPos, aSize );
+}
+
+
+void ExtensionBox_Impl::DeleteRemoved()
+{
+ const ::osl::MutexGuard aGuard( m_entriesMutex );
+
+ m_bInDelete = true;
+
+ m_vRemovedEntries.clear();
+
+ m_bInDelete = false;
+}
+
+
+//This function may be called with nPos < 0
+void ExtensionBox_Impl::selectEntry( const tools::Long nPos )
+{
+ bool invalidate = false;
+ {
+ //ToDo we should not use the guard at such a big scope here.
+ //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
+ //modified in this function.
+ //It would be probably best to always use a copy of m_vEntries
+ //and some other state variables from ExtensionBox_Impl for
+ //the whole painting operation. See issue i86993
+ ::osl::MutexGuard guard(m_entriesMutex);
+
+ if ( m_bInCheckMode )
+ return;
+
+ if ( m_bHasActive )
+ {
+ if ( nPos == m_nActive )
+ return;
+
+ m_bHasActive = false;
+ m_vEntries[ m_nActive ]->m_bActive = false;
+ }
+
+ if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
+ {
+ m_bHasActive = true;
+ m_nActive = nPos;
+ m_vEntries[ nPos ]->m_bActive = true;
+
+ if ( IsReallyVisible() )
+ {
+ m_bAdjustActive = true;
+ }
+ }
+
+ if ( IsReallyVisible() )
+ {
+ m_bNeedsRecalc = true;
+ invalidate = true;
+ }
+ }
+
+ if (invalidate)
+ {
+ SolarMutexGuard g;
+ Invalidate();
+ }
+}
+
+
+void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (rEntry->m_bActive)
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE))
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+
+ if (rEntry->m_bActive)
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawRect(rRect);
+ }
+ else
+ {
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.Erase(rRect);
+ }
+
+ // Draw extension icon
+ Point aPos( rRect.TopLeft() );
+ aPos += Point(TOP_OFFSET, TOP_OFFSET);
+ Image aImage;
+ if (!rEntry->m_aIcon)
+ aImage = m_aDefaultImage;
+ else
+ aImage = rEntry->m_aIcon;
+ Size aImageSize = aImage.GetSizePixel();
+ if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
+ rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2),
+ aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)),
+ aImage);
+ else
+ rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage);
+
+ // Setup fonts
+ // expand the point size of the desired font to the equivalent pixel size
+ weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font());
+ vcl::Font aStdFont(rRenderContext.GetFont());
+ vcl::Font aBoldFont(aStdFont);
+ aBoldFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aBoldFont);
+ auto aTextHeight = rRenderContext.GetTextHeight();
+
+ // Get max title width
+ auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
+ nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
+ rRenderContext.SetFont(aStdFont);
+ tools::Long nLinkWidth = 0;
+ if (!rEntry->m_sPublisher.isEmpty())
+ {
+ nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
+ nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
+ }
+ tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
+
+ aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
+
+ rRenderContext.SetFont(aBoldFont);
+ tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
+ if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
+ {
+ aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
+ OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
+ rRenderContext.DrawText(aPos, aShortTitle);
+ aTitleWidth += (aTextHeight / 3);
+ }
+ else
+ rRenderContext.DrawText(aPos, rEntry->m_sTitle);
+
+ rRenderContext.SetFont(aStdFont);
+ rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
+
+ tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
+ tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight();
+ if ( nIconHeight < nTitleHeight )
+ aTextHeight = nTitleHeight;
+ else
+ aTextHeight = nIconHeight;
+
+ // draw description
+ OUString sDescription;
+ if (!rEntry->m_sErrorText.isEmpty())
+ {
+ if (rEntry->m_bActive)
+ sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
+ else
+ sDescription = rEntry->m_sErrorText;
+ }
+ else
+ sDescription = rEntry->m_sDescription;
+
+ aPos.AdjustY(aTextHeight );
+ if (rEntry->m_bActive)
+ {
+ tools::Long nExtraHeight = 0;
+
+ if (rEntry->m_bHasButtons)
+ nExtraHeight = 2;
+
+ rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
+ sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+ }
+ else
+ {
+ //replace LF to space, so words do not stick together in one line view
+ sDescription = sDescription.replace(0x000A, ' ');
+ const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription );
+ if (nWidth > rRect.GetWidth() - aPos.X())
+ sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
+ rRenderContext.DrawText(aPos, sDescription);
+ }
+
+ // Draw publisher link
+ if (!rEntry->m_sPublisher.isEmpty())
+ {
+ aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
+
+ rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR);
+ rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
+ vcl::Font aFont = rRenderContext.GetFont();
+ // to underline
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ rRenderContext.SetFont(aFont);
+ rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
+ rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
+ rRenderContext.Pop();
+ }
+
+ // Draw status icons
+ if (!rEntry->m_bUser)
+ {
+ aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
+ if (rEntry->m_bLocked)
+ rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
+ else
+ rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
+ }
+ if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
+ {
+ aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
+ rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
+ }
+
+ rRenderContext.SetLineColor(COL_LIGHTGRAY);
+ rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
+}
+
+
+void ExtensionBox_Impl::RecalcAll()
+{
+ if ( m_bHasActive )
+ CalcActiveHeight( m_nActive );
+
+ SetupScrollBar();
+
+ if ( m_bHasActive )
+ {
+ tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
+
+ if ( m_bAdjustActive )
+ {
+ m_bAdjustActive = false;
+
+ // If the top of the selected entry isn't visible, make it visible
+ if ( aEntryRect.Top() < 0 )
+ {
+ m_nTopIndex += aEntryRect.Top();
+ aEntryRect.Move( 0, -aEntryRect.Top() );
+ }
+
+ // If the bottom of the selected entry isn't visible, make it visible even if now the top
+ // isn't visible any longer ( the buttons are more important )
+ Size aOutputSize = GetOutputSizePixel();
+ if ( aEntryRect.Bottom() > aOutputSize.Height() )
+ {
+ m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
+ aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
+ }
+
+ // If there is unused space below the last entry but all entries don't fit into the box,
+ // move the content down to use the whole space
+ const tools::Long nTotalHeight = GetTotalHeight();
+ if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
+ {
+ tools::Long nOffset = m_nTopIndex;
+ m_nTopIndex = nTotalHeight - aOutputSize.Height();
+ nOffset -= m_nTopIndex;
+ aEntryRect.Move( 0, nOffset );
+ }
+
+ if ( m_bHasScrollBar )
+ m_xScrollBar->vadjustment_set_value( m_nTopIndex );
+ }
+ }
+
+ m_bNeedsRecalc = false;
+}
+
+
+bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
+{
+ if ( m_vEntries.empty() )
+ return true;
+
+ tools::Long nSelect = 0;
+
+ if ( m_bHasActive )
+ {
+ tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
+ if ( nPageSize < 2 )
+ nPageSize = 2;
+
+ if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
+ nSelect = m_nActive + 1;
+ else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
+ nSelect = m_nActive - 1;
+ else if ( nKeyCode == KEY_HOME )
+ nSelect = 0;
+ else if ( nKeyCode == KEY_END )
+ nSelect = m_vEntries.size() - 1;
+ else if ( nKeyCode == KEY_PAGEUP )
+ nSelect = m_nActive - nPageSize + 1;
+ else if ( nKeyCode == KEY_PAGEDOWN )
+ nSelect = m_nActive + nPageSize - 1;
+ }
+ else // when there is no selected entry, we will select the first or the last.
+ {
+ if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
+ nSelect = 0;
+ else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
+ nSelect = m_vEntries.size() - 1;
+ }
+
+ if ( nSelect < 0 )
+ nSelect = 0;
+ if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() )
+ nSelect = m_vEntries.size() - 1;
+
+ selectEntry( nSelect );
+
+ return true;
+}
+
+
+void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
+{
+ if ( !m_bInDelete )
+ DeleteRemoved();
+
+ if ( m_bNeedsRecalc )
+ RecalcAll();
+
+ Point aStart( 0, -m_nTopIndex );
+ Size aSize(GetOutputSizePixel());
+
+ const ::osl::MutexGuard aGuard( m_entriesMutex );
+
+ for (auto const& entry : m_vEntries)
+ {
+ aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight );
+ tools::Rectangle aEntryRect( aStart, aSize );
+ DrawRow(rRenderContext, aEntryRect, entry);
+ aStart.AdjustY(aSize.Height() );
+ }
+}
+
+
+tools::Long ExtensionBox_Impl::GetTotalHeight() const
+{
+ tools::Long nHeight = m_vEntries.size() * m_nStdHeight;
+
+ if ( m_bHasActive )
+ {
+ nHeight += m_nActiveHeight - m_nStdHeight;
+ }
+
+ return nHeight;
+}
+
+
+void ExtensionBox_Impl::SetupScrollBar()
+{
+ const Size aSize = GetOutputSizePixel();
+ const auto nTotalHeight = GetTotalHeight();
+ const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
+
+ if ( bNeedsScrollBar )
+ {
+ if ( m_nTopIndex + aSize.Height() > nTotalHeight )
+ m_nTopIndex = nTotalHeight - aSize.Height();
+
+ m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
+ m_nStdHeight, ( aSize.Height() * 4 ) / 5,
+ aSize.Height());
+
+ if (!m_bHasScrollBar)
+ m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
+ }
+ else if ( m_bHasScrollBar )
+ {
+ m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
+ m_nTopIndex = 0;
+ }
+
+ m_bHasScrollBar = bNeedsScrollBar;
+}
+
+
+void ExtensionBox_Impl::Resize()
+{
+ RecalcAll();
+ Invalidate();
+}
+
+void ExtensionBox_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
+{
+ Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
+ pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
+ CustomWidgetController::SetDrawingArea(pDrawingArea);
+ SetOutputSizePixel(aSize);
+
+ Init();
+}
+
+tools::Long ExtensionBox_Impl::PointToPos( const Point& rPos )
+{
+ tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
+
+ if ( m_bHasActive && ( nPos > m_nActive ) )
+ {
+ if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
+ nPos = m_nActive;
+ else
+ nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
+ }
+
+ return nPos;
+}
+
+bool ExtensionBox_Impl::MouseMove( const MouseEvent& rMEvt )
+{
+ bool bOverHyperlink = false;
+
+ auto nPos = PointToPos( rMEvt.GetPosPixel() );
+ if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
+ {
+ const auto& rEntry = m_vEntries[nPos];
+ bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel());
+ }
+
+ if (bOverHyperlink)
+ SetPointer(PointerStyle::RefHand);
+ else
+ SetPointer(PointerStyle::Arrow);
+
+ return false;
+}
+
+OUString ExtensionBox_Impl::RequestHelp(tools::Rectangle& rRect)
+{
+ auto nPos = PointToPos( rRect.TopLeft() );
+ if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
+ {
+ const auto& rEntry = m_vEntries[nPos];
+ bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect);
+ if (bOverHyperlink)
+ {
+ rRect = rEntry->m_aLinkRect;
+ return rEntry->m_sPublisherURL;
+ }
+ }
+
+ return OUString();
+}
+
+bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() )
+ return false;
+
+ if (rMEvt.IsMod1() && m_bHasActive)
+ selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
+ else
+ {
+ auto nPos = PointToPos( rMEvt.GetPosPixel() );
+
+ if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
+ {
+ const auto& rEntry = m_vEntries[nPos];
+ if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()))
+ {
+ try
+ {
+ css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
+ css::system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
+ //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
+ xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
+ }
+ catch (...)
+ {
+ }
+ return true;
+ }
+ }
+
+ selectEntry( nPos );
+ }
+ return true;
+}
+
+bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt)
+{
+ if ( !m_bInDelete )
+ DeleteRemoved();
+
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ bool bHandled = false;
+ if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
+ bHandled = HandleCursorKey(nKeyCode);
+
+ return bHandled;
+}
+
+bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const tools::Long nStart,
+ const tools::Long nEnd, tools::Long &nPos )
+{
+ nPos = nStart;
+ if ( nStart > nEnd )
+ return false;
+
+ sal_Int32 eCompare;
+
+ if ( nStart == nEnd )
+ {
+ eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nStart ] );
+ if ( eCompare < 0 )
+ return false;
+ else if ( eCompare == 0 )
+ {
+ //Workaround. See i86963.
+ if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
+ return false;
+
+ if ( m_bInCheckMode )
+ m_vEntries[ nStart ]->m_bChecked = true;
+ return true;
+ }
+ else
+ {
+ nPos = nStart + 1;
+ return false;
+ }
+ }
+
+ const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 );
+ eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nMid ] );
+
+ if ( eCompare < 0 )
+ return FindEntryPos( rEntry, nStart, nMid-1, nPos );
+ else if ( eCompare > 0 )
+ return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
+ else
+ {
+ //Workaround.See i86963.
+ if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
+ return false;
+
+ if ( m_bInCheckMode )
+ m_vEntries[ nMid ]->m_bChecked = true;
+ nPos = nMid;
+ return true;
+ }
+}
+
+void ExtensionBox_Impl::cleanVecListenerAdded()
+{
+ std::erase_if(m_vListenerAdded,
+ [](const uno::WeakReference<deployment::XPackage>& rxListener) {
+ const uno::Reference<deployment::XPackage> hardRef(rxListener);
+ return !hardRef.is();
+ });
+}
+
+void ExtensionBox_Impl::addEventListenerOnce(
+ uno::Reference<deployment::XPackage > const & extension)
+{
+ //make sure to only add the listener once
+ cleanVecListenerAdded();
+ if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
+ FindWeakRef(extension)) )
+ {
+ extension->addEventListener( m_xRemoveListener );
+ m_vListenerAdded.emplace_back(extension);
+ }
+}
+
+
+void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
+ bool bLicenseMissing )
+{
+ PackageState eState = TheExtensionManager::getPackageState( xPackage );
+ bool bLocked = m_pManager->isReadOnly( xPackage );
+
+ TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked );
+
+ // Don't add empty entries
+ if ( pEntry->m_sTitle.isEmpty() )
+ return;
+
+ {
+ osl::MutexGuard guard(m_entriesMutex);
+ tools::Long nPos = 0;
+ if (m_vEntries.empty())
+ {
+ addEventListenerOnce(xPackage);
+ m_vEntries.push_back(pEntry);
+ }
+ else
+ {
+ if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
+ {
+ addEventListenerOnce(xPackage);
+ m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
+ }
+ else if (!m_bInCheckMode)
+ {
+ OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
+ }
+ }
+
+ pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage);
+ pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER);
+ pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER);
+ pEntry->m_bNew = m_bInCheckMode;
+ pEntry->m_bMissingLic = bLicenseMissing;
+
+ if (bLicenseMissing)
+ pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
+
+ //access to m_nActive must be guarded
+ if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
+ m_nActive += 1;
+ }
+
+ if ( IsReallyVisible() )
+ Invalidate();
+
+ m_bNeedsRecalc = true;
+}
+
+void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ for (auto const& entry : m_vEntries)
+ {
+ if ( entry->m_xPackage == xPackage )
+ {
+ PackageState eState = TheExtensionManager::getPackageState( xPackage );
+ entry->m_bHasOptions = m_pManager->supportsOptions( xPackage );
+ entry->m_eState = eState;
+ entry->m_sTitle = xPackage->getDisplayName();
+ entry->m_sVersion = xPackage->getVersion();
+ entry->m_sDescription = xPackage->getDescription();
+
+ if ( eState == REGISTERED )
+ entry->m_bMissingLic = false;
+
+ if ( eState == AMBIGUOUS )
+ entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
+ else if ( ! entry->m_bMissingLic )
+ entry->m_sErrorText.clear();
+
+ if ( IsReallyVisible() )
+ Invalidate();
+ break;
+ }
+ }
+}
+
+//This function is also called as a result of removing an extension.
+//see PackageManagerImpl::removePackage
+//The gui is a registered as listener on the package. Removing it will cause the
+//listeners to be notified and then this function is called. At this moment xPackage
+//is in the disposing state and all calls on it may result in a DisposedException.
+void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ if ( m_bInDelete )
+ return;
+
+ bool invalidate = false;
+ {
+ ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
+
+ auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(),
+ [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; });
+ if (iIndex != m_vEntries.end())
+ {
+ tools::Long nPos = iIndex - m_vEntries.begin();
+
+ // Entries mustn't be removed here, because they contain a hyperlink control
+ // which can only be deleted when the thread has the solar mutex. Therefore
+ // the entry will be moved into the m_vRemovedEntries list which will be
+ // cleared on the next paint event
+ m_vRemovedEntries.push_back( *iIndex );
+ (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
+ m_vEntries.erase( iIndex );
+
+ m_bNeedsRecalc = true;
+
+ if ( IsReallyVisible() )
+ invalidate = true;
+
+ if ( m_bHasActive )
+ {
+ if ( nPos < m_nActive )
+ m_nActive -= 1;
+ else if ( ( nPos == m_nActive ) &&
+ ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) )
+ m_nActive -= 1;
+
+ m_bHasActive = false;
+ //clear before calling out of this method
+ aGuard.clear();
+ selectEntry( m_nActive );
+ }
+ }
+ }
+
+ if (invalidate)
+ {
+ SolarMutexGuard g;
+ Invalidate();
+ }
+}
+
+
+void ExtensionBox_Impl::RemoveUnlocked()
+{
+ bool bAllRemoved = false;
+
+ while ( ! bAllRemoved )
+ {
+ bAllRemoved = true;
+
+ ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
+
+ for (auto const& entry : m_vEntries)
+ {
+ if ( !entry->m_bLocked )
+ {
+ bAllRemoved = false;
+ uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
+ aGuard.clear();
+ removeEntry( xPackage );
+ break;
+ }
+ }
+ }
+}
+
+
+void ExtensionBox_Impl::prepareChecking()
+{
+ m_bInCheckMode = true;
+ for (auto const& entry : m_vEntries)
+ {
+ entry->m_bChecked = false;
+ entry->m_bNew = false;
+ }
+}
+
+
+void ExtensionBox_Impl::checkEntries()
+{
+ tools::Long nNewPos = -1;
+ tools::Long nChangedActivePos = -1;
+ tools::Long nPos = 0;
+ bool bNeedsUpdate = false;
+
+ {
+ osl::MutexGuard guard(m_entriesMutex);
+ auto iIndex = m_vEntries.begin();
+ while (iIndex != m_vEntries.end())
+ {
+ if (!(*iIndex)->m_bChecked)
+ {
+ (*iIndex)->m_bChecked = true;
+ bNeedsUpdate = true;
+ nPos = iIndex - m_vEntries.begin();
+ if ((*iIndex)->m_bNew)
+ { // add entry to list and correct active pos
+ if (nNewPos == -1)
+ nNewPos = nPos;
+ if (nPos <= m_nActive)
+ m_nActive += 1;
+ ++iIndex;
+ }
+ else
+ { // remove entry from list
+ if (nPos < nNewPos)
+ {
+ --nNewPos;
+ }
+ if (nPos < nChangedActivePos)
+ {
+ --nChangedActivePos;
+ }
+ if (nPos < m_nActive)
+ m_nActive -= 1;
+ else if (nPos == m_nActive)
+ {
+ nChangedActivePos = nPos;
+ m_nActive = -1;
+ m_bHasActive = false;
+ }
+ m_vRemovedEntries.push_back(*iIndex);
+ (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
+ iIndex = m_vEntries.erase(iIndex);
+ }
+ }
+ else
+ ++iIndex;
+ }
+ }
+
+ m_bInCheckMode = false;
+
+ if ( nNewPos != - 1)
+ selectEntry( nNewPos );
+ else if (nChangedActivePos != -1) {
+ selectEntry(nChangedActivePos);
+ }
+
+ if ( bNeedsUpdate )
+ {
+ m_bNeedsRecalc = true;
+ if ( IsReallyVisible() )
+ Invalidate();
+ }
+}
+
+IMPL_LINK(ExtensionBox_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void)
+{
+ m_nTopIndex = rScrBar.vadjustment_get_value();
+ Invalidate();
+}
+
+} //namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.hxx b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx
new file mode 100644
index 0000000000..cfc04f115d
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <vcl/customweld.hxx>
+#include <vcl/image.hxx>
+#include <vcl/weld.hxx>
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+
+#include <memory>
+#include <optional>
+
+#include "dp_gui.h"
+
+namespace dp_gui {
+
+#define SMALL_ICON_SIZE 16
+#define TOP_OFFSET 5
+#define ICON_HEIGHT 42
+#define ICON_WIDTH 47
+#define ICON_OFFSET 72
+#define RIGHT_ICON_OFFSET 5
+#define SPACE_BETWEEN 3
+
+class TheExtensionManager;
+
+
+struct Entry_Impl;
+
+typedef std::shared_ptr< Entry_Impl > TEntry_Impl;
+
+struct Entry_Impl
+{
+ bool m_bActive :1;
+ bool m_bLocked :1;
+ bool m_bHasOptions :1;
+ bool m_bUser :1;
+ bool m_bShared :1;
+ bool m_bNew :1;
+ bool m_bChecked :1;
+ bool m_bMissingDeps :1;
+ bool m_bHasButtons :1;
+ bool m_bMissingLic :1;
+ PackageState m_eState;
+ OUString m_sTitle;
+ OUString m_sVersion;
+ OUString m_sDescription;
+ OUString m_sPublisher;
+ OUString m_sPublisherURL;
+ OUString m_sErrorText;
+ OUString m_sLicenseText;
+ Image m_aIcon;
+ tools::Rectangle m_aLinkRect;
+
+ css::uno::Reference<css::deployment::XPackage> m_xPackage;
+
+ Entry_Impl(const css::uno::Reference<css::deployment::XPackage> &xPackage,
+ const PackageState eState, const bool bReadOnly);
+ ~Entry_Impl();
+
+ sal_Int32 CompareTo(const CollatorWrapper *pCollator, const TEntry_Impl& rEntry) const;
+ void checkDependencies();
+};
+
+class ExtensionBox_Impl;
+
+
+class ExtensionRemovedListener : public ::cppu::WeakImplHelper<css::lang::XEventListener>
+{
+ ExtensionBox_Impl* m_pParent;
+
+public:
+
+ explicit ExtensionRemovedListener( ExtensionBox_Impl *pParent ) { m_pParent = pParent; }
+ virtual ~ExtensionRemovedListener() override;
+
+
+ // XEventListener
+ virtual void SAL_CALL disposing(css::lang::EventObject const& evt) override;
+};
+
+class ExtensionBox_Impl : public weld::CustomWidgetController
+{
+ bool m_bHasScrollBar : 1;
+ bool m_bHasActive : 1;
+ bool m_bNeedsRecalc : 1;
+ bool m_bInCheckMode : 1;
+ bool m_bAdjustActive : 1;
+ bool m_bInDelete : 1;
+ //Must be guarded together with m_vEntries to ensure a valid index at all times.
+ //Use m_entriesMutex as guard.
+ tools::Long m_nActive;
+ tools::Long m_nTopIndex;
+ tools::Long m_nStdHeight;
+ tools::Long m_nActiveHeight;
+ Image m_aSharedImage;
+ Image m_aLockedImage;
+ Image m_aWarningImage;
+ Image m_aDefaultImage;
+
+ rtl::Reference<ExtensionRemovedListener> m_xRemoveListener;
+
+ TheExtensionManager *m_pManager;
+ //This mutex is used for synchronizing access to m_vEntries.
+ //Currently it is used to synchronize adding, removing entries and
+ //functions like getItemName, getItemDescription, etc. to prevent
+ //that m_vEntries is accessed at an invalid index.
+ //ToDo: There are many more places where m_vEntries is read and which may
+ //fail. For example the Paint method is probable called from the main thread
+ //while new entries are added / removed in a separate thread.
+ mutable ::osl::Mutex m_entriesMutex;
+ std::vector< TEntry_Impl > m_vEntries;
+ std::vector< TEntry_Impl > m_vRemovedEntries;
+
+ std::unique_ptr<css::lang::Locale> m_pLocale;
+ std::optional<CollatorWrapper> m_oCollator;
+
+ //Holds weak references to extensions to which is we have added an XEventListener
+ std::vector< css::uno::WeakReference<
+ css::deployment::XPackage> > m_vListenerAdded;
+
+ std::unique_ptr<weld::ScrolledWindow> m_xScrollBar;
+
+ //Removes the dead weak references from m_vListenerAdded
+ void cleanVecListenerAdded();
+ void addEventListenerOnce(css::uno::Reference<css::deployment::XPackage> const & extension);
+
+ void CalcActiveHeight( const tools::Long nPos );
+ tools::Long GetTotalHeight() const;
+ void SetupScrollBar();
+ void DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry);
+ bool HandleCursorKey( sal_uInt16 nKeyCode );
+ bool FindEntryPos( const TEntry_Impl& rEntry, tools::Long nStart, tools::Long nEnd, tools::Long &nFound );
+ void DeleteRemoved();
+
+ DECL_LINK( ScrollHdl, weld::ScrolledWindow&, void );
+
+ void Init();
+public:
+ explicit ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll);
+ virtual ~ExtensionBox_Impl() override;
+
+ virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual bool MouseMove( const MouseEvent& rMEvt ) override;
+ virtual bool KeyInput(const KeyEvent& rKEvt) override;
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect ) override;
+ virtual void Resize() override;
+ virtual OUString RequestHelp(tools::Rectangle& rRect) override;
+
+ virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override;
+
+ TEntry_Impl const & GetEntryData( tools::Long nPos ) { return m_vEntries[ nPos ]; }
+ tools::Long GetEntryCount() const { return static_cast<tools::Long>(m_vEntries.size()); }
+ tools::Rectangle GetEntryRect( const tools::Long nPos ) const;
+ bool HasActive() const { return m_bHasActive; }
+ tools::Long PointToPos( const Point& rPos );
+ virtual void RecalcAll();
+ void RemoveUnlocked();
+
+
+ virtual void selectEntry( const tools::Long nPos );
+ void addEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage,
+ bool bLicenseMissing = false );
+ void updateEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage );
+ void removeEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage );
+
+ void prepareChecking();
+ void checkEntries();
+
+ void setExtensionManager(TheExtensionManager* pManager) { m_pManager = pManager; }
+
+ //These functions are used for automatic testing
+public:
+ enum { ENTRY_NOTFOUND = -1 };
+
+ /** @return The count of the entries in the list box. */
+ sal_Int32 getItemCount() const;
+
+ /** @return The index of the first selected entry in the list box.
+ When nothing is selected, which is the case when getItemCount returns '0',
+ then this function returns ENTRY_NOTFOUND */
+ /** @return The index of the first selected entry in the list box.
+ When nothing is selected, which is the case when getItemCount returns '0',
+ then this function returns ENTRY_NOTFOUND */
+ sal_Int32 getSelIndex() const;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_service.cxx b/desktop/source/deployment/gui/dp_gui_service.cxx
new file mode 100644
index 0000000000..c359cb7501
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_service.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <memory>
+#include "dp_gui_theextmgr.hxx"
+#include <osl/diagnose.h>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <unotools/configmgr.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/unwrapargs.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+#include <vcl/svapp.hxx>
+#include <com/sun/star/task/XJobExecutor.hpp>
+#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp>
+
+#include <optional>
+#include "license_dialog.hxx"
+#include "dp_gui_dialog2.hxx"
+#include "dp_gui_extensioncmdqueue.hxx"
+#include <dp_misc.h>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_gui {
+
+namespace {
+
+class MyApp : public Application
+{
+public:
+ MyApp();
+
+ MyApp(const MyApp&) = delete;
+ const MyApp& operator=(const MyApp&) = delete;
+
+ // Application
+ virtual int Main() override;
+ virtual void DeInit() override;
+};
+
+}
+
+MyApp::MyApp()
+{
+}
+
+
+int MyApp::Main()
+{
+ return EXIT_SUCCESS;
+}
+
+void MyApp::DeInit()
+{
+ css::uno::Reference< css::uno::XComponentContext > context(
+ comphelper::getProcessComponentContext());
+ dp_misc::disposeBridges(context);
+ css::uno::Reference< css::lang::XComponent >(
+ context, css::uno::UNO_QUERY_THROW)->dispose();
+ comphelper::setProcessServiceFactory(nullptr);
+}
+
+static OUString ReplaceProductNameHookProc( const OUString& rStr )
+{
+ if (rStr.indexOf( "%PRODUCT" ) == -1)
+ return rStr;
+
+ static const OUString sProductName = utl::ConfigManager::getProductName();
+ static const OUString sVersion = utl::ConfigManager::getProductVersion();
+ static const OUString sAboutBoxVersion = utl::ConfigManager::getAboutBoxProductVersion();
+ static const OUString sAboutBoxVersionSuffix = utl::ConfigManager::getAboutBoxProductVersionSuffix();
+ static const OUString sExtension = utl::ConfigManager::getProductExtension();
+ static const OUString sOOOVendor = utl::ConfigManager::getVendor();
+
+ OUString sRet = rStr.replaceAll( "%PRODUCTNAME", sProductName );
+ sRet = sRet.replaceAll( "%PRODUCTVERSION", sVersion );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSIONSUFFIX", sAboutBoxVersionSuffix );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSION", sAboutBoxVersion );
+ sRet = sRet.replaceAll( "%OOOVENDOR", sOOOVendor );
+ sRet = sRet.replaceAll( "%PRODUCTEXTENSION", sExtension );
+ return sRet;
+}
+
+namespace {
+
+class ServiceImpl
+ : public ::cppu::WeakImplHelper<ui::dialogs::XAsynchronousExecutableDialog,
+ task::XJobExecutor, css::lang::XServiceInfo>
+{
+ Reference<XComponentContext> const m_xComponentContext;
+ std::optional< Reference<awt::XWindow> > /* const */ m_parent;
+ std::optional<OUString> m_extensionURL;
+ OUString m_initialTitle;
+ bool m_bShowUpdateOnly;
+
+public:
+ ServiceImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XAsynchronousExecutableDialog
+ virtual void SAL_CALL setDialogTitle( OUString const & aTitle ) override;
+ virtual void SAL_CALL startExecuteModal(
+ Reference< ui::dialogs::XDialogClosedListener > const & xListener ) override;
+
+ // XJobExecutor
+ virtual void SAL_CALL trigger( OUString const & event ) override;
+};
+
+}
+
+ServiceImpl::ServiceImpl( Sequence<Any> const& args,
+ Reference<XComponentContext> const& xComponentContext)
+ : m_xComponentContext(xComponentContext),
+ m_bShowUpdateOnly( false )
+{
+ /* if true then this service is running in a unopkg process and not in an office process */
+ std::optional<OUString> view;
+ try {
+ std::optional<sal_Bool> unopkg;
+ comphelper::unwrapArgs( args, m_parent, view, unopkg );
+ return;
+ } catch ( const css::lang::IllegalArgumentException & ) {
+ }
+ try {
+ comphelper::unwrapArgs( args, m_extensionURL);
+ } catch ( const css::lang::IllegalArgumentException & ) {
+ }
+
+ ResHookProc pProc = Translate::GetReadStringHook();
+ if ( !pProc )
+ Translate::SetReadStringHook(ReplaceProductNameHookProc);
+}
+
+// XServiceInfo
+OUString ServiceImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.ui.PackageManagerDialog";
+}
+
+sal_Bool ServiceImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > ServiceImpl::getSupportedServiceNames()
+{
+ return { "com.sun.star.deployment.ui.PackageManagerDialog" };
+}
+
+// XAsynchronousExecutableDialog
+
+void ServiceImpl::setDialogTitle( OUString const & title )
+{
+ if ( dp_gui::TheExtensionManager::s_ExtMgr.is() )
+ {
+ const SolarMutexGuard guard;
+ ::rtl::Reference< ::dp_gui::TheExtensionManager > dialog(
+ ::dp_gui::TheExtensionManager::get( m_xComponentContext,
+ m_parent ? *m_parent : Reference<awt::XWindow>(),
+ m_extensionURL ? *m_extensionURL : OUString() ) );
+ dialog->SetText( title );
+ }
+ else
+ m_initialTitle = title;
+}
+
+
+void ServiceImpl::startExecuteModal(
+ Reference< ui::dialogs::XDialogClosedListener > const & xListener )
+{
+ bool bCloseDialog = true; // only used if m_bShowUpdateOnly is true
+ std::unique_ptr<Application> app;
+ //ToDo: synchronize access to s_dialog !!!
+ if (! dp_gui::TheExtensionManager::s_ExtMgr.is())
+ {
+ const bool bAppUp = (GetpApp() != nullptr);
+ bool bOfficePipePresent;
+ try {
+ bOfficePipePresent = dp_misc::office_is_running();
+ }
+ catch (const Exception & exc) {
+ if (bAppUp) {
+ const SolarMutexGuard guard;
+ vcl::Window* pWin = Application::GetActiveTopWindow();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok, exc.Message));
+ xBox->run();
+ }
+ throw;
+ }
+
+ if (! bOfficePipePresent) {
+ OSL_ASSERT( ! bAppUp );
+ app.reset( new MyApp );
+ if (! InitVCL() )
+ throw RuntimeException( "Cannot initialize VCL!",
+ static_cast<OWeakObject *>(this) );
+ Application::SetDisplayName(
+ utl::ConfigManager::getProductName() +
+ " " +
+ utl::ConfigManager::getProductVersion());
+ ExtensionCmdQueue::syncRepositories( m_xComponentContext );
+ }
+ }
+ else
+ {
+ // When m_bShowUpdateOnly is set, we are inside the office and the user clicked
+ // the update notification icon in the menu bar. We must not close the extensions
+ // dialog after displaying the update dialog when it has been visible before
+ if ( m_bShowUpdateOnly )
+ bCloseDialog = ! dp_gui::TheExtensionManager::s_ExtMgr->isVisible();
+ }
+
+ {
+ const SolarMutexGuard guard;
+ ::rtl::Reference< ::dp_gui::TheExtensionManager > myExtMgr(
+ ::dp_gui::TheExtensionManager::get(
+ m_xComponentContext,
+ m_parent ? *m_parent : Reference<awt::XWindow>(),
+ m_extensionURL ? *m_extensionURL : OUString() ) );
+ myExtMgr->createDialog( false );
+ if (!m_initialTitle.isEmpty()) {
+ myExtMgr->SetText( m_initialTitle );
+ m_initialTitle.clear();
+ }
+ if ( m_bShowUpdateOnly )
+ {
+ myExtMgr->checkUpdates();
+ if ( bCloseDialog )
+ myExtMgr->Close();
+ else
+ myExtMgr->ToTop();
+ }
+ else
+ {
+ myExtMgr->Show();
+ myExtMgr->ToTop();
+ }
+ }
+
+ if (app != nullptr)
+ {
+ Application::Execute();
+ DeInitVCL();
+ }
+
+ if (xListener.is())
+ xListener->dialogClosed(
+ ui::dialogs::DialogClosedEvent(
+ static_cast< ::cppu::OWeakObject * >(this),
+ sal_Int16(0)) );
+}
+
+// XJobExecutor
+
+void ServiceImpl::trigger( OUString const &rEvent )
+{
+ if ( rEvent == "SHOW_UPDATE_DIALOG" )
+ m_bShowUpdateOnly = true;
+ else
+ m_bShowUpdateOnly = false;
+
+ startExecuteModal( Reference< ui::dialogs::XDialogClosedListener >() );
+}
+
+} // namespace dp_gui
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_LicenseDialog_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_gui::LicenseDialog(args, context));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_ServiceImpl_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_gui::ServiceImpl(args, context));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_UpdateRequiredDialogService_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_gui::UpdateRequiredDialogService(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.cxx b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx
new file mode 100644
index 0000000000..48ed3b329b
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx
@@ -0,0 +1,539 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <comphelper/propertysequence.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/diagnose_ex.hxx>
+
+#include "dp_gui_dialog2.hxx"
+#include "dp_gui_extensioncmdqueue.hxx"
+#include "dp_gui_theextmgr.hxx"
+#include <dp_misc.h>
+#include <dp_update.hxx>
+
+constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user";
+constexpr OUString SHARED_PACKAGE_MANAGER = u"shared"_ustr;
+
+using namespace ::com::sun::star;
+
+namespace dp_gui {
+
+
+::rtl::Reference< TheExtensionManager > TheExtensionManager::s_ExtMgr;
+
+
+// TheExtensionManager
+
+
+TheExtensionManager::TheExtensionManager( uno::Reference< awt::XWindow > xParent,
+ const uno::Reference< uno::XComponentContext > &xContext ) :
+ m_xContext( xContext ),
+ m_xParent(std::move( xParent )),
+ m_bModified(false),
+ m_bExtMgrDialogExecuting(false)
+{
+ m_xExtensionManager = deployment::ExtensionManager::get( xContext );
+ m_xExtensionManager->addModifyListener( this );
+
+ uno::Reference< lang::XMultiServiceFactory > xConfig(
+ configuration::theDefaultProvider::get(xContext));
+ uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence(
+ {
+ {"nodepath", uno::Any(OUString("/org.openoffice.Office.OptionsDialog/Nodes"))}
+ }));
+ m_xNameAccessNodes.set(
+ xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args),
+ uno::UNO_QUERY_THROW);
+
+ // get the 'get more extensions here' url
+ uno::Sequence<uno::Any> args2(comphelper::InitAnyPropertySequence(
+ {
+ {"nodepath", uno::Any(OUString("/org.openoffice.Office.ExtensionManager/ExtensionRepositories"))}
+ }));
+ uno::Reference< container::XNameAccess > xNameAccessRepositories;
+ xNameAccessRepositories.set(
+ xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args2),
+ uno::UNO_QUERY_THROW);
+ try
+ { //throws css::container::NoSuchElementException, css::lang::WrappedTargetException
+ uno::Any value = xNameAccessRepositories->getByName("WebsiteLink");
+ m_sGetExtensionsURL = value.get< OUString > ();
+ }
+ catch ( const uno::Exception& )
+ {}
+
+ if ( dp_misc::office_is_running() )
+ {
+ // the registration should be done after the construction has been ended
+ // otherwise an exception prevents object creation, but it is registered as a listener
+ m_xDesktop.set( frame::Desktop::create(xContext), uno::UNO_SET_THROW );
+ m_xDesktop->addTerminateListener( this );
+ }
+}
+
+TheExtensionManager::~TheExtensionManager()
+{
+ if (m_xUpdReqDialog)
+ m_xUpdReqDialog->response(RET_CANCEL);
+ assert(!m_xUpdReqDialog);
+ if (m_xExtMgrDialog)
+ {
+ if (m_bExtMgrDialogExecuting)
+ m_xExtMgrDialog->response(RET_CANCEL);
+ else
+ {
+ m_xExtMgrDialog->Close();
+ m_xExtMgrDialog.reset();
+ }
+ }
+ assert(!m_xExtMgrDialog);
+}
+
+void TheExtensionManager::createDialog( const bool bCreateUpdDlg )
+{
+ const SolarMutexGuard guard;
+
+ if ( bCreateUpdDlg )
+ {
+ if ( !m_xUpdReqDialog )
+ {
+ m_xUpdReqDialog.reset(new UpdateRequiredDialog(Application::GetFrameWeld(m_xParent), this));
+ m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xUpdReqDialog.get(), this, m_xContext ) );
+ createPackageList();
+ }
+ }
+ else if ( !m_xExtMgrDialog )
+ {
+ m_xExtMgrDialog = std::make_shared<ExtMgrDialog>(Application::GetFrameWeld(m_xParent), this);
+ m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xExtMgrDialog.get(), this, m_xContext ) );
+ m_xExtMgrDialog->setGetExtensionsURL( m_sGetExtensionsURL );
+ createPackageList();
+ }
+}
+
+void TheExtensionManager::Show()
+{
+ const SolarMutexGuard guard;
+
+ m_bExtMgrDialogExecuting = true;
+
+ weld::DialogController::runAsync(m_xExtMgrDialog, [this](sal_Int32 /*nResult*/) {
+ m_bExtMgrDialogExecuting = false;
+ auto xExtMgrDialog = m_xExtMgrDialog;
+ m_xExtMgrDialog.reset();
+ xExtMgrDialog->Close();
+ });
+}
+
+void TheExtensionManager::SetText( const OUString &rTitle )
+{
+ const SolarMutexGuard guard;
+
+ if (weld::Window* pDialog = getDialog())
+ pDialog->set_title( rTitle );
+}
+
+
+void TheExtensionManager::ToTop()
+{
+ const SolarMutexGuard guard;
+
+ if (weld::Window* pDialog = getDialog())
+ pDialog->present();
+}
+
+void TheExtensionManager::Close()
+{
+ if (m_xExtMgrDialog)
+ {
+ if (m_bExtMgrDialogExecuting)
+ m_xExtMgrDialog->response(RET_CANCEL);
+ else
+ m_xExtMgrDialog->Close();
+ }
+ else if (m_xUpdReqDialog)
+ m_xUpdReqDialog->response(RET_CANCEL);
+}
+
+sal_Int16 TheExtensionManager::execute()
+{
+ sal_Int16 nRet = 0;
+
+ if ( m_xUpdReqDialog )
+ {
+ nRet = m_xUpdReqDialog->run();
+ m_xUpdReqDialog.reset();
+ }
+
+ return nRet;
+}
+
+bool TheExtensionManager::isVisible()
+{
+ weld::Window* pDialog = getDialog();
+ return pDialog && pDialog->get_visible();
+}
+
+void TheExtensionManager::checkUpdates()
+{
+ std::vector< uno::Reference< deployment::XPackage > > vEntries;
+ uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages;
+
+ try {
+ xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() );
+ } catch ( const deployment::DeploymentException & ) {
+ return;
+ } catch ( const ucb::CommandFailedException & ) {
+ return;
+ } catch ( const ucb::CommandAbortedException & ) {
+ return;
+ } catch ( const lang::IllegalArgumentException & e ) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+
+ for ( auto const & i : std::as_const(xAllPackages) )
+ {
+ uno::Reference< deployment::XPackage > xPackage = dp_misc::getExtensionWithHighestVersion(i);
+ OSL_ASSERT(xPackage.is());
+ if ( xPackage.is() )
+ {
+ vEntries.push_back( xPackage );
+ }
+ }
+
+ m_xExecuteCmdQueue->checkForUpdates( std::move(vEntries) );
+}
+
+
+bool TheExtensionManager::installPackage( const OUString &rPackageURL, bool bWarnUser )
+{
+ if ( rPackageURL.isEmpty() )
+ return false;
+
+ createDialog( false );
+
+ bool bInstall = true;
+ bool bInstallForAll = false;
+
+ // DV! missing function is read only repository from extension manager
+ if ( !bWarnUser && ! m_xExtensionManager->isReadOnlyRepository( SHARED_PACKAGE_MANAGER ) )
+ bInstall = getDialogHelper()->installForAllUsers( bInstallForAll );
+
+ if ( !bInstall )
+ return false;
+
+ if ( bInstallForAll )
+ m_xExecuteCmdQueue->addExtension( rPackageURL, SHARED_PACKAGE_MANAGER, false );
+ else
+ m_xExecuteCmdQueue->addExtension( rPackageURL, USER_PACKAGE_MANAGER, bWarnUser );
+
+ return true;
+}
+
+
+void TheExtensionManager::terminateDialog()
+{
+ if ( dp_misc::office_is_running() )
+ return;
+
+ const SolarMutexGuard guard;
+ if (m_xExtMgrDialog)
+ {
+ if (m_bExtMgrDialogExecuting)
+ m_xExtMgrDialog->response(RET_CANCEL);
+ else
+ {
+ m_xExtMgrDialog->Close();
+ m_xExtMgrDialog.reset();
+ }
+ }
+ assert(!m_xExtMgrDialog);
+ if (m_xUpdReqDialog)
+ m_xUpdReqDialog->response(RET_CANCEL);
+ assert(!m_xUpdReqDialog);
+ Application::Quit();
+}
+
+
+void TheExtensionManager::createPackageList()
+{
+ uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages;
+
+ try {
+ xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() );
+ } catch ( const deployment::DeploymentException & ) {
+ return;
+ } catch ( const ucb::CommandFailedException & ) {
+ return;
+ } catch ( const ucb::CommandAbortedException & ) {
+ return;
+ } catch ( const lang::IllegalArgumentException & e ) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+
+ for ( uno::Sequence< uno::Reference< deployment::XPackage > > const & xPackageList : std::as_const(xAllPackages) )
+ {
+ for ( uno::Reference< deployment::XPackage > const & xPackage : xPackageList )
+ {
+ if ( xPackage.is() )
+ {
+ PackageState eState = getPackageState( xPackage );
+ getDialogHelper()->addPackageToList( xPackage );
+ // When the package is enabled, we can stop here, otherwise we have to look for
+ // another version of this package
+ if ( ( eState == REGISTERED ) || ( eState == NOT_AVAILABLE ) )
+ break;
+ }
+ }
+ }
+
+ const uno::Sequence< uno::Reference< deployment::XPackage > > xNoLicPackages = m_xExtensionManager->getExtensionsWithUnacceptedLicenses( SHARED_PACKAGE_MANAGER,
+ uno::Reference< ucb::XCommandEnvironment >() );
+ for ( uno::Reference< deployment::XPackage > const & xPackage : xNoLicPackages )
+ {
+ if ( xPackage.is() )
+ {
+ getDialogHelper()->addPackageToList( xPackage, true );
+ }
+ }
+}
+
+
+PackageState TheExtensionManager::getPackageState( const uno::Reference< deployment::XPackage > &xPackage )
+{
+ try {
+ beans::Optional< beans::Ambiguous< sal_Bool > > option(
+ xPackage->isRegistered( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() ) );
+ if ( option.IsPresent )
+ {
+ ::beans::Ambiguous< sal_Bool > const & reg = option.Value;
+ if ( reg.IsAmbiguous )
+ return AMBIGUOUS;
+ else
+ return reg.Value ? REGISTERED : NOT_REGISTERED;
+ }
+ else
+ return NOT_AVAILABLE;
+ }
+ catch ( const uno::RuntimeException & ) {
+ throw;
+ }
+ catch (const uno::Exception &) {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ return NOT_AVAILABLE;
+ }
+}
+
+
+bool TheExtensionManager::isReadOnly( const uno::Reference< deployment::XPackage > &xPackage ) const
+{
+ if ( m_xExtensionManager.is() && xPackage.is() )
+ {
+ return m_xExtensionManager->isReadOnlyRepository( xPackage->getRepositoryName() );
+ }
+ else
+ return true;
+}
+
+
+// The function investigates if the extension supports options.
+bool TheExtensionManager::supportsOptions( const uno::Reference< deployment::XPackage > &xPackage ) const
+{
+ bool bOptions = false;
+
+ if ( ! xPackage->isBundle() )
+ return false;
+
+ beans::Optional< OUString > aId = xPackage->getIdentifier();
+
+ //a bundle must always have an id
+ OSL_ASSERT( aId.IsPresent );
+
+ //iterate over all available nodes
+ const uno::Sequence< OUString > seqNames = m_xNameAccessNodes->getElementNames();
+
+ for ( OUString const & nodeName : seqNames )
+ {
+ uno::Any anyNode = m_xNameAccessNodes->getByName( nodeName );
+ //If we have a node then it must contain the set of leaves. This is part of OptionsDialog.xcs
+ uno::Reference< XInterface> xIntNode = anyNode.get< uno::Reference< XInterface > >();
+ uno::Reference< container::XNameAccess > xNode( xIntNode, uno::UNO_QUERY_THROW );
+
+ uno::Any anyLeaves = xNode->getByName("Leaves");
+ uno::Reference< XInterface > xIntLeaves = anyLeaves.get< uno::Reference< XInterface > >();
+ uno::Reference< container::XNameAccess > xLeaves( xIntLeaves, uno::UNO_QUERY_THROW );
+
+ //iterate over all available leaves
+ const uno::Sequence< OUString > seqLeafNames = xLeaves->getElementNames();
+ for ( OUString const & leafName : seqLeafNames )
+ {
+ uno::Any anyLeaf = xLeaves->getByName( leafName );
+ uno::Reference< XInterface > xIntLeaf = anyLeaf.get< uno::Reference< XInterface > >();
+ uno::Reference< beans::XPropertySet > xLeaf( xIntLeaf, uno::UNO_QUERY_THROW );
+ //investigate the Id property if it matches the extension identifier which
+ //has been passed in.
+ uno::Any anyValue = xLeaf->getPropertyValue("Id");
+
+ OUString sId = anyValue.get< OUString >();
+ if ( sId == aId.Value )
+ {
+ bOptions = true;
+ break;
+ }
+ }
+ if ( bOptions )
+ break;
+ }
+ return bOptions;
+}
+
+
+// XEventListener
+void TheExtensionManager::disposing( lang::EventObject const & rEvt )
+{
+ bool shutDown = (rEvt.Source == m_xDesktop);
+
+ if ( shutDown && m_xDesktop.is() )
+ {
+ m_xDesktop->removeTerminateListener( this );
+ m_xDesktop.clear();
+ }
+
+ if ( !shutDown )
+ return;
+
+ if ( dp_misc::office_is_running() )
+ {
+ const SolarMutexGuard guard;
+ if (m_xExtMgrDialog)
+ {
+ if (m_bExtMgrDialogExecuting)
+ m_xExtMgrDialog->response(RET_CANCEL);
+ else
+ {
+ m_xExtMgrDialog->Close();
+ m_xExtMgrDialog.reset();
+ }
+ }
+ assert(!m_xExtMgrDialog);
+ if (m_xUpdReqDialog)
+ m_xUpdReqDialog->response(RET_CANCEL);
+ assert(!m_xUpdReqDialog);
+ }
+ s_ExtMgr.clear();
+}
+
+// XTerminateListener
+void TheExtensionManager::queryTermination( ::lang::EventObject const & )
+{
+ DialogHelper *pDialogHelper = getDialogHelper();
+
+ if ( m_xExecuteCmdQueue->isBusy() || ( pDialogHelper && pDialogHelper->isBusy() ) )
+ {
+ ToTop();
+ throw frame::TerminationVetoException(
+ "The office cannot be closed while the Extension Manager is running",
+ static_cast<frame::XTerminateListener*>(this));
+ }
+ else
+ {
+ clearModified();
+ if (m_xExtMgrDialog)
+ {
+ if (m_bExtMgrDialogExecuting)
+ m_xExtMgrDialog->response(RET_CANCEL);
+ else
+ {
+ m_xExtMgrDialog->Close();
+ m_xExtMgrDialog.reset();
+ }
+ }
+ if (m_xUpdReqDialog)
+ m_xUpdReqDialog->response(RET_CANCEL);
+ }
+}
+
+void TheExtensionManager::notifyTermination( ::lang::EventObject const & rEvt )
+{
+ disposing( rEvt );
+}
+
+// XModifyListener
+void TheExtensionManager::modified( ::lang::EventObject const & /*rEvt*/ )
+{
+ m_bModified = true;
+ DialogHelper *pDialogHelper = getDialogHelper();
+ if (!pDialogHelper)
+ return;
+ pDialogHelper->prepareChecking();
+ createPackageList();
+ pDialogHelper->checkEntries();
+}
+
+
+::rtl::Reference< TheExtensionManager > TheExtensionManager::get( const uno::Reference< uno::XComponentContext > &xContext,
+ const uno::Reference< awt::XWindow > &xParent,
+ const OUString & extensionURL )
+{
+ if ( s_ExtMgr.is() )
+ {
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ if ( !extensionURL.isEmpty() )
+ s_ExtMgr->installPackage( extensionURL, true );
+ return s_ExtMgr;
+ }
+
+ ::rtl::Reference<TheExtensionManager> that( new TheExtensionManager( xParent, xContext ) );
+
+ const SolarMutexGuard guard;
+ if ( ! s_ExtMgr.is() )
+ {
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ s_ExtMgr = that;
+ }
+
+ if ( !extensionURL.isEmpty() )
+ s_ExtMgr->installPackage( extensionURL, true );
+
+ return s_ExtMgr;
+}
+
+} //namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.hxx b/desktop/source/deployment/gui/dp_gui_theextmgr.hxx
new file mode 100644
index 0000000000..13c329d6d1
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_theextmgr.hxx
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/deployment/XExtensionManager.hpp>
+#include <com/sun/star/frame/XDesktop2.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/util/XModifyListener.hpp>
+
+#include "dp_gui.h"
+#include "dp_gui_dialog2.hxx"
+
+
+namespace dp_gui {
+
+
+class ExtensionCmdQueue;
+
+
+class TheExtensionManager :
+ public ::cppu::WeakImplHelper< css::frame::XTerminateListener,
+ css::util::XModifyListener >
+{
+private:
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::frame::XDesktop2 > m_xDesktop;
+ css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager;
+ css::uno::Reference< css::container::XNameAccess > m_xNameAccessNodes;
+ css::uno::Reference< css::awt::XWindow > m_xParent;
+ std::shared_ptr<ExtMgrDialog> m_xExtMgrDialog;
+ std::unique_ptr<UpdateRequiredDialog> m_xUpdReqDialog;
+ std::unique_ptr<ExtensionCmdQueue> m_xExecuteCmdQueue;
+
+ OUString m_sGetExtensionsURL;
+ bool m_bModified;
+ bool m_bExtMgrDialogExecuting;
+
+public:
+ static ::rtl::Reference<TheExtensionManager> s_ExtMgr;
+
+ TheExtensionManager( css::uno::Reference< css::awt::XWindow > xParent,
+ const css::uno::Reference< css::uno::XComponentContext > &xContext );
+ virtual ~TheExtensionManager() override;
+
+ void createDialog( const bool bCreateUpdDlg );
+ sal_Int16 execute();
+
+ bool isModified() const { return m_bModified; }
+ void clearModified() { m_bModified = false; }
+
+ weld::Window* getDialog()
+ {
+ if (m_xExtMgrDialog)
+ return m_xExtMgrDialog->getDialog();
+ if (m_xUpdReqDialog)
+ return m_xUpdReqDialog->getDialog();
+ return nullptr;
+ }
+ DialogHelper* getDialogHelper()
+ {
+ if (m_xExtMgrDialog)
+ return m_xExtMgrDialog.get();
+ return m_xUpdReqDialog.get();
+ }
+ ExtensionCmdQueue* getCmdQueue() const { return m_xExecuteCmdQueue.get(); }
+
+ void SetText( const OUString &rTitle );
+ void Show();
+ void ToTop();
+ void Close();
+ bool isVisible();
+
+
+ void checkUpdates();
+ bool installPackage( const OUString &rPackageURL, bool bWarnUser = false );
+ void createPackageList();
+
+ void terminateDialog();
+
+ // Tools
+ bool supportsOptions( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const;
+ static PackageState getPackageState( const css::uno::Reference< css::deployment::XPackage > &xPackage );
+ const css::uno::Reference< css::uno::XComponentContext >& getContext() const { return m_xContext; }
+ const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const { return m_xExtensionManager; }
+ bool isReadOnly( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const;
+
+
+ static ::rtl::Reference<TheExtensionManager> get(
+ css::uno::Reference< css::uno::XComponentContext> const & xContext,
+ css::uno::Reference< css::awt::XWindow> const & xParent = nullptr,
+ OUString const & view = OUString() );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override;
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( css::lang::EventObject const & evt ) override;
+ virtual void SAL_CALL notifyTermination( css::lang::EventObject const & evt ) override;
+
+ // XModifyListener
+ virtual void SAL_CALL modified( css::lang::EventObject const & evt ) override;
+};
+
+} // namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updatedata.hxx b/desktop/source/deployment/gui/dp_gui_updatedata.hxx
new file mode 100644
index 0000000000..efac4c587b
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_updatedata.hxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <sal/config.h>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <utility>
+
+namespace com::sun::star::deployment {
+ class XPackage;
+}
+namespace com::sun::star::xml::dom {
+ class XNode;
+}
+
+
+namespace dp_gui {
+
+struct UpdateData
+{
+ explicit UpdateData( css::uno::Reference< css::deployment::XPackage > xExt):
+ bIsShared(false), aInstalledPackage(std::move(xExt)) {};
+
+ //When entries added to the listbox then there can be one for the user update and one
+ //for the shared update. However, both list entries will contain the same UpdateData.
+ //isShared is used to indicate which one is used for the shared entry.
+ bool bIsShared;
+
+ //The currently installed extension which is going to be updated. If the extension exist in
+ //multiple repositories then it is the one with the highest version.
+ css::uno::Reference< css::deployment::XPackage > aInstalledPackage;
+
+ //The version of the update
+ OUString updateVersion;
+
+ //For online update
+
+ // The content of the update information.
+ //Only if aUpdateInfo is set then there is an online update available with a better version
+ //than any of the currently installed extensions with the same identifier.
+ css::uno::Reference< css::xml::dom::XNode > aUpdateInfo;
+ //The URL of the locally downloaded extension. It will only be set if there were no errors
+ //during the download
+ OUString sLocalURL;
+ //The URL of the website where the download can be obtained.
+ OUString sWebsiteURL;
+
+ //For local update
+
+ //The locale extension which is used as update for the user or shared repository.
+ //If set then the data for the online update (aUpdateInfo, sLocalURL, sWebsiteURL)
+ //are to be ignored.
+ css::uno::Reference< css::deployment::XPackage > aUpdateSource;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.cxx b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx
new file mode 100644
index 0000000000..e8da99f3c1
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx
@@ -0,0 +1,988 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/config.h>
+
+#include <utility>
+#include <vector>
+
+
+#include <optional>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/deployment/UpdateInformationProvider.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/XUpdateInformationProvider.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/xml/dom/XElement.hpp>
+#include <osl/diagnose.h>
+#include <rtl/ref.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <salhelper/thread.hxx>
+#include <tools/gen.hxx>
+#include <tools/link.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/svapp.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <dp_dependencies.hxx>
+#include <dp_descriptioninfoset.hxx>
+#include <dp_identifier.hxx>
+#include <dp_misc.h>
+#include <dp_update.hxx>
+
+#include <strings.hrc>
+#include "dp_gui_updatedata.hxx"
+#include "dp_gui_updatedialog.hxx"
+#include <dp_shared.hxx>
+
+class KeyEvent;
+class MouseEvent;
+namespace com::sun::star::uno {
+ class XComponentContext;
+}
+
+using namespace ::com::sun::star;
+using dp_gui::UpdateDialog;
+
+namespace {
+
+sal_Unicode const LF = 0x000A;
+sal_Unicode const CR = 0x000D;
+
+constexpr OUStringLiteral IGNORED_UPDATES = u"/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates";
+constexpr OUStringLiteral PROPERTY_VERSION = u"Version";
+
+enum Kind { ENABLED_UPDATE, DISABLED_UPDATE, SPECIFIC_ERROR };
+
+OUString confineToParagraph(OUString const & text) {
+ // Confine arbitrary text to a single paragraph in a VclMultiLineEdit
+ // This assumes that U+000A and U+000D are the only paragraph separators in
+ // a VclMultiLineEdit, and that replacing them with a single space
+ // each is acceptable:
+ return text.replace(LF, ' ').replace(CR, ' ');
+}
+}
+
+struct UpdateDialog::DisabledUpdate {
+ OUString name;
+ uno::Sequence< OUString > unsatisfiedDependencies;
+ // We also want to show release notes and publisher for disabled updates
+ css::uno::Reference< css::xml::dom::XNode > aUpdateInfo;
+};
+
+struct UpdateDialog::SpecificError {
+ OUString name;
+ OUString message;
+};
+
+
+struct UpdateDialog::IgnoredUpdate {
+ OUString sExtensionID;
+ OUString sVersion;
+
+ IgnoredUpdate( OUString aExtensionID, OUString aVersion );
+};
+
+
+UpdateDialog::IgnoredUpdate::IgnoredUpdate( OUString aExtensionID, OUString aVersion ):
+ sExtensionID(std::move( aExtensionID )),
+ sVersion(std::move( aVersion ))
+{}
+
+
+struct UpdateDialog::Index
+{
+ Kind m_eKind;
+ bool m_bIgnored;
+ sal_uInt16 m_nIndex;
+ OUString m_aName;
+
+ Index( Kind theKind, sal_uInt16 nIndex, OUString aName ) :
+ m_eKind( theKind ),
+ m_bIgnored( false ),
+ m_nIndex( nIndex ),
+ m_aName(std::move( aName )) {}
+};
+
+
+class UpdateDialog::Thread: public salhelper::Thread {
+public:
+ Thread(
+ uno::Reference< uno::XComponentContext > const & context,
+ UpdateDialog & dialog,
+ std::vector< uno::Reference< deployment::XPackage > > && vExtensionList);
+
+ void stop();
+
+private:
+ virtual ~Thread() override;
+
+ virtual void execute() override;
+
+ void handleSpecificError(
+ uno::Reference< deployment::XPackage > const & package,
+ uno::Any const & exception) const;
+
+ OUString getUpdateDisplayString(
+ dp_gui::UpdateData const & data, std::u16string_view version = std::u16string_view()) const;
+
+ void prepareUpdateData(
+ css::uno::Reference< css::xml::dom::XNode > const & updateInfo,
+ UpdateDialog::DisabledUpdate & out_du,
+ dp_gui::UpdateData & out_data) const;
+
+ bool update(
+ UpdateDialog::DisabledUpdate const & du,
+ dp_gui::UpdateData const & data) const;
+
+ uno::Reference< uno::XComponentContext > m_context;
+ UpdateDialog & m_dialog;
+ std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList;
+ uno::Reference< deployment::XUpdateInformationProvider > m_updateInformation;
+ uno::Reference< task::XInteractionHandler > m_xInteractionHdl;
+
+ // guarded by Application::GetSolarMutex():
+ bool m_stop;
+};
+
+UpdateDialog::Thread::Thread(
+ uno::Reference< uno::XComponentContext > const & context,
+ UpdateDialog & dialog,
+ std::vector< uno::Reference< deployment::XPackage > >&& vExtensionList):
+ salhelper::Thread("dp_gui_updatedialog"),
+ m_context(context),
+ m_dialog(dialog),
+ m_vExtensionList(std::move(vExtensionList)),
+ m_updateInformation(
+ deployment::UpdateInformationProvider::create(context)),
+ m_stop(false)
+{
+ if( m_context.is() )
+ {
+ m_xInteractionHdl =
+ task::InteractionHandler::createWithParent(m_context, dialog.getDialog()->GetXWindow());
+ m_updateInformation->setInteractionHandler( m_xInteractionHdl );
+ }
+}
+
+void UpdateDialog::Thread::stop() {
+ {
+ SolarMutexGuard g;
+ m_stop = true;
+ }
+ m_updateInformation->cancel();
+}
+
+UpdateDialog::Thread::~Thread()
+{
+ if ( m_xInteractionHdl.is() )
+ m_updateInformation->setInteractionHandler( uno::Reference< task::XInteractionHandler > () );
+}
+
+void UpdateDialog::Thread::execute()
+{
+ {
+ SolarMutexGuard g;
+ if ( m_stop ) {
+ return;
+ }
+ }
+ uno::Reference<deployment::XExtensionManager> extMgr =
+ deployment::ExtensionManager::get(m_context);
+
+ std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors;
+
+ dp_misc::UpdateInfoMap updateInfoMap = dp_misc::getOnlineUpdateInfos(
+ m_context, extMgr, m_updateInformation, &m_vExtensionList, errors);
+
+ for (auto const& elem : errors)
+ handleSpecificError(elem.first, elem.second);
+
+ for (auto const& updateInfo : updateInfoMap)
+ {
+ dp_misc::UpdateInfo const & info = updateInfo.second;
+ UpdateData updateData(info.extension);
+ DisabledUpdate disableUpdate;
+ //determine if online updates meet the requirements
+ prepareUpdateData(info.info, disableUpdate, updateData);
+
+ //determine if the update is installed in the user or shared repository
+ OUString sOnlineVersion;
+ if (info.info.is())
+ sOnlineVersion = info.version;
+ OUString sVersionUser;
+ OUString sVersionShared;
+ OUString sVersionBundled;
+ uno::Sequence< uno::Reference< deployment::XPackage> > extensions;
+ try {
+ extensions = extMgr->getExtensionsWithSameIdentifier(
+ dp_misc::getIdentifier(info.extension), info.extension->getName(),
+ uno::Reference<ucb::XCommandEnvironment>());
+ } catch ( const lang::IllegalArgumentException& ) {
+ OSL_ASSERT(false);
+ continue;
+ } catch ( const css::ucb::CommandFailedException& ) {
+ OSL_ASSERT(false);
+ continue;
+ }
+ OSL_ASSERT(extensions.getLength() == 3);
+ if (extensions[0].is() )
+ sVersionUser = extensions[0]->getVersion();
+ if (extensions[1].is() )
+ sVersionShared = extensions[1]->getVersion();
+ if (extensions[2].is() )
+ sVersionBundled = extensions[2]->getVersion();
+
+ bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared");
+
+ dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension(
+ bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion);
+ dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension(
+ bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion);
+
+ if (sourceUser != dp_misc::UPDATE_SOURCE_NONE)
+ {
+ if (sourceUser == dp_misc::UPDATE_SOURCE_SHARED)
+ {
+ updateData.aUpdateSource = extensions[1];
+ updateData.updateVersion = extensions[1]->getVersion();
+ }
+ else if (sourceUser == dp_misc::UPDATE_SOURCE_BUNDLED)
+ {
+ updateData.aUpdateSource = extensions[2];
+ updateData.updateVersion = extensions[2]->getVersion();
+ }
+ if (!update(disableUpdate, updateData))
+ return;
+ }
+
+ if (sourceShared != dp_misc::UPDATE_SOURCE_NONE)
+ {
+ if (sourceShared == dp_misc::UPDATE_SOURCE_BUNDLED)
+ {
+ updateData.aUpdateSource = extensions[2];
+ updateData.updateVersion = extensions[2]->getVersion();
+ }
+ updateData.bIsShared = true;
+ if (!update(disableUpdate, updateData))
+ return;
+ }
+ }
+
+
+ SolarMutexGuard g;
+ if (!m_stop) {
+ m_dialog.checkingDone();
+ }
+}
+
+//Parameter package can be null
+void UpdateDialog::Thread::handleSpecificError(
+ uno::Reference< deployment::XPackage > const & package,
+ uno::Any const & exception) const
+{
+ UpdateDialog::SpecificError data;
+ if (package.is())
+ data.name = package->getDisplayName();
+ uno::Exception e;
+ if (exception >>= e) {
+ data.message = e.Message;
+ }
+ SolarMutexGuard g;
+ if (!m_stop) {
+ m_dialog.addSpecificError(data);
+ }
+}
+
+OUString UpdateDialog::Thread::getUpdateDisplayString(
+ dp_gui::UpdateData const & data, std::u16string_view version) const
+{
+ OSL_ASSERT(data.aInstalledPackage.is());
+ OUStringBuffer b(data.aInstalledPackage->getDisplayName());
+ b.append(' ');
+ {
+ SolarMutexGuard g;
+ if(!m_stop)
+ b.append(m_dialog.m_version);
+ }
+ b.append(' ');
+ if (!version.empty())
+ b.append(version);
+ else
+ b.append(data.updateVersion);
+
+ if (!data.sWebsiteURL.isEmpty())
+ {
+ b.append(' ');
+ {
+ SolarMutexGuard g;
+ if(!m_stop)
+ b.append(m_dialog.m_browserbased);
+ }
+ }
+ return b.makeStringAndClear();
+}
+
+/** out_data will only be filled if all dependencies are ok.
+ */
+void UpdateDialog::Thread::prepareUpdateData(
+ uno::Reference< xml::dom::XNode > const & updateInfo,
+ UpdateDialog::DisabledUpdate & out_du,
+ dp_gui::UpdateData & out_data) const
+{
+ if (!updateInfo.is())
+ return;
+ dp_misc::DescriptionInfoset infoset(m_context, updateInfo);
+ OSL_ASSERT(!infoset.getVersion().isEmpty());
+ uno::Sequence< uno::Reference< xml::dom::XElement > > ds(
+ dp_misc::Dependencies::check(infoset));
+
+ out_du.aUpdateInfo = updateInfo;
+ out_du.unsatisfiedDependencies.realloc(ds.getLength());
+ auto p_unsatisfiedDependencies = out_du.unsatisfiedDependencies.getArray();
+ for (sal_Int32 i = 0; i < ds.getLength(); ++i) {
+ p_unsatisfiedDependencies[i] = dp_misc::Dependencies::getErrorText(ds[i]);
+ }
+
+ const ::std::optional< OUString> updateWebsiteURL(infoset.getLocalizedUpdateWebsiteURL());
+
+ out_du.name = getUpdateDisplayString(out_data, infoset.getVersion());
+
+ if (!out_du.unsatisfiedDependencies.hasElements())
+ {
+ out_data.aUpdateInfo = updateInfo;
+ out_data.updateVersion = infoset.getVersion();
+ if (updateWebsiteURL)
+ out_data.sWebsiteURL = *updateWebsiteURL;
+ }
+}
+
+bool UpdateDialog::Thread::update(
+ UpdateDialog::DisabledUpdate const & du,
+ dp_gui::UpdateData const & data) const
+{
+ bool ret = false;
+ if (!du.unsatisfiedDependencies.hasElements())
+ {
+ SolarMutexGuard g;
+ if (!m_stop) {
+ m_dialog.addEnabledUpdate(getUpdateDisplayString(data), data);
+ }
+ ret = !m_stop;
+ } else {
+ SolarMutexGuard g;
+ if (!m_stop) {
+ m_dialog.addDisabledUpdate(du);
+ }
+ ret = !m_stop;
+ }
+ return ret;
+}
+
+// UpdateDialog ----------------------------------------------------------
+UpdateDialog::UpdateDialog(
+ uno::Reference< uno::XComponentContext > const & context,
+ weld::Window * parent, std::vector<uno::Reference< deployment::XPackage > > && vExtensionList,
+ std::vector< dp_gui::UpdateData > * updateData)
+ : GenericDialogController(parent, "desktop/ui/updatedialog.ui", "UpdateDialog")
+ , m_context(context)
+ , m_none(DpResId(RID_DLG_UPDATE_NONE))
+ , m_noInstallable(DpResId(RID_DLG_UPDATE_NOINSTALLABLE))
+ , m_failure(DpResId(RID_DLG_UPDATE_FAILURE))
+ , m_unknownError(DpResId(RID_DLG_UPDATE_UNKNOWNERROR))
+ , m_noDescription(DpResId(RID_DLG_UPDATE_NODESCRIPTION))
+ , m_noInstall(DpResId(RID_DLG_UPDATE_NOINSTALL))
+ , m_noDependency(DpResId(RID_DLG_UPDATE_NODEPENDENCY))
+ , m_noDependencyCurVer(DpResId(RID_DLG_UPDATE_NODEPENDENCY_CUR_VER))
+ , m_browserbased(DpResId(RID_DLG_UPDATE_BROWSERBASED))
+ , m_version(DpResId(RID_DLG_UPDATE_VERSION))
+ , m_ignoredUpdate(DpResId(RID_DLG_UPDATE_IGNORED_UPDATE))
+ , m_updateData(*updateData)
+ , m_thread(new UpdateDialog::Thread(context, *this, std::move(vExtensionList)))
+ , m_xChecking(m_xBuilder->weld_label("UPDATE_CHECKING"))
+ , m_xThrobber(m_xBuilder->weld_spinner("THROBBER"))
+ , m_xUpdate(m_xBuilder->weld_label("UPDATE_LABEL"))
+ , m_xUpdates(m_xBuilder->weld_tree_view("checklist"))
+ , m_xAll(m_xBuilder->weld_check_button("UPDATE_ALL"))
+ , m_xDescription(m_xBuilder->weld_label("DESCRIPTION_LABEL"))
+ , m_xPublisherLabel(m_xBuilder->weld_label("PUBLISHER_LABEL"))
+ , m_xPublisherLink(m_xBuilder->weld_link_button("PUBLISHER_LINK"))
+ , m_xReleaseNotesLabel(m_xBuilder->weld_label("RELEASE_NOTES_LABEL"))
+ , m_xReleaseNotesLink(m_xBuilder->weld_link_button("RELEASE_NOTES_LINK"))
+ , m_xDescriptions(m_xBuilder->weld_text_view("DESCRIPTIONS"))
+ , m_xOk(m_xBuilder->weld_button("ok"))
+ , m_xClose(m_xBuilder->weld_button("close"))
+ , m_xHelp(m_xBuilder->weld_button("help"))
+{
+ auto nWidth = m_xDescriptions->get_approximate_digit_width() * 62;
+ auto nHeight = m_xDescriptions->get_height_rows(8);
+ m_xDescriptions->set_size_request(nWidth, nHeight);
+ m_xUpdates->set_size_request(nWidth, nHeight);
+
+ m_xUpdates->enable_toggle_buttons(weld::ColumnToggleType::Check);
+
+ OSL_ASSERT(updateData != nullptr);
+
+ m_xExtensionManager = deployment::ExtensionManager::get( context );
+
+ m_xUpdates->connect_changed(LINK(this, UpdateDialog, selectionHandler));
+ m_xUpdates->connect_toggled(LINK(this, UpdateDialog, entryToggled));
+ m_xAll->connect_toggled(LINK(this, UpdateDialog, allHandler));
+ m_xOk->connect_clicked(LINK(this, UpdateDialog, okHandler));
+ m_xClose->connect_clicked(LINK(this, UpdateDialog, closeHandler));
+ if (!dp_misc::office_is_running())
+ m_xHelp->set_sensitive(false);
+
+ initDescription();
+ getIgnoredUpdates();
+}
+
+UpdateDialog::~UpdateDialog()
+{
+}
+
+short UpdateDialog::run() {
+ m_xThrobber->start();
+ m_thread->launch();
+ short nRet = GenericDialogController::run();
+ m_thread->stop();
+ return nRet;
+}
+
+IMPL_LINK(UpdateDialog, entryToggled, const weld::TreeView::iter_col&, rRowCol, void)
+{
+ // error's can't be enabled
+ const UpdateDialog::Index* p = weld::fromId<UpdateDialog::Index const *>(m_xUpdates->get_id(rRowCol.first));
+ if (p->m_eKind == SPECIFIC_ERROR)
+ m_xUpdates->set_toggle(rRowCol.first, TRISTATE_FALSE);
+
+ enableOk();
+}
+
+void UpdateDialog::insertItem(const UpdateDialog::Index *pEntry, bool bEnabledCheckBox)
+{
+ int nEntry = m_xUpdates->n_children();
+ m_xUpdates->append();
+ m_xUpdates->set_toggle(nEntry, bEnabledCheckBox ? TRISTATE_TRUE : TRISTATE_FALSE);
+ m_xUpdates->set_text(nEntry, pEntry->m_aName, 0);
+ m_xUpdates->set_id(nEntry, weld::toId(pEntry));
+}
+
+void UpdateDialog::addAdditional(const UpdateDialog::Index * index, bool bEnabledCheckBox)
+{
+ m_xAll->set_sensitive(true);
+ if (m_xAll->get_active())
+ {
+ insertItem(index, bEnabledCheckBox);
+ m_xUpdate->set_sensitive(true);
+ m_xUpdates->set_sensitive(true);
+ m_xDescription->set_sensitive(true);
+ m_xDescriptions->set_sensitive(true);
+ }
+}
+
+void UpdateDialog::addEnabledUpdate( OUString const & name,
+ dp_gui::UpdateData const & data )
+{
+ sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_enabledUpdates.size() );
+ UpdateDialog::Index *pEntry = new UpdateDialog::Index( ENABLED_UPDATE, nIndex, name );
+
+ m_enabledUpdates.push_back( data );
+ m_ListboxEntries.emplace_back( pEntry );
+
+ if (!isIgnoredUpdate(pEntry))
+ {
+ insertItem(pEntry, true);
+ }
+ else
+ addAdditional(pEntry, false);
+
+ m_xUpdate->set_sensitive(true);
+ m_xUpdates->set_sensitive(true);
+ m_xDescription->set_sensitive(true);
+ m_xDescriptions->set_sensitive(true);
+}
+
+void UpdateDialog::addDisabledUpdate( UpdateDialog::DisabledUpdate const & data )
+{
+ sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_disabledUpdates.size() );
+ UpdateDialog::Index *pEntry = new UpdateDialog::Index( DISABLED_UPDATE, nIndex, data.name );
+
+ m_disabledUpdates.push_back( data );
+ m_ListboxEntries.emplace_back( pEntry );
+
+ isIgnoredUpdate( pEntry );
+ addAdditional(pEntry, false);
+}
+
+void UpdateDialog::addSpecificError( UpdateDialog::SpecificError const & data )
+{
+ sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_specificErrors.size() );
+ UpdateDialog::Index *pEntry = new UpdateDialog::Index( SPECIFIC_ERROR, nIndex, data.name );
+
+ m_specificErrors.push_back( data );
+ m_ListboxEntries.emplace_back( pEntry );
+
+ addAdditional(pEntry, false);
+}
+
+void UpdateDialog::checkingDone() {
+ m_xChecking->hide();
+ m_xThrobber->stop();
+ m_xThrobber->hide();
+ if (m_xUpdates->n_children() == 0)
+ {
+ clearDescription();
+ m_xDescription->set_sensitive(true);
+ m_xDescriptions->set_sensitive(true);
+
+ if ( m_disabledUpdates.empty() && m_specificErrors.empty() && m_ignoredUpdates.empty() )
+ showDescription( m_none );
+ else
+ showDescription( m_noInstallable );
+ }
+
+ enableOk();
+}
+
+void UpdateDialog::enableOk() {
+ if (!m_xChecking->get_visible()) {
+ int nChecked = 0;
+ for (int i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) {
+ if (m_xUpdates->get_toggle(i) == TRISTATE_TRUE)
+ ++nChecked;
+ }
+ m_xOk->set_sensitive(nChecked != 0);
+ }
+}
+
+// *********************************************************************************
+void UpdateDialog::createNotifyJob( bool bPrepareOnly,
+ uno::Sequence< uno::Sequence< OUString > > const &rItemList )
+{
+ if ( !dp_misc::office_is_running() )
+ return;
+
+ // notify update check job
+ try
+ {
+ uno::Reference< lang::XMultiServiceFactory > xConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()));
+
+ uno::Sequence< uno::Any > aArgumentList{ uno::Any(comphelper::makePropertyValue(
+ "nodepath",
+ OUString("org.openoffice.Office.Addons/AddonUI/OfficeHelp/UpdateCheckJob"))) };
+
+ uno::Reference< container::XNameAccess > xNameAccess(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess", aArgumentList ),
+ uno::UNO_QUERY_THROW );
+
+ util::URL aURL;
+ xNameAccess->getByName("URL") >>= aURL.Complete;
+
+ uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ uno::Reference < util::XURLTransformer > xTransformer = util::URLTransformer::create(xContext);
+
+ xTransformer->parseStrict(aURL);
+
+ uno::Reference < frame::XDesktop2 > xDesktop = frame::Desktop::create( xContext );
+ uno::Reference< frame::XDispatchProvider > xDispatchProvider( xDesktop->getCurrentFrame(),
+ uno::UNO_QUERY_THROW );
+ uno::Reference< frame::XDispatch > xDispatch = xDispatchProvider->queryDispatch(aURL, OUString(), 0);
+
+ if( xDispatch.is() )
+ {
+ uno::Sequence aPropList{ comphelper::makePropertyValue("updateList", rItemList),
+ comphelper::makePropertyValue("prepareOnly", bPrepareOnly) };
+
+ xDispatch->dispatch(aURL, aPropList );
+ }
+ }
+ catch( const uno::Exception& e )
+ {
+ dp_misc::TRACE( "Caught exception: "
+ + e.Message + "\n thread terminated.\n\n");
+ }
+}
+
+// *********************************************************************************
+void UpdateDialog::notifyMenubar( bool bPrepareOnly, bool bRecheckOnly )
+{
+ if ( !dp_misc::office_is_running() )
+ return;
+
+ uno::Sequence< uno::Sequence< OUString > > aItemList;
+
+ if ( ! bRecheckOnly )
+ {
+ sal_Int32 nCount = 0;
+ for (sal_uInt16 i = 0, nItemCount = m_xUpdates->n_children(); i < nItemCount; ++i)
+ {
+
+ UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i));
+
+ if ( p->m_eKind == ENABLED_UPDATE )
+ {
+ dp_gui::UpdateData aUpdData = m_enabledUpdates[ p->m_nIndex ];
+
+ dp_misc::DescriptionInfoset aInfoset( m_context, aUpdData.aUpdateInfo );
+ uno::Sequence< OUString > aItem
+ {
+ dp_misc::getIdentifier( aUpdData.aInstalledPackage ),
+ aInfoset.getVersion()
+ };
+ aItemList.realloc( nCount + 1 );
+ aItemList.getArray()[ nCount ] = aItem;
+ nCount += 1;
+ }
+ else
+ continue;
+ }
+ }
+
+ createNotifyJob( bPrepareOnly, aItemList );
+}
+
+// *********************************************************************************
+
+void UpdateDialog::initDescription()
+{
+ m_xPublisherLabel->hide();
+ m_xPublisherLink->hide();
+ m_xReleaseNotesLabel->hide();
+ m_xReleaseNotesLink->hide();
+}
+
+void UpdateDialog::clearDescription()
+{
+ m_xPublisherLabel->hide();
+ m_xPublisherLink->hide();
+ m_xPublisherLink->set_label("");
+ m_xPublisherLink->set_uri("");
+ m_xReleaseNotesLabel->hide();
+ m_xReleaseNotesLink->hide();
+ m_xReleaseNotesLink->set_uri( "" );
+ m_xDescriptions->set_text("");
+}
+
+bool UpdateDialog::showDescription(uno::Reference< xml::dom::XNode > const & aUpdateInfo)
+{
+ dp_misc::DescriptionInfoset infoset(m_context, aUpdateInfo);
+ return showDescription(infoset.getLocalizedPublisherNameAndURL(),
+ infoset.getLocalizedReleaseNotesURL());
+}
+
+bool UpdateDialog::showDescription(uno::Reference< deployment::XPackage > const & aExtension)
+{
+ OSL_ASSERT(aExtension.is());
+ beans::StringPair pubInfo = aExtension->getPublisherInfo();
+ return showDescription(std::make_pair(pubInfo.First, pubInfo.Second),
+ "");
+}
+
+bool UpdateDialog::showDescription(std::pair< OUString, OUString > const & pairPublisher,
+ OUString const & sReleaseNotes)
+{
+ OUString sPub = pairPublisher.first;
+ OUString sURL = pairPublisher.second;
+
+ if ( sPub.isEmpty() && sURL.isEmpty() && sReleaseNotes.isEmpty() )
+ // nothing to show
+ return false;
+
+ if ( !sPub.isEmpty() )
+ {
+ m_xPublisherLabel->show();
+ m_xPublisherLink->show();
+ m_xPublisherLink->set_label(sPub);
+ m_xPublisherLink->set_uri(sURL);
+ }
+
+ if ( !sReleaseNotes.isEmpty() )
+ {
+ m_xReleaseNotesLabel->show();
+ m_xReleaseNotesLink->show();
+ m_xReleaseNotesLink->set_uri( sReleaseNotes );
+ }
+ return true;
+}
+
+bool UpdateDialog::showDescription( const OUString& rDescription)
+{
+ if ( rDescription.isEmpty() )
+ // nothing to show
+ return false;
+
+ m_xDescriptions->set_text(rDescription);
+ return true;
+}
+
+void UpdateDialog::getIgnoredUpdates()
+{
+ uno::Reference< lang::XMultiServiceFactory > xConfig(
+ configuration::theDefaultProvider::get(m_context));
+ beans::NamedValue aValue( "nodepath", uno::Any( OUString(IGNORED_UPDATES) ) );
+ uno::Sequence< uno::Any > args{ uno::Any(aValue) };
+
+ uno::Reference< container::XNameAccess > xNameAccess( xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args), uno::UNO_QUERY_THROW );
+ const uno::Sequence< OUString > aElementNames = xNameAccess->getElementNames();
+
+ for ( OUString const & aIdentifier : aElementNames )
+ {
+ OUString aVersion;
+
+ uno::Any aPropValue( uno::Reference< beans::XPropertySet >( xNameAccess->getByName( aIdentifier ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) );
+ aPropValue >>= aVersion;
+ IgnoredUpdate *pData = new IgnoredUpdate( aIdentifier, aVersion );
+ m_ignoredUpdates.emplace_back( pData );
+ }
+}
+
+
+bool UpdateDialog::isIgnoredUpdate( UpdateDialog::Index * index )
+{
+ bool bIsIgnored = false;
+
+ if (! m_ignoredUpdates.empty() )
+ {
+ OUString aExtensionID;
+ OUString aVersion;
+
+ if ( index->m_eKind == ENABLED_UPDATE )
+ {
+ dp_gui::UpdateData aUpdData = m_enabledUpdates[ index->m_nIndex ];
+ aExtensionID = dp_misc::getIdentifier( aUpdData.aInstalledPackage );
+ aVersion = aUpdData.updateVersion;
+ }
+ else if ( index->m_eKind == DISABLED_UPDATE )
+ {
+ DisabledUpdate &rData = m_disabledUpdates[ index->m_nIndex ];
+ dp_misc::DescriptionInfoset aInfoset( m_context, rData.aUpdateInfo );
+ ::std::optional< OUString > aID( aInfoset.getIdentifier() );
+ if ( aID )
+ aExtensionID = *aID;
+ aVersion = aInfoset.getVersion();
+ }
+
+ for (auto const& ignoredUpdate : m_ignoredUpdates)
+ {
+ if ( ignoredUpdate->sExtensionID == aExtensionID )
+ {
+ if ( ( !ignoredUpdate->sVersion.isEmpty() ) || ( ignoredUpdate->sVersion == aVersion ) )
+ {
+ bIsIgnored = true;
+ index->m_bIgnored = true;
+ }
+ break;
+ }
+ }
+ }
+
+ return bIsIgnored;
+}
+
+
+IMPL_LINK_NOARG(UpdateDialog, selectionHandler, weld::TreeView&, void)
+{
+ OUStringBuffer b;
+ int nSelectedPos = m_xUpdates->get_selected_index();
+ clearDescription();
+
+ const UpdateDialog::Index* p = nullptr;
+ if (nSelectedPos != -1)
+ p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(nSelectedPos));
+ if (p != nullptr)
+ {
+ sal_uInt16 pos = p->m_nIndex;
+
+ switch (p->m_eKind)
+ {
+ case ENABLED_UPDATE:
+ {
+ if ( m_enabledUpdates[ pos ].aUpdateSource.is() )
+ showDescription( m_enabledUpdates[ pos ].aUpdateSource );
+ else
+ showDescription( m_enabledUpdates[ pos ].aUpdateInfo );
+
+ if ( p->m_bIgnored )
+ b.append( m_ignoredUpdate );
+
+ break;
+ }
+ case DISABLED_UPDATE:
+ {
+ if ( !m_disabledUpdates.empty() )
+ showDescription( m_disabledUpdates[pos].aUpdateInfo );
+
+ if ( p->m_bIgnored )
+ b.append( m_ignoredUpdate );
+
+ if ( m_disabledUpdates.empty() )
+ break;
+
+ UpdateDialog::DisabledUpdate & data = m_disabledUpdates[ pos ];
+ if (data.unsatisfiedDependencies.hasElements())
+ {
+ // create error string for version mismatch
+ OUString sVersion( "%VERSION" );
+ OUString sProductName( "%PRODUCTNAME" );
+ sal_Int32 nPos = m_noDependencyCurVer.indexOf( sVersion );
+ if ( nPos >= 0 )
+ {
+ m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sVersion.getLength(), utl::ConfigManager::getAboutBoxProductVersion() );
+ }
+ nPos = m_noDependencyCurVer.indexOf( sProductName );
+ if ( nPos >= 0 )
+ {
+ m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() );
+ }
+ nPos = m_noDependency.indexOf( sProductName );
+ if ( nPos >= 0 )
+ {
+ m_noDependency = m_noDependency.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() );
+ }
+
+ b.append(m_noInstall + OUStringChar(LF) + m_noDependency);
+ for (sal_Int32 i = 0;
+ i < data.unsatisfiedDependencies.getLength(); ++i)
+ {
+ b.append(OUStringChar(LF) + " ");
+ // U+2003 EM SPACE would be better than two spaces,
+ // but some fonts do not contain it
+ b.append(
+ confineToParagraph(
+ data.unsatisfiedDependencies[i]));
+ }
+ b.append(OUStringChar(LF) + " " + m_noDependencyCurVer);
+ }
+ break;
+ }
+ case SPECIFIC_ERROR:
+ {
+ UpdateDialog::SpecificError & data = m_specificErrors[ pos ];
+ b.append(m_failure + OUStringChar(LF));
+ b.append( data.message.isEmpty() ? m_unknownError : data.message );
+ break;
+ }
+ default:
+ OSL_ASSERT(false);
+ break;
+ }
+ }
+
+ if ( b.isEmpty() )
+ b.append( m_noDescription );
+
+ showDescription( b.makeStringAndClear() );
+}
+
+IMPL_LINK_NOARG(UpdateDialog, allHandler, weld::Toggleable&, void)
+{
+ if (m_xAll->get_active())
+ {
+ m_xUpdate->set_sensitive(true);
+ m_xUpdates->set_sensitive(true);
+ m_xDescription->set_sensitive(true);
+ m_xDescriptions->set_sensitive(true);
+
+ for (auto const& listboxEntry : m_ListboxEntries)
+ {
+ if ( listboxEntry->m_bIgnored || ( listboxEntry->m_eKind != ENABLED_UPDATE ) )
+ insertItem(listboxEntry.get(), false);
+ }
+ }
+ else
+ {
+ for (sal_uInt16 i = m_xUpdates->n_children(); i != 0 ;)
+ {
+ i -= 1;
+ UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i));
+ if ( p->m_bIgnored || ( p->m_eKind != ENABLED_UPDATE ) )
+ {
+ m_xUpdates->remove(i);
+ }
+ }
+
+ if (m_xUpdates->n_children() == 0)
+ {
+ clearDescription();
+ m_xUpdate->set_sensitive(false);
+ m_xUpdates->set_sensitive(false);
+ if (m_xChecking->get_visible())
+ m_xDescription->set_sensitive(false);
+ else
+ showDescription(m_noInstallable);
+ }
+ }
+}
+
+IMPL_LINK_NOARG(UpdateDialog, okHandler, weld::Button&, void)
+{
+ //If users are going to update a shared extension then we need
+ //to warn them
+ for (auto const& enableUpdate : m_enabledUpdates)
+ {
+ OSL_ASSERT(enableUpdate.aInstalledPackage.is());
+ //If the user has no write access to the shared folder then the update
+ //for a shared extension is disable, that is it cannot be in m_enabledUpdates
+ }
+
+
+ for (sal_uInt16 i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i)
+ {
+ UpdateDialog::Index const * p =
+ weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i));
+ if (p->m_eKind == ENABLED_UPDATE && m_xUpdates->get_toggle(i) == TRISTATE_TRUE) {
+ m_updateData.push_back( m_enabledUpdates[ p->m_nIndex ] );
+ }
+ }
+
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK_NOARG(UpdateDialog, closeHandler, weld::Button&, void)
+{
+ m_thread->stop();
+ m_xDialog->response(RET_CANCEL);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.hxx b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx
new file mode 100644
index 0000000000..cbf376955d
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vector>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/link.hxx>
+#include <vcl/weld.hxx>
+
+#include "dp_gui_updatedata.hxx"
+
+/// @HTML
+
+class Image;
+class KeyEvent;
+class MouseEvent;
+class ResId;
+
+namespace com::sun::star {
+ namespace deployment { class XExtensionManager;
+ class XPackage; }
+ namespace uno { class XComponentContext; }
+}
+
+namespace dp_gui {
+/**
+ The modal &ldquo;Check for Updates&rdquo; dialog.
+*/
+class UpdateDialog: public weld::GenericDialogController {
+public:
+ /**
+ Create an instance.
+
+ <p>Exactly one of <code>selectedPackages</code> and
+ <code>packageManagers</code> must be non-null.</p>
+
+ @param context
+ a non-null component context
+
+ @param parent
+ the parent window, may be null
+
+ @param vExtensionList
+ check for updates for the contained extensions. There must only be one extension with
+ a particular identifier. If one extension is installed in several repositories, then the
+ one with the highest version must be used, because it contains the latest known update
+ information.
+ */
+ UpdateDialog(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ weld::Window * parent,
+ std::vector< css::uno::Reference< css::deployment::XPackage > > && vExtensionList,
+ std::vector< dp_gui::UpdateData > * updateData);
+
+ virtual ~UpdateDialog() override;
+
+ virtual short run() override;
+
+ void notifyMenubar( bool bPrepareOnly, bool bRecheckOnly );
+ static void createNotifyJob( bool bPrepareOnly,
+ css::uno::Sequence< css::uno::Sequence< OUString > > const &rItemList );
+
+private:
+ UpdateDialog(UpdateDialog const &) = delete;
+ UpdateDialog& operator =(UpdateDialog const &) = delete;
+
+ struct DisabledUpdate;
+ struct SpecificError;
+ struct IgnoredUpdate;
+ struct Index;
+ friend struct Index;
+ class Thread;
+ friend class Thread;
+
+ friend class CheckListBox;
+
+ void insertItem(const UpdateDialog::Index *pIndex, bool bEnableCheckBox);
+ void addAdditional(const UpdateDialog::Index *pIndex, bool bEnableCheckBox);
+ bool isIgnoredUpdate( UpdateDialog::Index *pIndex );
+
+ void addEnabledUpdate( OUString const & name, dp_gui::UpdateData const & data );
+ void addDisabledUpdate( UpdateDialog::DisabledUpdate const & data );
+ void addSpecificError( UpdateDialog::SpecificError const & data );
+
+ void checkingDone();
+
+ void enableOk();
+
+ void getIgnoredUpdates();
+
+ void initDescription();
+ void clearDescription();
+ bool showDescription(css::uno::Reference<
+ css::deployment::XPackage > const & aExtension);
+ bool showDescription(std::pair< OUString, OUString > const & pairPublisher,
+ OUString const & sReleaseNotes);
+ bool showDescription( css::uno::Reference<
+ css::xml::dom::XNode > const & aUpdateInfo);
+ bool showDescription( const OUString& rDescription);
+
+ DECL_LINK(selectionHandler, weld::TreeView&, void);
+ DECL_LINK(allHandler, weld::Toggleable&, void);
+ DECL_LINK(okHandler, weld::Button&, void);
+ DECL_LINK(closeHandler, weld::Button&, void);
+ DECL_LINK(entryToggled, const weld::TreeView::iter_col&, void);
+
+ css::uno::Reference< css::uno::XComponentContext > m_context;
+ OUString m_none;
+ OUString m_noInstallable;
+ OUString m_failure;
+ OUString m_unknownError;
+ OUString m_noDescription;
+ OUString m_noInstall;
+ OUString m_noDependency;
+ OUString m_noDependencyCurVer;
+ OUString m_browserbased;
+ OUString m_version;
+ OUString m_ignoredUpdate;
+ std::vector< dp_gui::UpdateData > m_enabledUpdates;
+ std::vector< UpdateDialog::DisabledUpdate > m_disabledUpdates;
+ std::vector< UpdateDialog::SpecificError > m_specificErrors;
+ std::vector< std::unique_ptr<UpdateDialog::IgnoredUpdate> > m_ignoredUpdates;
+ std::vector< std::unique_ptr<Index> > m_ListboxEntries;
+ std::vector< dp_gui::UpdateData > & m_updateData;
+ rtl::Reference< UpdateDialog::Thread > m_thread;
+ css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager;
+
+ std::unique_ptr<weld::Label> m_xChecking;
+ std::unique_ptr<weld::Spinner> m_xThrobber;
+ std::unique_ptr<weld::Label> m_xUpdate;
+ std::unique_ptr<weld::TreeView> m_xUpdates;
+ std::unique_ptr<weld::CheckButton> m_xAll;
+ std::unique_ptr<weld::Label> m_xDescription;
+ std::unique_ptr<weld::Label> m_xPublisherLabel;
+ std::unique_ptr<weld::LinkButton> m_xPublisherLink;
+ std::unique_ptr<weld::Label> m_xReleaseNotesLabel;
+ std::unique_ptr<weld::LinkButton> m_xReleaseNotesLink;
+ std::unique_ptr<weld::TextView> m_xDescriptions;
+ std::unique_ptr<weld::Button> m_xOk;
+ std::unique_ptr<weld::Button> m_xClose;
+ std::unique_ptr<weld::Button> m_xHelp;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx
new file mode 100644
index 0000000000..0248a1537f
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx
@@ -0,0 +1,662 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "dp_gui_updatedata.hxx"
+
+#include <sal/config.h>
+#include <osl/file.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/lang/WrappedTargetException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/VersionException.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+
+#include <dp_descriptioninfoset.hxx>
+#include <strings.hrc>
+#include "dp_gui_updateinstalldialog.hxx"
+#include <dp_shared.hxx>
+#include <dp_ucb.h>
+#include <dp_misc.h>
+#include "dp_gui_extensioncmdqueue.hxx"
+#include <ucbhelper/content.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ref.hxx>
+#include <salhelper/thread.hxx>
+#include <com/sun/star/uno/Sequence.h>
+#include <comphelper/anytostring.hxx>
+
+#include <string_view>
+#include <vector>
+
+using dp_misc::StrTitle;
+
+namespace dp_gui {
+
+class UpdateInstallDialog::Thread: public salhelper::Thread {
+ friend class UpdateCommandEnv;
+public:
+ Thread(css::uno::Reference< css::uno::XComponentContext > const & ctx,
+ UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData);
+
+ void stop();
+
+private:
+ virtual ~Thread() override;
+
+ virtual void execute() override;
+ void downloadExtensions();
+ bool download(OUString const & aUrls, UpdateData & aUpdatData);
+ void installExtensions();
+ void removeTempDownloads();
+
+ UpdateInstallDialog & m_dialog;
+
+ // guarded by Application::GetSolarMutex():
+ css::uno::Reference< css::task::XAbortChannel > m_abort;
+ css::uno::Reference< css::uno::XComponentContext > m_xComponentContext;
+ std::vector< dp_gui::UpdateData > & m_aVecUpdateData;
+ ::rtl::Reference<UpdateCommandEnv> m_updateCmdEnv;
+
+ //A folder which is created in the temp directory in which then the updates are downloaded
+ OUString m_sDownloadFolder;
+
+ bool m_stop;
+
+};
+
+class UpdateCommandEnv
+ : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
+ css::task::XInteractionHandler,
+ css::ucb::XProgressHandler >
+{
+ friend class UpdateInstallDialog::Thread;
+
+ ::rtl::Reference<UpdateInstallDialog::Thread> m_installThread;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+public:
+ UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx,
+ ::rtl::Reference<UpdateInstallDialog::Thread> thread);
+
+ // XCommandEnvironment
+ virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual css::uno::Reference<css::ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL update( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+
+UpdateInstallDialog::Thread::Thread(
+ css::uno::Reference< css::uno::XComponentContext> const & xCtx,
+ UpdateInstallDialog & dialog,
+ std::vector< dp_gui::UpdateData > & aVecUpdateData):
+ salhelper::Thread("dp_gui_updateinstalldialog"),
+ m_dialog(dialog),
+ m_xComponentContext(xCtx),
+ m_aVecUpdateData(aVecUpdateData),
+ m_updateCmdEnv(new UpdateCommandEnv(xCtx, this)),
+ m_stop(false)
+{}
+
+void UpdateInstallDialog::Thread::stop() {
+ css::uno::Reference< css::task::XAbortChannel > abort;
+ {
+ SolarMutexGuard g;
+ abort = m_abort;
+ m_stop = true;
+ }
+ if (abort.is()) {
+ abort->sendAbort();
+ }
+}
+
+UpdateInstallDialog::Thread::~Thread() {}
+
+void UpdateInstallDialog::Thread::execute()
+{
+ try {
+ downloadExtensions();
+ installExtensions();
+ }
+ catch (...)
+ {
+ }
+
+ //clean up the temp directories
+ try {
+ removeTempDownloads();
+ } catch( ... ) {
+ }
+
+ {
+ //make sure m_dialog is still alive
+ SolarMutexGuard g;
+ if (! m_stop)
+ m_dialog.updateDone();
+ }
+ //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it.
+ m_updateCmdEnv->m_installThread.clear();
+}
+
+UpdateInstallDialog::UpdateInstallDialog(
+ weld::Window* pParent,
+ std::vector<dp_gui::UpdateData> & aVecUpdateData,
+ css::uno::Reference< css::uno::XComponentContext > const & xCtx)
+ : GenericDialogController(pParent, "desktop/ui/updateinstalldialog.ui",
+ "UpdateInstallDialog")
+ , m_thread(new Thread(xCtx, *this, aVecUpdateData))
+ , m_bError(false)
+ , m_bNoEntry(true)
+ , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING))
+ , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED))
+ , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS))
+ , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD))
+ , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION))
+ , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED))
+ , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL))
+ , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED))
+ , m_xFt_action(m_xBuilder->weld_label("DOWNLOADING"))
+ , m_xStatusbar(m_xBuilder->weld_progress_bar("STATUSBAR"))
+ , m_xFt_extension_name(m_xBuilder->weld_label("EXTENSION_NAME"))
+ , m_xMle_info(m_xBuilder->weld_text_view("INFO"))
+ , m_xHelp(m_xBuilder->weld_button("help"))
+ , m_xOk(m_xBuilder->weld_button("ok"))
+ , m_xCancel(m_xBuilder->weld_button("cancel"))
+{
+ m_xMle_info->set_size_request(m_xMle_info->get_approximate_digit_width() * 52,
+ m_xMle_info->get_height_rows(5));
+
+ m_xExtensionManager = css::deployment::ExtensionManager::get( xCtx );
+
+ m_xCancel->connect_clicked(LINK(this, UpdateInstallDialog, cancelHandler));
+ if ( ! dp_misc::office_is_running())
+ m_xHelp->set_sensitive(false);
+}
+
+UpdateInstallDialog::~UpdateInstallDialog()
+{
+}
+
+short UpdateInstallDialog::run()
+{
+ m_thread->launch();
+ short nRet = GenericDialogController::run();
+ m_thread->stop();
+ return nRet;
+}
+
+// make sure the solar mutex is locked before calling
+void UpdateInstallDialog::updateDone()
+{
+ if (!m_bError)
+ m_xMle_info->set_text(m_xMle_info->get_text() + m_sNoErrors);
+ m_xOk->set_sensitive(true);
+ m_xOk->grab_focus();
+ m_xCancel->set_sensitive(false);
+}
+
+// make sure the solar mutex is locked before calling
+//sets an error message in the text area
+void UpdateInstallDialog::setError(INSTALL_ERROR err, std::u16string_view sExtension,
+ std::u16string_view exceptionMessage)
+{
+ OUString sError;
+ m_bError = true;
+
+ switch (err)
+ {
+ case ERROR_DOWNLOAD:
+ sError = m_sErrorDownload;
+ break;
+ case ERROR_INSTALLATION:
+ sError = m_sErrorInstallation;
+ break;
+ case ERROR_LICENSE_DECLINED:
+ sError = m_sErrorLicenseDeclined;
+ break;
+
+ default:
+ OSL_ASSERT(false);
+ }
+
+ OUString sMsg(m_xMle_info->get_text());
+ sError = sError.replaceFirst("%NAME", sExtension);
+ //We want to have an empty line between the error messages. However,
+ //there shall be no empty line after the last entry.
+ if (m_bNoEntry)
+ m_bNoEntry = false;
+ else
+ sMsg += "\n";
+ sMsg += sError;
+ //Insert more information about the error
+ if (!exceptionMessage.empty())
+ sMsg += m_sThisErrorOccurred + exceptionMessage + "\n";
+
+ sMsg += m_sNoInstall + "\n";
+
+ m_xMle_info->set_text(sMsg);
+}
+
+void UpdateInstallDialog::setError(std::u16string_view exceptionMessage)
+{
+ m_bError = true;
+ m_xMle_info->set_text(m_xMle_info->get_text() + exceptionMessage + "\n");
+}
+
+IMPL_LINK_NOARG(UpdateInstallDialog, cancelHandler, weld::Button&, void)
+{
+ m_xDialog->response(RET_CANCEL);
+}
+
+void UpdateInstallDialog::Thread::downloadExtensions()
+{
+ try
+ {
+ //create the download directory in the temp folder
+ OUString sTempDir;
+ if (::osl::FileBase::getTempDirURL(sTempDir) != ::osl::FileBase::E_None)
+ throw css::uno::Exception("Could not get URL for the temp directory. No extensions will be installed.", nullptr);
+
+ //create a unique name for the directory
+ OUString tempEntry, destFolder;
+ if (::osl::File::createTempFile(&sTempDir, nullptr, &tempEntry ) != ::osl::File::E_None)
+ throw css::uno::Exception("Could not create a temporary file in " + sTempDir +
+ ". No extensions will be installed", nullptr );
+
+ tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 );
+
+ destFolder = dp_misc::makeURL( sTempDir, tempEntry ) + "_";
+ m_sDownloadFolder = destFolder;
+ try
+ {
+ dp_misc::create_folder(nullptr, destFolder, m_updateCmdEnv );
+ } catch (const css::uno::Exception & e)
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetException( e.Message + " No extensions will be installed",
+ nullptr, anyEx );
+ }
+
+
+ sal_uInt16 count = 0;
+ for (auto & updateData : m_aVecUpdateData)
+ {
+ if (!updateData.aUpdateInfo.is() || updateData.aUpdateSource.is())
+ continue;
+ //We assume that m_aVecUpdateData contains only information about extensions which
+ //can be downloaded directly.
+ OSL_ASSERT(updateData.sWebsiteURL.isEmpty());
+
+ //update the name of the extension which is to be downloaded
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName());
+ sal_uInt16 prog = (sal::static_int_cast<sal_uInt16>(100) * ++count) /
+ sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size());
+ m_dialog.m_xStatusbar->set_percentage(prog);
+ }
+ dp_misc::DescriptionInfoset info(m_xComponentContext, updateData.aUpdateInfo);
+ //remember occurring exceptions in case we need to print out error information
+ std::vector< std::pair<OUString, css::uno::Exception> > vecExceptions;
+ css::uno::Sequence<OUString> seqDownloadURLs = info.getUpdateDownloadUrls();
+ OSL_ENSURE(seqDownloadURLs.hasElements(), "No download URL provided!");
+ for (sal_Int32 j = 0; j < seqDownloadURLs.getLength(); j++)
+ {
+ try
+ {
+ OSL_ENSURE(!seqDownloadURLs[j].isEmpty(), "Download URL is empty!");
+ bool bCancelled = download(seqDownloadURLs[j], updateData);
+ if (bCancelled || !updateData.sLocalURL.isEmpty())
+ break;
+ }
+ catch ( css::uno::Exception & e )
+ {
+ vecExceptions.emplace_back(seqDownloadURLs[j], e);
+ //There can be several different errors, for example, the URL is wrong, webserver cannot be reached,
+ //name cannot be resolved. The UCB helper API does not specify different special exceptions for these
+ //cases. Therefore ignore and continue.
+ continue;
+ }
+ }
+ //update the progress and display download error
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ if (updateData.sLocalURL.isEmpty())
+ {
+ //Construct a string of all messages contained in the exceptions plus the respective download URLs
+ OUStringBuffer buf(256);
+ size_t nPos = 0;
+ for (auto const& elem : vecExceptions)
+ {
+ if (nPos)
+ buf.append("\n");
+ buf.append("Could not download " + elem.first + ". " + elem.second.Message);
+ ++nPos;
+ }
+ m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, updateData.aInstalledPackage->getDisplayName(),
+ buf);
+ }
+ }
+
+ }
+ }
+ catch (const css::uno::Exception & e)
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.setError(e.Message);
+ }
+}
+
+void UpdateInstallDialog::Thread::installExtensions()
+{
+ //Update the fix text in the dialog to "Installing extensions..."
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.m_xFt_action->set_label(m_dialog.m_sInstalling);
+ m_dialog.m_xStatusbar->set_percentage(0);
+ }
+
+ sal_uInt16 count = 0;
+ for (auto const& updateData : m_aVecUpdateData)
+ {
+ //update the name of the extension which is to be installed
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ //we only show progress after an extension has been installed.
+ if (count > 0) {
+ m_dialog.m_xStatusbar->set_percentage(
+ (sal::static_int_cast<sal_uInt16>(100) * count) /
+ sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size()));
+ }
+ m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName());
+ }
+ bool bError = false;
+ bool bLicenseDeclined = false;
+ css::uno::Reference<css::deployment::XPackage> xExtension;
+ css::uno::Exception exc;
+ try
+ {
+ css::uno::Reference< css::task::XAbortChannel > xAbortChannel(
+ updateData.aInstalledPackage->createAbortChannel() );
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_abort = xAbortChannel;
+ }
+ if (!updateData.aUpdateSource.is() && !updateData.sLocalURL.isEmpty())
+ {
+ css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1")));
+ if (!updateData.bIsShared)
+ xExtension = m_dialog.getExtensionManager()->addExtension(
+ updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
+ "user", xAbortChannel, m_updateCmdEnv);
+ else
+ xExtension = m_dialog.getExtensionManager()->addExtension(
+ updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
+ "shared", xAbortChannel, m_updateCmdEnv);
+ }
+ else if (updateData.aUpdateSource.is())
+ {
+ OSL_ASSERT(updateData.aUpdateSource.is());
+ //I am not sure if we should obtain the install properties and pass them into
+ //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen
+ //that a license is displayed when updating from the shared repository, although the
+ //shared extension was installed using "SUPPRESS_LICENSE".
+ css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1")));
+ if (!updateData.bIsShared)
+ xExtension = m_dialog.getExtensionManager()->addExtension(
+ updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
+ "user", xAbortChannel, m_updateCmdEnv);
+ else
+ xExtension = m_dialog.getExtensionManager()->addExtension(
+ updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
+ "shared", xAbortChannel, m_updateCmdEnv);
+ }
+ }
+ catch (css::deployment::DeploymentException & de)
+ {
+ if (de.Cause.has<css::deployment::LicenseException>())
+ {
+ bLicenseDeclined = true;
+ }
+ else
+ {
+ exc = de.Cause.get<css::uno::Exception>();
+ bError = true;
+ }
+ }
+ catch (css::uno::Exception& e)
+ {
+ exc = e;
+ bError = true;
+ }
+
+ if (bLicenseDeclined)
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED,
+ updateData.aInstalledPackage->getDisplayName(), std::u16string_view());
+ }
+ else if (!xExtension.is() || bError)
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION,
+ updateData.aInstalledPackage->getDisplayName(), exc.Message);
+ }
+ ++count;
+ }
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return;
+ }
+ m_dialog.m_xStatusbar->set_percentage(100);
+ m_dialog.m_xFt_extension_name->set_label(OUString());
+ m_dialog.m_xFt_action->set_label(m_dialog.m_sFinished);
+ }
+}
+
+void UpdateInstallDialog::Thread::removeTempDownloads()
+{
+ if (!m_sDownloadFolder.isEmpty())
+ {
+ dp_misc::erase_path(m_sDownloadFolder,
+ css::uno::Reference<css::ucb::XCommandEnvironment>(),false /* no throw: ignore errors */ );
+ //remove also the temp file which we have used to create the unique name
+ OUString tempFile = m_sDownloadFolder.copy(0, m_sDownloadFolder.getLength() - 1);
+ dp_misc::erase_path(tempFile, css::uno::Reference<css::ucb::XCommandEnvironment>(),false);
+ m_sDownloadFolder.clear();
+ }
+}
+
+bool UpdateInstallDialog::Thread::download(OUString const & sDownloadURL, UpdateData & aUpdateData)
+{
+ {
+ SolarMutexGuard g;
+ if (m_stop) {
+ return m_stop;
+ }
+ }
+
+ OSL_ASSERT(m_sDownloadFolder.getLength());
+ OUString destFolder, tempEntry;
+ if (::osl::File::createTempFile(
+ &m_sDownloadFolder,
+ nullptr, &tempEntry ) != ::osl::File::E_None)
+ {
+ //ToDo feedback in window that download of this component failed
+ throw css::uno::Exception("Could not create temporary file in folder " + destFolder + ".", nullptr);
+ }
+ tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 );
+
+ destFolder = dp_misc::makeURL( m_sDownloadFolder, tempEntry ) + "_";
+
+ ::ucbhelper::Content destFolderContent;
+ dp_misc::create_folder( &destFolderContent, destFolder, m_updateCmdEnv );
+
+ ::ucbhelper::Content sourceContent;
+ (void)dp_misc::create_ucb_content(&sourceContent, sDownloadURL, m_updateCmdEnv);
+
+ const OUString sTitle( StrTitle::getTitle( sourceContent ) );
+
+ destFolderContent.transferContent(
+ sourceContent, ::ucbhelper::InsertOperation::Copy,
+ sTitle, css::ucb::NameClash::OVERWRITE );
+
+ {
+ //the user may have cancelled the dialog because downloading took too long
+ SolarMutexGuard g;
+ if (m_stop) {
+ return m_stop;
+ }
+ //all errors should be handled by the command environment.
+ aUpdateData.sLocalURL = destFolder + "/" + sTitle;
+ }
+
+ return m_stop;
+}
+
+UpdateCommandEnv::UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx,
+ ::rtl::Reference<UpdateInstallDialog::Thread> thread)
+ : m_installThread(std::move(thread)),
+ m_xContext(std::move(xCtx))
+{
+}
+
+// XCommandEnvironment
+css::uno::Reference<css::task::XInteractionHandler> UpdateCommandEnv::getInteractionHandler()
+{
+ return this;
+}
+
+css::uno::Reference<css::ucb::XProgressHandler> UpdateCommandEnv::getProgressHandler()
+{
+ return this;
+}
+
+// XInteractionHandler
+void UpdateCommandEnv::handle(
+ css::uno::Reference< css::task::XInteractionRequest> const & xRequest )
+{
+ css::uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == css::uno::TypeClass_EXCEPTION );
+ dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n"
+ + ::comphelper::anyToString(request) + "\n\n");
+
+ css::deployment::VersionException verExc;
+ bool approve = false;
+
+ if (request >>= verExc)
+ { //We must catch the version exception during the update,
+ //because otherwise the user would be confronted with the dialogs, asking
+ //them if they want to replace an already installed version of the same extension.
+ //During an update we assume that we always want to replace the old version with the
+ //new version.
+ approve = true;
+ }
+
+ if (!approve)
+ {
+ //forward to interaction handler for main dialog.
+ handleInteractionRequest( m_xContext, xRequest );
+ }
+ else
+ {
+ // select:
+ css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > conts(
+ xRequest->getContinuations() );
+ css::uno::Reference< css::task::XInteractionContinuation > const * pConts =
+ conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if (approve) {
+ css::uno::Reference< css::task::XInteractionApprove > xInteractionApprove(
+ pConts[ pos ], css::uno::UNO_QUERY );
+ if (xInteractionApprove.is()) {
+ xInteractionApprove->select();
+ // don't query again for ongoing continuations:
+ approve = false;
+ }
+ }
+ }
+ }
+}
+
+// XProgressHandler
+void UpdateCommandEnv::push( css::uno::Any const & /*Status*/ )
+{
+}
+
+void UpdateCommandEnv::update( css::uno::Any const & /*Status */)
+{
+}
+
+void UpdateCommandEnv::pop()
+{
+}
+
+
+} //end namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx
new file mode 100644
index 0000000000..39b37ed4bc
--- /dev/null
+++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <vcl/weld.hxx>
+#include <rtl/ref.hxx>
+
+#include <string_view>
+#include <vector>
+
+/// @HTML
+namespace com::sun::star::deployment {
+ class XExtensionManager;
+}
+namespace com::sun::star::uno {
+ class XComponentContext;
+}
+
+namespace dp_gui {
+
+ struct UpdateData;
+ class UpdateCommandEnv;
+
+
+/**
+ The modal &ldquo;Download and Installation&rdquo; dialog.
+*/
+class UpdateInstallDialog : public weld::GenericDialogController
+{
+public:
+ /**
+ Create an instance.
+
+ @param parent
+ the parent window, may be null
+ */
+ UpdateInstallDialog(weld::Window* parent, std::vector<UpdateData> & aVecUpdateData,
+ css::uno::Reference< css::uno::XComponentContext > const & xCtx);
+
+ virtual ~UpdateInstallDialog() override;
+
+ virtual short run() override;
+
+private:
+ UpdateInstallDialog(UpdateInstallDialog const &) = delete;
+ UpdateInstallDialog& operator =(UpdateInstallDialog const &) = delete;
+
+ class Thread;
+ friend class Thread;
+ friend class UpdateCommandEnv;
+
+ DECL_LINK(cancelHandler, weld::Button&, void);
+
+ //signals in the dialog that we have finished.
+ void updateDone();
+ //Writes a particular error into the info listbox.
+ enum INSTALL_ERROR
+ {
+ ERROR_DOWNLOAD,
+ ERROR_INSTALLATION,
+ ERROR_LICENSE_DECLINED
+ };
+ void setError(INSTALL_ERROR err, std::u16string_view sExtension, std::u16string_view exceptionMessage);
+ void setError(std::u16string_view exceptionMessage);
+ const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const
+ { return m_xExtensionManager; }
+
+ rtl::Reference< Thread > m_thread;
+ css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager;
+ //Signals that an error occurred during download and installation
+ bool m_bError;
+ bool m_bNoEntry;
+
+ OUString m_sInstalling;
+ OUString m_sFinished;
+ OUString m_sNoErrors;
+ OUString m_sErrorDownload;
+ OUString m_sErrorInstallation;
+ OUString m_sErrorLicenseDeclined;
+ OUString m_sNoInstall;
+ OUString m_sThisErrorOccurred;
+
+ std::unique_ptr<weld::Label> m_xFt_action;
+ std::unique_ptr<weld::ProgressBar> m_xStatusbar;
+ std::unique_ptr<weld::Label> m_xFt_extension_name;
+ std::unique_ptr<weld::TextView> m_xMle_info;
+ std::unique_ptr<weld::Button> m_xHelp;
+ std::unique_ptr<weld::Button> m_xOk;
+ std::unique_ptr<weld::Button> m_xCancel;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/license_dialog.cxx b/desktop/source/deployment/gui/license_dialog.cxx
new file mode 100644
index 0000000000..23f184a333
--- /dev/null
+++ b/desktop/source/deployment/gui/license_dialog.cxx
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <comphelper/unwrapargs.hxx>
+#include <vcl/event.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/threadex.hxx>
+#include <vcl/weld.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include "license_dialog.hxx"
+
+#include <functional>
+#include <string_view>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_gui {
+
+namespace {
+
+struct LicenseDialogImpl : public weld::GenericDialogController
+{
+ bool m_bLicenseRead;
+ Idle m_aResized;
+ AutoTimer m_aRepeat;
+
+ std::unique_ptr<weld::Label> m_xFtHead;
+ std::unique_ptr<weld::Widget> m_xArrow1;
+ std::unique_ptr<weld::Widget> m_xArrow2;
+ std::unique_ptr<weld::TextView> m_xLicense;
+ std::unique_ptr<weld::Button> m_xDown;
+ std::unique_ptr<weld::Button> m_xAcceptButton;
+ std::unique_ptr<weld::Button> m_xDeclineButton;
+
+ void PageDown();
+ DECL_LINK(ScrollTimerHdl, Timer*, void);
+ DECL_LINK(ScrolledHdl, weld::TextView&, void);
+ DECL_LINK(ResizedHdl, Timer*, void);
+ DECL_LINK(CancelHdl, weld::Button&, void);
+ DECL_LINK(AcceptHdl, weld::Button&, void);
+ DECL_LINK(KeyInputHdl, const KeyEvent&, bool);
+ DECL_STATIC_LINK(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool);
+ DECL_LINK(MousePressHdl, const MouseEvent&, bool);
+ DECL_LINK(MouseReleaseHdl, const MouseEvent&, bool);
+ DECL_LINK(SizeAllocHdl, const Size&, void);
+
+ LicenseDialogImpl(weld::Window * pParent,
+ std::u16string_view sExtensionName,
+ const OUString & sLicenseText);
+
+ bool IsEndReached() const;
+};
+
+}
+
+LicenseDialogImpl::LicenseDialogImpl(
+ weld::Window * pParent,
+ std::u16string_view sExtensionName,
+ const OUString & sLicenseText)
+ : GenericDialogController(pParent, "desktop/ui/licensedialog.ui", "LicenseDialog")
+ , m_bLicenseRead(false)
+ , m_aResized("desktop LicenseDialogImpl m_aResized")
+ , m_aRepeat("LicenseDialogImpl m_aRepeat")
+ , m_xFtHead(m_xBuilder->weld_label("head"))
+ , m_xArrow1(m_xBuilder->weld_widget("arrow1"))
+ , m_xArrow2(m_xBuilder->weld_widget("arrow2"))
+ , m_xLicense(m_xBuilder->weld_text_view("textview"))
+ , m_xDown(m_xBuilder->weld_button("down"))
+ , m_xAcceptButton(m_xBuilder->weld_button("ok"))
+ , m_xDeclineButton(m_xBuilder->weld_button("cancel"))
+{
+ m_xArrow1->show();
+ m_xArrow2->hide();
+
+ m_xLicense->connect_size_allocate(LINK(this, LicenseDialogImpl, SizeAllocHdl));
+ m_xLicense->set_size_request(m_xLicense->get_approximate_digit_width() * 72,
+ m_xLicense->get_height_rows(21));
+
+ m_xLicense->set_text(sLicenseText);
+ m_xFtHead->set_label(m_xFtHead->get_label() + "\n" + sExtensionName);
+
+ m_xAcceptButton->connect_clicked( LINK(this, LicenseDialogImpl, AcceptHdl) );
+ m_xDeclineButton->connect_clicked( LINK(this, LicenseDialogImpl, CancelHdl) );
+
+ m_xLicense->connect_vadjustment_changed(LINK(this, LicenseDialogImpl, ScrolledHdl));
+ m_xDown->connect_mouse_press(LINK(this, LicenseDialogImpl, MousePressHdl));
+ m_xDown->connect_mouse_release(LINK(this, LicenseDialogImpl, MouseReleaseHdl));
+ m_xDown->connect_key_press(LINK(this, LicenseDialogImpl, KeyInputHdl));
+ m_xDown->connect_key_release(LINK(this, LicenseDialogImpl, KeyReleaseHdl));
+
+ m_aRepeat.SetTimeout(Application::GetSettings().GetMouseSettings().GetButtonRepeat());
+ m_aRepeat.SetInvokeHandler(LINK(this, LicenseDialogImpl, ScrollTimerHdl));
+
+ m_aResized.SetPriority(TaskPriority::LOWEST);
+ m_aResized.SetInvokeHandler(LINK(this, LicenseDialogImpl, ResizedHdl));
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, SizeAllocHdl, const Size&, void)
+{
+ m_aResized.Start();
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, AcceptHdl, weld::Button&, void)
+{
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, CancelHdl, weld::Button&, void)
+{
+ m_xDialog->response(RET_CANCEL);
+}
+
+bool LicenseDialogImpl::IsEndReached() const
+{
+ return m_xLicense->vadjustment_get_value() + m_xLicense->vadjustment_get_page_size() >= m_xLicense->vadjustment_get_upper();
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, ScrolledHdl, weld::TextView&, void)
+{
+ if (IsEndReached())
+ {
+ m_xDown->set_sensitive(false);
+ m_aRepeat.Stop();
+
+ if (!m_bLicenseRead)
+ {
+ m_xAcceptButton->set_sensitive(true);
+ m_xAcceptButton->grab_focus();
+ m_xArrow1->hide();
+ m_xArrow2->show();
+ m_bLicenseRead = true;
+ }
+ }
+ else
+ m_xDown->set_sensitive(true);
+}
+
+void LicenseDialogImpl::PageDown()
+{
+ m_xLicense->vadjustment_set_value(m_xLicense->vadjustment_get_value() +
+ m_xLicense->vadjustment_get_page_size());
+ ScrolledHdl(*m_xLicense);
+}
+
+IMPL_LINK(LicenseDialogImpl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ if (aKeyCode.GetCode() == KEY_RETURN || aKeyCode.GetCode() == KEY_SPACE)
+ PageDown();
+ return false;
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, ResizedHdl, Timer*, void)
+{
+ ScrolledHdl(*m_xLicense);
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, ScrollTimerHdl, Timer*, void)
+{
+ PageDown();
+}
+
+IMPL_STATIC_LINK_NOARG(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool)
+{
+ return false;
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, MousePressHdl, const MouseEvent&, bool)
+{
+ PageDown();
+ m_aRepeat.Start();
+ return false;
+}
+
+IMPL_LINK_NOARG(LicenseDialogImpl, MouseReleaseHdl, const MouseEvent&, bool)
+{
+ m_aRepeat.Stop();
+ return false;
+}
+
+LicenseDialog::LicenseDialog( Sequence<Any> const& args,
+ Reference<XComponentContext> const& )
+{
+ comphelper::unwrapArgs( args, m_parent, m_sExtensionName, m_sLicenseText );
+}
+
+// XServiceInfo
+OUString LicenseDialog::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.ui.LicenseDialog";
+}
+
+sal_Bool LicenseDialog::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > LicenseDialog::getSupportedServiceNames()
+{
+ return { "com.sun.star.deployment.ui.LicenseDialog" };
+}
+
+
+// XExecutableDialog
+
+void LicenseDialog::setTitle( OUString const & )
+{
+}
+
+sal_Int16 LicenseDialog::execute()
+{
+ return vcl::solarthread::syncExecute(
+ std::bind(&LicenseDialog::solar_execute, this));
+}
+
+sal_Int16 LicenseDialog::solar_execute()
+{
+ LicenseDialogImpl dlg(Application::GetFrameWeld(m_parent), m_sExtensionName, m_sLicenseText);
+ return dlg.run();
+}
+
+} // namespace dp_gui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/license_dialog.hxx b/desktop/source/deployment/gui/license_dialog.hxx
new file mode 100644
index 0000000000..3c628a880d
--- /dev/null
+++ b/desktop/source/deployment/gui/license_dialog.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+namespace dp_gui
+{
+class LicenseDialog
+ : public ::cppu::WeakImplHelper<css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo>
+{
+ css::uno::Reference<css::awt::XWindow> /* const */ m_parent;
+ OUString m_sExtensionName;
+ OUString /* const */ m_sLicenseText;
+
+ sal_Int16 solar_execute();
+
+public:
+ LicenseDialog(css::uno::Sequence<css::uno::Any> const& args,
+ css::uno::Reference<css::uno::XComponentContext> const& xComponentContext);
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ // XExecutableDialog
+ virtual void SAL_CALL setTitle(OUString const& title) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_dependencies.hxx b/desktop/source/deployment/inc/dp_dependencies.hxx
new file mode 100644
index 0000000000..1c9872c785
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_dependencies.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include "dp_misc_api.hxx"
+
+/// @HTML
+
+namespace com::sun::star::xml::dom {
+ class XElement;
+}
+namespace dp_misc { class DescriptionInfoset; }
+
+namespace dp_misc {
+
+/**
+ Dependency handling.
+*/
+namespace Dependencies {
+ /**
+ Check for unsatisfied dependencies.
+
+ @param infoset
+ the infoset containing the dependencies to check
+
+ @return
+ a list of the unsatisfied dependencies from <code>infoset</code> (in no
+ specific order)
+ */
+ DESKTOP_DEPLOYMENTMISC_DLLPUBLIC css::uno::Sequence<
+ css::uno::Reference< css::xml::dom::XElement > >
+ check(dp_misc::DescriptionInfoset const & infoset);
+
+ /**
+ Obtain the (human-readable) error message of a failed dependency.
+
+ @param dependency
+ a dependency represented as a non-null XML element
+
+ @return
+ the name of the dependency; will never be empty, as a localized
+ &ldquo;unknown&rdquo; is substituted for an empty/missing name
+ */
+ DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getErrorText(
+ css::uno::Reference< css::xml::dom::XElement >
+ const & dependency);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_descriptioninfoset.hxx b/desktop/source/deployment/inc/dp_descriptioninfoset.hxx
new file mode 100644
index 0000000000..08d533a79d
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_descriptioninfoset.hxx
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <optional>
+#include <string_view>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <sal/types.h>
+#include "dp_misc_api.hxx"
+
+/// @HTML
+
+namespace com::sun::star {
+ namespace uno { class XComponentContext; }
+ namespace xml {
+ namespace dom {
+ class XNode;
+ class XNodeList;
+ }
+ namespace xpath { class XXPathAPI; }
+ }
+}
+
+namespace dp_misc {
+
+struct DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SimpleLicenseAttributes
+{
+ OUString acceptBy;
+ //Attribute suppress-on-update. Default is false.
+ bool suppressOnUpdate;
+ //Attribute suppress-if-required. Default is false.
+ bool suppressIfRequired;
+};
+
+
+/**
+ Access to the content of an XML <code>description</code> element.
+
+ <p>This works for <code>description</code> elements in both the
+ <code>description.xml</code> file and online update information formats.</p>
+*/
+class DESKTOP_DEPLOYMENTMISC_DLLPUBLIC DescriptionInfoset {
+public:
+ /**
+ Create an instance.
+
+ @param context
+ a non-null component context
+
+ @param element
+ a <code>description</code> element; may be null (equivalent to an element
+ with no content)
+ */
+ DescriptionInfoset(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ css::uno::Reference< css::xml::dom::XNode > const & element);
+
+ ~DescriptionInfoset();
+
+ /**
+ Return the identifier.
+
+ @return
+ the identifier, or an empty <code>optional</code> if none is specified
+ */
+ ::std::optional< OUString > getIdentifier() const;
+
+ /**
+ Return the textual version representation.
+
+ @return
+ textual version representation
+ */
+ OUString getVersion() const;
+
+ /**
+ Returns a list of supported platforms.
+
+ If the extension does not specify a platform by leaving out the platform element
+ then we assume that the extension supports all platforms. In this case the returned
+ sequence will have one element, which is &quot;all&quot;.
+ If the platform element is present but does not specify a platform then an empty
+ sequence is returned. Examples for invalid platform elements:
+ <pre>
+ <platform />, <platform value="" />, <platform value=",">
+ </pre>
+
+ The value attribute can contain various platform tokens. They must be separated by
+ commas.Each token will be stripped from leading and trailing white space (trim()).
+ */
+ css::uno::Sequence< OUString > getSupportedPlatforms() const;
+
+ /**
+ Returns the localized publisher name and the corresponding URL.
+
+ In case there is no publisher element then a pair of two empty strings is returned.
+ */
+ std::pair< OUString, OUString > getLocalizedPublisherNameAndURL() const;
+
+ /**
+ Returns the URL for the release notes corresponding to the office's locale.
+
+ In case there is no release-notes element then an empty string is returned.
+ */
+ OUString getLocalizedReleaseNotesURL() const;
+
+ /** returns the relative path to the license file.
+
+ In case there is no simple-license element then an empty string is returned.
+ */
+ OUString getLocalizedLicenseURL() const;
+
+ /** returns the attributes of the simple-license element
+
+ As long as there is a simple-license element, the function will return
+ the structure. If it does not exist, then the optional object is uninitialized.
+ */
+ ::std::optional<SimpleLicenseAttributes> getSimpleLicenseAttributes() const;
+
+ /** returns the localized display name of the extensions.
+
+ In case there is no localized display-name then an empty string is returned.
+ */
+ OUString getLocalizedDisplayName() const;
+
+ /**
+ returns the download website URL from the update information.
+
+ There can be multiple URLs where each is assigned to a particular locale.
+ The function returns the URL which locale matches best the one used in the office.
+
+ The return value is an optional because it may be necessary to find out if there
+ was a value provided or not. This is necessary to flag the extension in the update dialog
+ properly as "browser based update". The return value will only then not be initialized
+ if there is no <code>&lt;update-website&gt;</code>. If the element exists, then it must
+ have at least one child element containing a URL.
+
+ The <code>&lt;update-website&gt;</code> and <code>&lt;update-download&gt;</code>
+ elements are mutually exclusive.
+
+ @return
+ the download website URL, or an empty <code>optional</code> if none is
+ specified
+ */
+ ::std::optional< OUString > getLocalizedUpdateWebsiteURL() const;
+
+ /** returns the relative URL to the description.
+
+ The URL is relative to the root directory of the extensions.
+ */
+ OUString getLocalizedDescriptionURL() const;
+ /**
+ Return the dependencies.
+
+ @return
+ dependencies; will never be null
+ */
+ css::uno::Reference< css::xml::dom::XNodeList >
+ getDependencies() const;
+
+ /**
+ Return the update information URLs.
+
+ @return
+ update information URLs
+ */
+ css::uno::Sequence< OUString > getUpdateInformationUrls() const;
+
+ /**
+ Return the download URLs from the update information.
+
+ Because the <code>&lt;update-download&gt;</code> and the <code>&lt;update-website&gt;</code>
+ elements are mutually exclusive one may need to determine exactly if the element
+ was provided.
+
+ @return
+ download URLs
+ */
+ css::uno::Sequence< OUString > getUpdateDownloadUrls() const;
+
+ /**
+ Returns the URL for the icon image.
+ */
+ OUString getIconURL( bool bHighContrast ) const;
+
+ bool hasDescription() const;
+
+private:
+ SAL_DLLPRIVATE ::std::optional< OUString > getOptionalValue(
+ OUString const & expression) const;
+
+ SAL_DLLPRIVATE css::uno::Sequence< OUString > getUrls(
+ OUString const & expression) const;
+
+ /** Retrieves a child element which as lang attribute which matches the office locale.
+
+ Only top-level children are taken into account. It is also assumed that they are all
+ of the same element type and have a lang attribute. The matching algorithm is according
+ to RFC 3066, with the exception that only one variant is allowed.
+ @param parent
+ the expression used to obtain the parent of the localized children. It can be null.
+ Then a null reference is returned.
+ */
+ SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode >
+ getLocalizedChild( OUString const & sParent) const;
+ SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode>
+ matchLanguageTag(
+ css::uno::Reference< css::xml::dom::XNode > const & xParent,
+ std::u16string_view rTag) const;
+
+ /** If there is no child element with a locale matching the office locale, then we use
+ the first child. In the case of the simple-license we also use the former default locale, which
+ was determined by the default-license-id (/description/registration/simple-license/@default-license-id)
+ and the license-id attributes (/description/registration/simple-license/license-text/@license-id).
+ However, since OOo 2.4 we use also the first child as default for the license
+ unless the two attributes are present.
+ */
+ SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode>
+ getChildWithDefaultLocale(
+ css::uno::Reference< css::xml::dom::XNode > const & xParent) const;
+ /**
+ @param out_bParentExists
+ indicates if the element node specified in sXPathParent exists.
+ */
+ SAL_DLLPRIVATE OUString getLocalizedHREFAttrFromChild(
+ OUString const & sXPathParent, bool * out_bParentExists) const;
+
+ /** Gets the node value for a given expression. The expression is used in
+ m_xpath-selectSingleNode. The value of the returned node is return value
+ of this function.
+ */
+ SAL_DLLPRIVATE OUString
+ getNodeValueFromExpression(OUString const & expression) const;
+
+ /** Check the extensions denylist if additional extension meta data (e.g. dependencies)
+ are defined for this extension and have to be taken into account.
+ */
+ SAL_DLLPRIVATE void
+ checkDenylist() const;
+
+ /** Helper method to compare the versions with the current version
+ */
+ SAL_DLLPRIVATE static bool
+ checkDenylistVersion(std::u16string_view currentversion,
+ css::uno::Sequence< OUString > const & versions);
+
+ css::uno::Reference< css::uno::XComponentContext > m_context;
+ css::uno::Reference< css::xml::dom::XNode > m_element;
+ css::uno::Reference< css::xml::xpath::XXPathAPI > m_xpath;
+};
+
+inline bool DescriptionInfoset::hasDescription() const
+{
+ return m_element.is();
+}
+
+/** creates a DescriptionInfoset object.
+
+ The argument sExtensionFolderURL is a file URL to extension folder containing
+ the description.xml.
+ */
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+DescriptionInfoset getDescriptionInfoset(std::u16string_view sExtensionFolderURL);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_identifier.hxx b/desktop/source/deployment/inc/dp_identifier.hxx
new file mode 100644
index 0000000000..bd11170b6a
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_identifier.hxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <optional>
+#include <string_view>
+
+#include <com/sun/star/uno/Reference.hxx>
+
+#include "dp_misc_api.hxx"
+
+namespace com::sun::star::deployment {
+ class XPackage;
+}
+
+namespace dp_misc {
+
+/**
+ Generates an identifier from an optional identifier.
+
+ @param optional
+ an optional identifier
+
+ @param fileName
+ a file name
+
+ @return
+ the given optional identifier if present, otherwise a legacy identifier based
+ on the given file name
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString generateIdentifier(
+ ::std::optional< OUString > const & optional,
+ std::u16string_view fileName);
+
+/**
+ Gets the identifier of a package.
+
+ @param package
+ a non-null package
+
+ @return
+ the explicit identifier of the given package if present, otherwise the
+ implicit legacy identifier of the given package
+
+ @throws css::uno::RuntimeException
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getIdentifier(
+ css::uno::Reference< css::deployment::XPackage >
+ const & package);
+
+/**
+ Generates a legacy identifier based on a file name.
+
+ @param fileName
+ a file name
+
+ @return
+ a legacy identifier based on the given file name
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString generateLegacyIdentifier(
+ std::u16string_view fileName);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_interact.h b/desktop/source/deployment/inc/dp_interact.h
new file mode 100644
index 0000000000..6a041d99f1
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_interact.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <config_options.h>
+#include <rtl/ref.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/task/XAbortChannel.hpp>
+#include <utility>
+#include "dp_misc_api.hxx"
+
+namespace dp_misc
+{
+
+inline void progressUpdate(
+ OUString const & status,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ if (xCmdEnv.is()) {
+ css::uno::Reference<css::ucb::XProgressHandler> xProgressHandler(
+ xCmdEnv->getProgressHandler() );
+ if (xProgressHandler.is()) {
+ xProgressHandler->update( css::uno::Any(status) );
+ }
+ }
+}
+
+
+class ProgressLevel
+{
+ css::uno::Reference<css::ucb::XProgressHandler> m_xProgressHandler;
+
+public:
+ inline ~ProgressLevel();
+ inline ProgressLevel(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ OUString const & status );
+
+ inline void update( OUString const & status ) const;
+ inline void update( css::uno::Any const & status ) const;
+};
+
+
+inline ProgressLevel::ProgressLevel(
+ css::uno::Reference< css::ucb::XCommandEnvironment > const & xCmdEnv,
+ OUString const & status )
+{
+ if (xCmdEnv.is())
+ m_xProgressHandler = xCmdEnv->getProgressHandler();
+ if (m_xProgressHandler.is())
+ m_xProgressHandler->push( css::uno::Any(status) );
+}
+
+
+inline ProgressLevel::~ProgressLevel()
+{
+ if (m_xProgressHandler.is())
+ m_xProgressHandler->pop();
+}
+
+
+inline void ProgressLevel::update( OUString const & status ) const
+{
+ if (m_xProgressHandler.is())
+ m_xProgressHandler->update( css::uno::Any(status) );
+}
+
+
+inline void ProgressLevel::update( css::uno::Any const & status ) const
+{
+ if (m_xProgressHandler.is())
+ m_xProgressHandler->update( status );
+}
+
+
+
+/** @return true if ia handler is present and any selection has been chosen
+ */
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool interactContinuation(
+ css::uno::Any const & request,
+ css::uno::Type const & continuation,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ bool * pcont, bool * pabort );
+
+
+
+
+class UNLESS_MERGELIBS(DESKTOP_DEPLOYMENTMISC_DLLPUBLIC) AbortChannel :
+ public ::cppu::WeakImplHelper<css::task::XAbortChannel>
+{
+ bool m_aborted;
+ css::uno::Reference<css::task::XAbortChannel> m_xNext;
+
+public:
+ AbortChannel() : m_aborted( false ) {}
+ static AbortChannel * get(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel )
+ { return static_cast<AbortChannel *>(xAbortChannel.get()); }
+
+ bool isAborted() const { return m_aborted; }
+
+ // XAbortChannel
+ virtual void SAL_CALL sendAbort() override;
+
+ class SAL_DLLPRIVATE Chain
+ {
+ const ::rtl::Reference<AbortChannel> m_abortChannel;
+ public:
+ Chain(
+ ::rtl::Reference<AbortChannel> abortChannel,
+ css::uno::Reference<css::task::XAbortChannel> const & xNext )
+ : m_abortChannel(std::move( abortChannel ))
+ { if (m_abortChannel.is()) m_abortChannel->m_xNext = xNext; }
+ ~Chain()
+ { if (m_abortChannel.is()) m_abortChannel->m_xNext.clear(); }
+ };
+ friend class Chain;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_misc_api.hxx b/desktop/source/deployment/inc/dp_misc_api.hxx
new file mode 100644
index 0000000000..ecb07dada3
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_misc_api.hxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <sal/types.h>
+
+#if defined DESKTOP_DEPLOYMENTMISC_DLLIMPLEMENTATION
+#define DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_package.hxx b/desktop/source/deployment/inc/dp_package.hxx
new file mode 100644
index 0000000000..f287990f25
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_package.hxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ustring.hxx>
+
+namespace com::sun::star {
+ namespace deployment { class XPackageRegistry; }
+ namespace uno { class XComponentContext; }
+}
+
+namespace dp_registry::backend::bundle {
+
+css::uno::Reference<css::deployment::XPackageRegistry> create(
+ css::uno::Reference<css::deployment::XPackageRegistry> const &
+ xRootRegistry,
+ OUString const & context, OUString const & cachePath,
+ css::uno::Reference<css::uno::XComponentContext> const & xComponentContext);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_persmap.h b/desktop/source/deployment/inc/dp_persmap.h
new file mode 100644
index 0000000000..c369798073
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_persmap.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <osl/file.hxx>
+#include <unordered_map>
+
+namespace dp_misc
+{
+typedef std::unordered_map<OString, OString> t_string2string_map;
+
+// Class to read obsolete registered extensions
+// should be removed for LibreOffice 4.0
+class PersistentMap final
+{
+ ::osl::File m_MapFile;
+ t_string2string_map m_entries;
+ bool m_bIsOpen;
+ bool m_bToBeCreated;
+ bool m_bIsDirty;
+
+public:
+ ~PersistentMap();
+ PersistentMap(OUString const& url);
+ /** in mem db */
+ PersistentMap();
+
+ bool has(OString const& key) const;
+ bool get(OString* value, OString const& key) const;
+ const t_string2string_map& getEntries() const { return m_entries; }
+ void put(OString const& key, OString const& value);
+ bool erase(OString const& key);
+
+private:
+ void open();
+ void readAll();
+ void add(OString const& key, OString const& value);
+ void flush();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_platform.hxx b/desktop/source/deployment/inc/dp_platform.hxx
new file mode 100644
index 0000000000..5e454700d2
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_platform.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "dp_misc_api.hxx"
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ustring.hxx>
+
+namespace dp_misc
+{
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString const& getPlatformString();
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+bool platform_fits(std::u16string_view platform_string);
+
+/** determines if the current platform corresponds to one of the platform strings.
+
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+bool hasValidPlatform(css::uno::Sequence<OUString> const& platformStrings);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_registry.hxx b/desktop/source/deployment/inc/dp_registry.hxx
new file mode 100644
index 0000000000..76742587ed
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_registry.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ustring.hxx>
+
+namespace com::sun::star {
+ namespace deployment { class XPackageRegistry; }
+ namespace uno { class XComponentContext; }
+}
+
+namespace dp_registry {
+
+css::uno::Reference<css::deployment::XPackageRegistry> create(
+ OUString const & context, OUString const & cachePath,
+ css::uno::Reference<css::uno::XComponentContext> const & xComponentContext);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_resource.h b/desktop/source/deployment/inc/dp_resource.h
new file mode 100644
index 0000000000..471960e8d4
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_resource.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <i18nlangtag/languagetag.hxx>
+#include "dp_misc_api.hxx"
+
+namespace dp_misc
+{
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC const LanguageTag& getOfficeLanguageTag();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_ucb.h b/desktop/source/deployment/inc/dp_ucb.h
new file mode 100644
index 0000000000..e72a2cce93
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_ucb.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vector>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include "dp_misc_api.hxx"
+#include <ucbhelper/content.hxx>
+
+namespace ucbhelper
+{
+class Content;
+}
+
+namespace dp_misc {
+
+struct DESKTOP_DEPLOYMENTMISC_DLLPUBLIC StrTitle
+{
+ static css::uno::Sequence< OUString > getTitleSequence()
+ {
+ css::uno::Sequence<OUString> aSeq { "Title" };
+ return aSeq;
+ }
+ static OUString getTitle( ::ucbhelper::Content &rContent )
+ {
+ return rContent.getPropertyValue("Title").get<OUString>();
+ }
+ // just return titles - the ucbhelper should have a simpler API for this [!]
+ static css::uno::Reference< css::sdbc::XResultSet >
+ createCursor( ::ucbhelper::Content &rContent,
+ ucbhelper::ResultSetInclude eInclude )
+ {
+ return rContent.createCursor( StrTitle::getTitleSequence(), eInclude );
+ }
+};
+
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool create_ucb_content(
+ ::ucbhelper::Content * ucb_content,
+ OUString const & url,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ bool throw_exc = true );
+
+
+/** @return true if previously non-existing folder has been created
+ */
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool create_folder(
+ ::ucbhelper::Content * ucb_content,
+ OUString const & url,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ bool throw_exc = true );
+
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool erase_path(
+ OUString const & url,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ bool throw_exc = true );
+
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+std::vector<sal_Int8> readFile( ::ucbhelper::Content & ucb_content );
+
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+bool readLine( OUString * res, std::u16string_view startingWith,
+ ::ucbhelper::Content & ucb_content, rtl_TextEncoding textenc );
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+bool readProperties( std::vector< std::pair< OUString, OUString> > & out_result,
+ ::ucbhelper::Content & ucb_content);
+
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_update.hxx b/desktop/source/deployment/inc/dp_update.hxx
new file mode 100644
index 0000000000..f673d2f66a
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_update.hxx
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/deployment/XExtensionManager.hpp>
+#include <com/sun/star/deployment/XUpdateInformationProvider.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/xml/dom/XNode.hpp>
+
+#include "dp_misc_api.hxx"
+
+#include <map>
+#include <vector>
+
+namespace dp_misc {
+
+/** returns the default update URL (for the update information) which
+ is used when an extension does not provide its own URL.
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+OUString getExtensionDefaultUpdateURL();
+
+enum UPDATE_SOURCE
+{
+ UPDATE_SOURCE_NONE,
+ UPDATE_SOURCE_SHARED,
+ UPDATE_SOURCE_BUNDLED,
+ UPDATE_SOURCE_ONLINE
+};
+
+/* determine if an update is available which is installed in the
+ user repository.
+
+ If the return value is UPDATE_SOURCE_NONE, then no update is
+ available, otherwise the return value determine from which the
+ repository the update is used.
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+UPDATE_SOURCE isUpdateUserExtension(
+ bool bReadOnlyShared,
+ OUString const & userVersion,
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ std::u16string_view onlineVersion);
+
+/* determine if an update is available which is installed in the
+ shared repository.
+
+ If the return value is UPDATE_SOURCE_NONE, then no update is
+ available, otherwise the return value determine from which the
+ repository the update is used.
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+UPDATE_SOURCE isUpdateSharedExtension(
+ bool bReadOnlyShared,
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ std::u16string_view onlineVersion);
+
+/* determines the extension with the highest identifier and returns it
+
+ */
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+css::uno::Reference< css::deployment::XPackage>
+getExtensionWithHighestVersion(
+ css::uno::Sequence<
+ css::uno::Reference<
+ css::deployment::XPackage> > const & seqExtensionsWithSameId);
+
+
+struct UpdateInfo
+{
+ UpdateInfo( css::uno::Reference< css::deployment::XPackage> const & ext);
+
+ css::uno::Reference< css::deployment::XPackage> extension;
+ //version of the update
+ OUString version;
+ css::uno::Reference< css::xml::dom::XNode > info;
+};
+
+typedef std::map< OUString, UpdateInfo > UpdateInfoMap;
+
+/*
+ @param extensionList
+ List of extension for which online update information is to be obtained. If NULL, then
+ for update information is obtained for all installed extension. There may be only one extension
+ with a particular identifier contained in the list. If one extension is installed
+ in several repositories, then the one with the highest version must be used, because it contains
+ the more recent URLs for getting the update information (if at all).
+ @param out_errors
+ the first member of the pair is the extension and the second the exception that was produced
+ when processing the extension.
+
+ @return
+ A map of UpdateInfo instances. If the parameter extensionList was given, then the map contains
+ at only information for those extensions.
+ */
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+UpdateInfoMap getOnlineUpdateInfos(
+ css::uno::Reference< css::uno::XComponentContext> const &xContext,
+ css::uno::Reference< css::deployment::XExtensionManager> const & xExtMgr,
+ css::uno::Reference< css::deployment::XUpdateInformationProvider > const & updateInformation,
+ std::vector< css::uno::Reference< css::deployment::XPackage > > const * extensionList,
+ std::vector< std::pair< css::uno::Reference<
+ css::deployment::XPackage>, css::uno::Any> > & out_errors);
+
+/* returns the highest version from the provided arguments.
+*/
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC
+OUString getHighestVersion(
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ OUString const & onlineVersion);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_version.hxx b/desktop/source/deployment/inc/dp_version.hxx
new file mode 100644
index 0000000000..f088b6861a
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_version.hxx
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <rtl/ustring.hxx>
+#include "dp_misc_api.hxx"
+
+
+namespace dp_misc {
+
+enum Order { LESS, EQUAL, GREATER };
+
+DESKTOP_DEPLOYMENTMISC_DLLPUBLIC Order compareVersions(
+ std::u16string_view version1, std::u16string_view version2);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/dp_xml.h b/desktop/source/deployment/inc/dp_xml.h
new file mode 100644
index 0000000000..608073328b
--- /dev/null
+++ b/desktop/source/deployment/inc/dp_xml.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+
+
+namespace ucbhelper
+{
+class Content;
+}
+
+namespace dp_misc
+{
+
+
+void xml_parse(
+ css::uno::Reference< css::xml::sax::XDocumentHandler > const & xDocHandler,
+ ::ucbhelper::Content & ucb_content,
+ css::uno::Reference< css::uno::XComponentContext > const & xContext );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/inc/lockfile.hxx b/desktop/source/deployment/inc/lockfile.hxx
new file mode 100644
index 0000000000..982a0c2f05
--- /dev/null
+++ b/desktop/source/deployment/inc/lockfile.hxx
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/* Information:
+ * This class implements a mechanism to lock a users installation directory,
+ * which is necessary because instances of staroffice could be running on
+ * different hosts while using the same directory thus causing data
+ * inconsistency.
+ * When an existing lock is detected, the user will be asked whether he wants
+ * to continue anyway, thus removing the lock and replacing it with a new one
+ *
+ * ideas:
+ * - store information about user and host and time in the lockfile and display
+ * these when asking whether to remove the lockfile.
+ * - periodically check the lockfile and warn the user when it gets replaced
+ *
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+#include "dp_misc_api.hxx"
+
+#define LOCKFILE_GROUP "Lockdata"
+#define LOCKFILE_USERKEY "User"
+#define LOCKFILE_HOSTKEY "Host"
+#define LOCKFILE_STAMPKEY "Stamp"
+#define LOCKFILE_TIMEKEY "Time"
+#define LOCKFILE_IPCKEY "IPCServer"
+
+namespace desktop {
+
+ class Lockfile;
+ bool Lockfile_execWarning( Lockfile const * that );
+
+ class DESKTOP_DEPLOYMENTMISC_DLLPUBLIC Lockfile
+ {
+ public:
+
+ // constructs a new lockfile object
+ Lockfile( bool bIPCserver = true );
+
+ // separating GUI code:
+ typedef bool (* fpExecWarning)( Lockfile const * that );
+
+ // checks the lockfile, asks user when lockfile is
+ // found (iff gui) and returns false when we may not continue
+ bool check( fpExecWarning execWarning );
+
+ // removes the lockfile
+ ~Lockfile();
+
+ private:
+ bool m_bIPCserver;
+ // full qualified name (file://-url) of the lockfile
+ OUString m_aLockname;
+ // flag whether the d'tor should delete the lock
+ bool m_bRemove;
+ bool m_bIsLocked;
+ // ID
+ OUString m_aId;
+ OUString m_aDate;
+ // access to data in file
+ void syncToFile() const;
+ bool isStale() const;
+ friend bool Lockfile_execWarning( Lockfile const * that );
+
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_activepackages.cxx b/desktop/source/deployment/manager/dp_activepackages.cxx
new file mode 100644
index 0000000000..c1c5f2b28d
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_activepackages.cxx
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_extensions.h>
+
+#include <sal/config.h>
+
+#include <string_view>
+#include <utility>
+
+#include <osl/diagnose.h>
+#include <rtl/string.hxx>
+#include <rtl/textenc.h>
+#include <rtl/ustring.hxx>
+
+#include <dp_identifier.hxx>
+#include "dp_activepackages.hxx"
+
+// Old format of database entry:
+// key: UTF8(filename)
+// value: UTF8(tempname ";" mediatype)
+// New format of database entry:
+// key: 0xFF UTF8(identifier)
+// value: UTF8(tempname) 0xFF UTF8(filename) 0xFF UTF8(mediatype)
+
+#if HAVE_FEATURE_EXTENSIONS
+
+namespace {
+
+constexpr const char separator[] = "\xff";
+
+OString oldKey(std::u16string_view fileName) {
+ return OUStringToOString(fileName, RTL_TEXTENCODING_UTF8);
+}
+
+OString newKey(std::u16string_view id) {
+ return separator + OUStringToOString(id, RTL_TEXTENCODING_UTF8);
+}
+
+::dp_manager::ActivePackages::Data decodeOldData(
+ OUString const & fileName, OString const & value)
+{
+ ::dp_manager::ActivePackages::Data d;
+ sal_Int32 i = value.indexOf(';');
+ OSL_ASSERT(i >= 0);
+ d.temporaryName = OUString(value.getStr(), i, RTL_TEXTENCODING_UTF8);
+ d.fileName = fileName;
+ d.mediaType = OUString(
+ value.getStr() + i + 1, value.getLength() - i - 1,
+ RTL_TEXTENCODING_UTF8);
+ return d;
+}
+
+::dp_manager::ActivePackages::Data decodeNewData(OString const & value) {
+ ::dp_manager::ActivePackages::Data d;
+ sal_Int32 i1 = value.indexOf(separator);
+ OSL_ASSERT(i1 >= 0);
+ d.temporaryName = OUString(
+ value.getStr(), i1, RTL_TEXTENCODING_UTF8);
+ sal_Int32 i2 = value.indexOf(separator, i1 + 1);
+ OSL_ASSERT(i2 >= 0);
+ d.fileName = OUString(
+ value.getStr() + i1 + 1, i2 - i1 - 1, RTL_TEXTENCODING_UTF8);
+ sal_Int32 i3 = value.indexOf(separator, i2 + 1);
+
+ if (i3 < 0)
+ {
+ //Before ActivePackages::Data::version was added
+ d.mediaType = OUString(
+ value.getStr() + i2 + 1, value.getLength() - i2 - 1,
+ RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+ sal_Int32 i4 = value.indexOf(separator, i3 + 1);
+ d.mediaType = OUString(
+ value.getStr() + i2 + 1, i3 - i2 -1, RTL_TEXTENCODING_UTF8);
+ d.version = OUString(
+ value.getStr() + i3 + 1, i4 - i3 - 1,
+ RTL_TEXTENCODING_UTF8);
+ d.failedPrerequisites = OUString(
+ value.getStr() + i4 + 1, value.getLength() - i4 - 1,
+ RTL_TEXTENCODING_UTF8);
+ }
+ return d;
+}
+
+}
+#endif
+
+namespace dp_manager {
+
+ActivePackages::ActivePackages() {}
+
+ActivePackages::ActivePackages(OUString const & url)
+#if HAVE_FEATURE_EXTENSIONS
+ : m_map(url)
+#endif
+{
+#if !HAVE_FEATURE_EXTENSIONS
+ (void) url;
+#endif
+}
+
+ActivePackages::~ActivePackages() {}
+
+bool ActivePackages::has(
+ OUString const & id, OUString const & fileName) const
+{
+ return get(nullptr, id, fileName);
+}
+
+bool ActivePackages::get(
+ Data * data, OUString const & id, OUString const & fileName)
+ const
+{
+#if HAVE_FEATURE_EXTENSIONS
+ OString v;
+ if (m_map.get(&v, newKey(id))) {
+ if (data != nullptr) {
+ *data = decodeNewData(v);
+ }
+ return true;
+ } else if (m_map.get(&v, oldKey(fileName))) {
+ if (data != nullptr) {
+ *data = decodeOldData(fileName, v);
+ }
+ return true;
+ } else {
+ return false;
+ }
+#else
+ (void) data;
+ (void) id;
+ (void) fileName;
+ (void) this;
+ return false;
+#endif
+}
+
+ActivePackages::Entries ActivePackages::getEntries() const {
+ Entries es;
+#if HAVE_FEATURE_EXTENSIONS
+ ::dp_misc::t_string2string_map m(m_map.getEntries());
+ for (auto const& elem : m)
+ {
+ if (!elem.first.isEmpty() && elem.first[0] == separator[0]) {
+ es.emplace_back(
+ OUString(
+ elem.first.getStr() + 1, elem.first.getLength() - 1,
+ RTL_TEXTENCODING_UTF8),
+ decodeNewData(elem.second));
+ } else {
+ OUString fn(
+ OStringToOUString(elem.first, RTL_TEXTENCODING_UTF8));
+ es.emplace_back(
+ ::dp_misc::generateLegacyIdentifier(fn),
+ decodeOldData(fn, elem.second));
+ }
+ }
+#else
+ (void) this;
+#endif
+ return es;
+}
+
+void ActivePackages::put(OUString const & id, Data const & data) {
+#if HAVE_FEATURE_EXTENSIONS
+ OString b =
+ OUStringToOString(data.temporaryName, RTL_TEXTENCODING_UTF8) +
+ separator +
+ OUStringToOString(data.fileName, RTL_TEXTENCODING_UTF8) +
+ separator +
+ OUStringToOString(data.mediaType, RTL_TEXTENCODING_UTF8) +
+ separator +
+ OUStringToOString(data.version, RTL_TEXTENCODING_UTF8) +
+ separator +
+ OUStringToOString(data.failedPrerequisites, RTL_TEXTENCODING_UTF8);
+ m_map.put(newKey(id), b);
+#else
+ (void) id;
+ (void) data;
+ (void) this;
+#endif
+}
+
+void ActivePackages::erase(
+ OUString const & id, OUString const & fileName)
+{
+#if HAVE_FEATURE_EXTENSIONS
+ m_map.erase(newKey(id)) || m_map.erase(oldKey(fileName));
+#else
+ (void) id;
+ (void) fileName;
+ (void) this;
+#endif
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_activepackages.hxx b/desktop/source/deployment/manager/dp_activepackages.hxx
new file mode 100644
index 0000000000..fae938019c
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_activepackages.hxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <config_extensions.h>
+
+#include <sal/config.h>
+#include <rtl/ustring.hxx>
+
+#include <utility>
+#include <vector>
+
+#if HAVE_FEATURE_EXTENSIONS
+#include <dp_persmap.h>
+#endif
+
+
+namespace dp_manager {
+
+class ActivePackages {
+public:
+ struct Data {
+ Data(): failedPrerequisites("0")
+ {}
+ /* name of the temporary file (shared, user extension) or the name of
+ the folder of the bundled extension.
+ It does not contain the trailing '_' of the folder.
+ UTF-8 encoded
+ */
+ OUString temporaryName;
+ /* The file name (shared, user) or the folder name (bundled)
+ If the key is the file name, then file name is not encoded.
+ If the key is the identifier then the file name is UTF-8 encoded.
+ */
+ OUString fileName;
+ OUString mediaType;
+ OUString version;
+ /* If this string contains the value according to
+ css::deployment::Prerequisites or "0". That is, if
+ the value is > 0 then
+ the call to XPackage::checkPrerequisites failed.
+ In this case the extension must not be registered.
+ */
+ OUString failedPrerequisites;
+ };
+
+ typedef std::vector< std::pair< OUString, Data > > Entries;
+
+ ActivePackages();
+
+ explicit ActivePackages(OUString const & url);
+
+ ~ActivePackages();
+
+ bool has(OUString const & id, OUString const & fileName)
+ const;
+
+ bool get(
+ Data * data, OUString const & id,
+ OUString const & fileName) const;
+
+ Entries getEntries() const;
+
+ void put(OUString const & id, Data const & value);
+
+ void erase(OUString const & id, OUString const & fileName);
+
+private:
+ ActivePackages(ActivePackages const &) = delete;
+ ActivePackages& operator =(ActivePackages const &) = delete;
+#if HAVE_FEATURE_EXTENSIONS
+ ::dp_misc::PersistentMap m_map;
+#endif
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_commandenvironments.cxx b/desktop/source/deployment/manager/dp_commandenvironments.cxx
new file mode 100644
index 0000000000..0a25e042f0
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_commandenvironments.cxx
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/deployment/VersionException.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/InstallException.hpp>
+#include <com/sun/star/deployment/DependencyException.hpp>
+#include <com/sun/star/deployment/PlatformException.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <utility>
+#include "dp_commandenvironments.hxx"
+#include <osl/diagnose.h>
+
+namespace deployment = com::sun::star::deployment;
+namespace task = com::sun::star::task;
+namespace ucb = com::sun::star::ucb;
+namespace uno = com::sun::star::uno;
+
+using ::com::sun::star::uno::Reference;
+
+namespace dp_manager {
+
+BaseCommandEnv::BaseCommandEnv()
+{
+}
+
+BaseCommandEnv::BaseCommandEnv(
+ Reference< task::XInteractionHandler> const & handler)
+ : m_forwardHandler(handler)
+{
+}
+
+BaseCommandEnv::~BaseCommandEnv()
+{
+}
+// XCommandEnvironment
+
+Reference<task::XInteractionHandler> BaseCommandEnv::getInteractionHandler()
+{
+ return this;
+}
+
+
+Reference<ucb::XProgressHandler> BaseCommandEnv::getProgressHandler()
+{
+ return this;
+}
+
+void BaseCommandEnv::handle(
+ Reference< task::XInteractionRequest> const & /*xRequest*/ )
+{
+}
+
+void BaseCommandEnv::handle_(bool approve,
+ Reference< task::XInteractionRequest> const & xRequest )
+{
+ if (!approve)
+ {
+ //not handled so far -> forwarding
+ if (m_forwardHandler.is())
+ m_forwardHandler->handle(xRequest);
+ else
+ return; //cannot handle
+ }
+ else
+ {
+ // select:
+ uno::Sequence< Reference< task::XInteractionContinuation > > conts(
+ xRequest->getContinuations() );
+ Reference< task::XInteractionContinuation > const * pConts =
+ conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if (approve) {
+ Reference< task::XInteractionApprove > xInteractionApprove(
+ pConts[ pos ], uno::UNO_QUERY );
+ if (xInteractionApprove.is()) {
+ xInteractionApprove->select();
+ // don't query again for ongoing continuations:
+ approve = false;
+ }
+ }
+ }
+ }
+
+}
+
+// XProgressHandler
+void BaseCommandEnv::push( uno::Any const & /*Status*/ )
+{
+}
+
+void BaseCommandEnv::update( uno::Any const & /*Status */)
+{
+}
+
+void BaseCommandEnv::pop()
+{
+}
+
+
+TmpRepositoryCommandEnv::TmpRepositoryCommandEnv()
+{
+}
+
+TmpRepositoryCommandEnv::TmpRepositoryCommandEnv(
+ css::uno::Reference< css::task::XInteractionHandler> const & handler):
+ BaseCommandEnv(handler)
+{
+}
+// XInteractionHandler
+void TmpRepositoryCommandEnv::handle(
+ Reference< task::XInteractionRequest> const & xRequest )
+{
+ uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
+
+ deployment::VersionException verExc;
+ deployment::LicenseException licExc;
+ deployment::InstallException instExc;
+
+ bool approve = false;
+
+ if ((request >>= verExc)
+ || (request >>= licExc)
+ || (request >>= instExc))
+ {
+ approve = true;
+ }
+
+ handle_(approve, xRequest);
+}
+
+
+LicenseCommandEnv::LicenseCommandEnv(
+ css::uno::Reference< css::task::XInteractionHandler> const & handler,
+ bool bSuppressLicense,
+ OUString repository):
+ BaseCommandEnv(handler), m_repository(std::move(repository)),
+ m_bSuppressLicense(bSuppressLicense)
+{
+}
+// XInteractionHandler
+void LicenseCommandEnv::handle(
+ Reference< task::XInteractionRequest> const & xRequest )
+{
+ uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
+
+ deployment::LicenseException licExc;
+
+ bool approve = false;
+
+ if (request >>= licExc)
+ {
+ if (m_bSuppressLicense
+ || m_repository == "bundled"
+ || licExc.AcceptBy == "admin")
+ {
+ //always approve in bundled case, because we do not support
+ //showing licenses anyway.
+ //The "admin" already accepted the license when installing the
+ // shared extension
+ approve = true;
+ }
+ }
+
+ handle_(approve, xRequest);
+}
+
+
+NoLicenseCommandEnv::NoLicenseCommandEnv(
+ css::uno::Reference< css::task::XInteractionHandler> const & handler):
+ BaseCommandEnv(handler)
+{
+}
+// XInteractionHandler
+void NoLicenseCommandEnv::handle(
+ Reference< task::XInteractionRequest> const & xRequest )
+{
+ uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
+
+ deployment::LicenseException licExc;
+
+ bool approve = false;
+
+ if (request >>= licExc)
+ {
+ approve = true;
+ }
+ handle_(approve, xRequest);
+}
+
+SilentCheckPrerequisitesCommandEnv::SilentCheckPrerequisitesCommandEnv()
+{
+}
+
+void SilentCheckPrerequisitesCommandEnv::handle(
+ Reference< task::XInteractionRequest> const & xRequest )
+{
+ uno::Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
+
+ deployment::LicenseException licExc;
+ deployment::PlatformException platformExc;
+ deployment::DependencyException depExc;
+
+ if (request >>= licExc)
+ {
+ handle_(true, xRequest); // approve = true
+ }
+ else if ((request >>= platformExc)
+ || (request >>= depExc))
+ {
+ m_Exception = request;
+ }
+ else
+ {
+ m_UnknownException = request;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_commandenvironments.hxx b/desktop/source/deployment/manager/dp_commandenvironments.hxx
new file mode 100644
index 0000000000..6533d45b4f
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_commandenvironments.hxx
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/task/XInteractionRequest.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+
+namespace dp_manager {
+
+/**
+ This command environment is to be used when an extension is temporarily
+ stored in the "tmp" repository. It prevents all kind of user interaction.
+ */
+class BaseCommandEnv
+ : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
+ css::task::XInteractionHandler,
+ css::ucb::XProgressHandler >
+{
+ css::uno::Reference< css::task::XInteractionHandler> m_forwardHandler;
+protected:
+ void handle_(bool approve,
+ css::uno::Reference< css::task::XInteractionRequest> const & xRequest );
+public:
+ virtual ~BaseCommandEnv() override;
+ BaseCommandEnv();
+ explicit BaseCommandEnv(
+ css::uno::Reference< css::task::XInteractionHandler> const & handler);
+
+ // XCommandEnvironment
+ virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual css::uno::Reference<css::ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL update( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+class TmpRepositoryCommandEnv : public BaseCommandEnv
+{
+public:
+ TmpRepositoryCommandEnv();
+ explicit TmpRepositoryCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler);
+
+// XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+};
+
+/** this class is for use in XPackageManager::synchronize.
+
+ It handles particular license cases.
+ */
+class LicenseCommandEnv : public BaseCommandEnv
+{
+private:
+ OUString m_repository;
+ bool m_bSuppressLicense;
+public:
+ LicenseCommandEnv(
+ css::uno::Reference< css::task::XInteractionHandler> const & handler,
+ bool bSuppressLicense,
+ OUString repository);
+
+// XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+};
+
+/** this class is for use in XPackageManager::checkPrerequisites
+
+ It always prohibits a license interaction
+ */
+class NoLicenseCommandEnv : public BaseCommandEnv
+{
+
+public:
+ explicit NoLicenseCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler);
+
+// XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+};
+
+/* For use in XExtensionManager::addExtension in the call to
+ XPackage::checkPrerequisites
+ It prevents all user interactions. The license is always accepted.
+ It remembers if there was a platform or a dependency exception in
+ the member m_bException. if there was any other exception then m_bUnknownException
+ is set.
+
+ */
+class SilentCheckPrerequisitesCommandEnv : public BaseCommandEnv
+{
+public:
+ SilentCheckPrerequisitesCommandEnv();
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+
+ // Set to true if a PlatformException or a DependencyException were handled.
+ css::uno::Any m_Exception;
+ // Set to true if an unknown exception was handled.
+ css::uno::Any m_UnknownException;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_extensionmanager.cxx b/desktop/source/deployment/manager/dp_extensionmanager.cxx
new file mode 100644
index 0000000000..f393a30c94
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_extensionmanager.cxx
@@ -0,0 +1,1432 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <rtl/bootstrap.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/XExtensionManager.hpp>
+#include <com/sun/star/deployment/thePackageManagerFactory.hpp>
+#include <com/sun/star/deployment/XPackageManager.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/deployment/InstallException.hpp>
+#include <com/sun/star/deployment/VersionException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/Optional.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/beans/Ambiguous.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/util/XModifyBroadcaster.hpp>
+#include <comphelper/sequence.hxx>
+#include <utility>
+#include <xmlscript/xml_helper.hxx>
+#include <osl/diagnose.h>
+#include <dp_interact.h>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <dp_identifier.hxx>
+#include <dp_descriptioninfoset.hxx>
+#include "dp_extensionmanager.hxx"
+#include "dp_commandenvironments.hxx"
+#include "dp_properties.hxx"
+
+#include <vector>
+#include <algorithm>
+#include <set>
+#include <string_view>
+
+namespace lang = com::sun::star::lang;
+namespace task = com::sun::star::task;
+namespace ucb = com::sun::star::ucb;
+namespace uno = com::sun::star::uno;
+namespace beans = com::sun::star::beans;
+namespace util = com::sun::star::util;
+
+using ::com::sun::star::uno::Reference;
+
+namespace {
+
+struct CompIdentifiers
+{
+ bool operator() (std::vector<Reference<css::deployment::XPackage> > const & a,
+ std::vector<Reference<css::deployment::XPackage> > const & b)
+ {
+ return getName(a).compareTo(getName(b)) < 0;
+ }
+
+ static OUString getName(std::vector<Reference<css::deployment::XPackage> > const & a);
+};
+
+OUString CompIdentifiers::getName(std::vector<Reference<css::deployment::XPackage> > const & a)
+{
+ OSL_ASSERT(a.size() == 3);
+ //get the first non-null reference
+ Reference<css::deployment::XPackage> extension;
+ for (auto const& elem : a)
+ {
+ if (elem.is())
+ {
+ extension = elem;
+ break;
+ }
+ }
+ OSL_ASSERT(extension.is());
+ return extension->getDisplayName();
+}
+
+void writeLastModified(OUString & url, Reference<ucb::XCommandEnvironment> const & xCmdEnv, Reference< uno::XComponentContext > const & xContext)
+{
+ //Write the lastmodified file
+ try {
+ ::rtl::Bootstrap::expandMacros(url);
+ ::ucbhelper::Content ucbStamp(url, xCmdEnv, xContext);
+ dp_misc::erase_path( url, xCmdEnv );
+ OString stamp("1"_ostr );
+ Reference<css::io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(stamp.getStr()),
+ stamp.getLength() ) );
+ ucbStamp.writeStream( xData, true /* replace existing */ );
+ }
+ catch(...)
+ {
+ uno::Any exc(::cppu::getCaughtException());
+ throw css::deployment::DeploymentException("Failed to update" + url, nullptr, exc);
+ }
+}
+
+class ExtensionRemoveGuard
+{
+ css::uno::Reference<css::deployment::XPackage> m_extension;
+ css::uno::Reference<css::deployment::XPackageManager> m_xPackageManager;
+
+public:
+ ExtensionRemoveGuard(){};
+ ExtensionRemoveGuard(
+ css::uno::Reference<css::deployment::XPackage> extension,
+ css::uno::Reference<css::deployment::XPackageManager> xPackageManager):
+ m_extension(std::move(extension)), m_xPackageManager(std::move(xPackageManager)) {}
+ ~ExtensionRemoveGuard();
+
+ void set(css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager) {
+ m_extension = extension;
+ m_xPackageManager = xPackageManager;
+ }
+};
+
+ExtensionRemoveGuard::~ExtensionRemoveGuard()
+{
+ try {
+ OSL_ASSERT(!(m_extension.is() && !m_xPackageManager.is()));
+ if (m_xPackageManager.is() && m_extension.is())
+ m_xPackageManager->removePackage(
+ dp_misc::getIdentifier(m_extension), OUString(),
+ css::uno::Reference<css::task::XAbortChannel>(),
+ css::uno::Reference<css::ucb::XCommandEnvironment>());
+ } catch (...) {
+ OSL_ASSERT(false);
+ }
+}
+
+}
+
+namespace dp_manager {
+
+//ToDo: bundled extension
+ExtensionManager::ExtensionManager( Reference< uno::XComponentContext > const& xContext) :
+ ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo >(m_aMutex)
+ , m_xContext(xContext)
+{
+ m_xPackageManagerFactory = css::deployment::thePackageManagerFactory::get(m_xContext);
+ OSL_ASSERT(m_xPackageManagerFactory.is());
+
+ m_repositoryNames.emplace_back("user");
+ m_repositoryNames.emplace_back("shared");
+ m_repositoryNames.emplace_back("bundled");
+}
+
+ExtensionManager::~ExtensionManager()
+{
+}
+
+// XServiceInfo
+OUString ExtensionManager::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.ExtensionManager";
+}
+
+sal_Bool ExtensionManager::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > ExtensionManager::getSupportedServiceNames()
+{
+ // a private one:
+ return { "com.sun.star.comp.deployment.ExtensionManager" };
+}
+
+Reference<css::deployment::XPackageManager> ExtensionManager::getUserRepository()
+{
+ return m_xPackageManagerFactory->getPackageManager("user");
+}
+Reference<css::deployment::XPackageManager> ExtensionManager::getSharedRepository()
+{
+ return m_xPackageManagerFactory->getPackageManager("shared");
+}
+Reference<css::deployment::XPackageManager> ExtensionManager::getBundledRepository()
+{
+ return m_xPackageManagerFactory->getPackageManager("bundled");
+}
+Reference<css::deployment::XPackageManager> ExtensionManager::getTmpRepository()
+{
+ return m_xPackageManagerFactory->getPackageManager("tmp");
+}
+Reference<css::deployment::XPackageManager> ExtensionManager::getBakRepository()
+{
+ return m_xPackageManagerFactory->getPackageManager("bak");
+}
+
+Reference<task::XAbortChannel> ExtensionManager::createAbortChannel()
+{
+ return new dp_misc::AbortChannel;
+}
+
+css::uno::Reference<css::deployment::XPackageManager>
+ExtensionManager::getPackageManager(std::u16string_view repository)
+{
+ Reference<css::deployment::XPackageManager> xPackageManager;
+ if (repository == u"user")
+ xPackageManager = getUserRepository();
+ else if (repository == u"shared")
+ xPackageManager = getSharedRepository();
+ else if (repository == u"bundled")
+ xPackageManager = getBundledRepository();
+ else if (repository == u"tmp")
+ xPackageManager = getTmpRepository();
+ else if (repository == u"bak")
+ xPackageManager = getBakRepository();
+ else
+ throw lang::IllegalArgumentException(
+ "No valid repository name provided.",
+ static_cast<cppu::OWeakObject*>(this), 0);
+ return xPackageManager;
+}
+
+/*
+ Enters the XPackage objects into a map. They must be all from the
+ same repository. The value type of the map is a vector, where each vector
+ represents an extension with a particular identifier. The first member
+ represents the user extension, the second the shared extension and the
+ third the bundled extension.
+ */
+void ExtensionManager::addExtensionsToMap(
+ id2extensions & mapExt,
+ uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt,
+ std::u16string_view repository)
+{
+ //Determine the index in the vector where these extensions are to be
+ //added.
+ int index = 0;
+ for (auto const& repositoryName : m_repositoryNames)
+ {
+ if (repositoryName == repository)
+ break;
+ ++index;
+ }
+
+ for (const Reference<css::deployment::XPackage>& xExtension : seqExt)
+ {
+ OUString id = dp_misc::getIdentifier(xExtension);
+ id2extensions::iterator ivec = mapExt.find(id);
+ if (ivec == mapExt.end())
+ {
+ std::vector<Reference<css::deployment::XPackage> > vec(3);
+ vec[index] = xExtension;
+ mapExt[id] = vec;
+ }
+ else
+ {
+ ivec->second[index] = xExtension;
+ }
+ }
+}
+
+/*
+ returns a list containing extensions with the same identifier from
+ all repositories (user, shared, bundled). If one repository does not
+ have this extension, then the list contains an empty Reference. The list
+ is ordered according to the priority of the repositories:
+ 1. user
+ 2. shared
+ 3. bundled
+
+ The number of elements is always three, unless the number of repository
+ changes.
+ */
+std::vector<Reference<css::deployment::XPackage> >
+ ExtensionManager::getExtensionsWithSameId(
+ OUString const & identifier, OUString const & fileName)
+
+{
+ std::vector<Reference<css::deployment::XPackage> > extensionList;
+ Reference<css::deployment::XPackageManager> lRepos[] = {
+ getUserRepository(), getSharedRepository(), getBundledRepository() };
+ for (std::size_t i(0); i != std::size(lRepos); ++i)
+ {
+ Reference<css::deployment::XPackage> xPackage;
+ try
+ {
+ xPackage = lRepos[i]->getDeployedPackage(
+ identifier, fileName, Reference<ucb::XCommandEnvironment>());
+ }
+ catch(const lang::IllegalArgumentException &)
+ {
+ // thrown if the extension does not exist in this repository
+ }
+ extensionList.push_back(xPackage);
+ }
+ OSL_ASSERT(extensionList.size() == 3);
+ return extensionList;
+}
+
+uno::Sequence<Reference<css::deployment::XPackage> >
+ExtensionManager::getExtensionsWithSameIdentifier(
+ OUString const & identifier,
+ OUString const & fileName,
+ Reference< ucb::XCommandEnvironment> const & /*xCmdEnv*/ )
+{
+ try
+ {
+ std::vector<Reference<css::deployment::XPackage> > listExtensions =
+ getExtensionsWithSameId(identifier, fileName);
+ bool bHasExtension = false;
+
+ //throw an IllegalArgumentException if there is no extension at all.
+ for (auto const& extension : listExtensions)
+ bHasExtension |= extension.is();
+ if (!bHasExtension)
+ throw lang::IllegalArgumentException(
+ "Could not find extension: " + identifier + ", " + fileName,
+ static_cast<cppu::OWeakObject*>(this), -1);
+
+ return comphelper::containerToSequence(listExtensions);
+ }
+ catch ( const css::deployment::DeploymentException & )
+ {
+ throw;
+ }
+ catch ( const ucb::CommandFailedException & )
+ {
+ throw;
+ }
+ catch (css::uno::RuntimeException &)
+ {
+ throw;
+ }
+ catch (...)
+ {
+ uno::Any exc = ::cppu::getCaughtException();
+ throw css::deployment::DeploymentException(
+ "Extension Manager: exception during getExtensionsWithSameIdentifier",
+ static_cast<OWeakObject*>(this), exc);
+ }
+}
+
+bool ExtensionManager::isUserDisabled(
+ OUString const & identifier, OUString const & fileName)
+{
+ std::vector<Reference<css::deployment::XPackage> > listExtensions;
+
+ try {
+ listExtensions = getExtensionsWithSameId(identifier, fileName);
+ } catch ( const lang::IllegalArgumentException & ) {
+ }
+ OSL_ASSERT(listExtensions.size() == 3);
+
+ return isUserDisabled( ::comphelper::containerToSequence(listExtensions) );
+}
+
+bool ExtensionManager::isUserDisabled(
+ uno::Sequence<Reference<css::deployment::XPackage> > const & seqExtSameId)
+{
+ OSL_ASSERT(seqExtSameId.getLength() == 3);
+ Reference<css::deployment::XPackage> const & userExtension = seqExtSameId[0];
+ if (userExtension.is())
+ {
+ beans::Optional<beans::Ambiguous<sal_Bool> > reg =
+ userExtension->isRegistered(Reference<task::XAbortChannel>(),
+ Reference<ucb::XCommandEnvironment>());
+ //If the value is ambiguous, then we assume that the extension
+ //is enabled, but something went wrong during enabling. We do not
+ //automatically disable user extensions.
+ if (reg.IsPresent &&
+ ! reg.Value.IsAmbiguous && ! reg.Value.Value)
+ return true;
+ }
+ return false;
+}
+
+/*
+ This method determines the active extension (XPackage.registerPackage) with a
+ particular identifier.
+
+ The parameter bUserDisabled determines if the user extension is disabled.
+
+ When the user repository contains an extension with the given identifier and
+ it is not disabled by the user, then it is always registered. Otherwise an
+ extension is only registered when there is no registered extension in one of
+ the repositories with a higher priority. That is, if the extension is from
+ the shared repository and an active extension with the same identifier is in
+ the user repository, then the extension is not registered. Similarly a
+ bundled extension is not registered if there is an active extension with the
+ same identifier in the shared or user repository.
+*/
+void ExtensionManager::activateExtension(
+ OUString const & identifier, OUString const & fileName,
+ bool bUserDisabled,
+ bool bStartup,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ std::vector<Reference<css::deployment::XPackage> > listExtensions;
+ try {
+ listExtensions = getExtensionsWithSameId(identifier, fileName);
+ } catch (const lang::IllegalArgumentException &) {
+ }
+ OSL_ASSERT(listExtensions.size() == 3);
+
+ activateExtension(
+ ::comphelper::containerToSequence(listExtensions),
+ bUserDisabled, bStartup, xAbortChannel, xCmdEnv);
+
+ fireModified();
+}
+
+void ExtensionManager::activateExtension(
+ uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt,
+ bool bUserDisabled,
+ bool bStartup,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ bool bActive = false;
+ sal_Int32 len = seqExt.getLength();
+ for (sal_Int32 i = 0; i < len; i++)
+ {
+ Reference<css::deployment::XPackage> const & aExt = seqExt[i];
+ if (aExt.is())
+ {
+ //get the registration value of the current iteration
+ beans::Optional<beans::Ambiguous<sal_Bool> > optReg =
+ aExt->isRegistered(xAbortChannel, xCmdEnv);
+ //If nothing can be registered then break
+ if (!optReg.IsPresent)
+ break;
+
+ //Check if this is a disabled user extension,
+ if (i == 0 && bUserDisabled)
+ {
+ aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv);
+ continue;
+ }
+
+ //If we have already determined an active extension then we must
+ //make sure to unregister all extensions with the same id in
+ //repositories with a lower priority
+ if (bActive)
+ {
+ aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv);
+ }
+ else
+ {
+ //This is the first extension in the ordered list, which becomes
+ //the active extension
+ bActive = true;
+ //Register if not already done.
+ //reregister if the value is ambiguous, which indicates that
+ //something went wrong during last registration.
+ aExt->registerPackage(bStartup, xAbortChannel, xCmdEnv);
+ }
+ }
+ }
+}
+
+Reference<css::deployment::XPackage> ExtensionManager::backupExtension(
+ OUString const & identifier, OUString const & fileName,
+ Reference<css::deployment::XPackageManager> const & xPackageManager,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ Reference<css::deployment::XPackage> xBackup;
+ Reference<ucb::XCommandEnvironment> tmpCmdEnv(
+ new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler()));
+ Reference<css::deployment::XPackage> xOldExtension = xPackageManager->getDeployedPackage(
+ identifier, fileName, tmpCmdEnv);
+
+ if (xOldExtension.is())
+ {
+ xBackup = getTmpRepository()->addPackage(
+ xOldExtension->getURL(), uno::Sequence<beans::NamedValue>(),
+ OUString(), Reference<task::XAbortChannel>(), tmpCmdEnv);
+
+ OSL_ENSURE(xBackup.is(), "Failed to backup extension");
+ }
+ return xBackup;
+}
+
+//The supported package types are actually determined by the registry. However
+//creating a registry
+//(desktop/source/deployment/registry/dp_registry.cxx:PackageRegistryImpl) will
+//create all the backends, so that the registry can obtain from them the package
+//types. Creating the registry will also set up the registry folder containing
+//all the subfolders for the respective backends.
+//Because all repositories support the same backends, we can just delegate this
+//call to one of the repositories.
+uno::Sequence< Reference<css::deployment::XPackageTypeInfo> >
+ExtensionManager::getSupportedPackageTypes()
+{
+ return getUserRepository()->getSupportedPackageTypes();
+}
+//Do some necessary checks and user interaction. This function does not
+//acquire the extension manager mutex and that mutex must not be acquired
+//when this function is called. doChecksForAddExtension does synchronous
+//user interactions which may require acquiring the solar mutex.
+//Returns true if the extension can be installed.
+bool ExtensionManager::doChecksForAddExtension(
+ Reference<css::deployment::XPackageManager> const & xPackageMgr,
+ uno::Sequence<beans::NamedValue> const & properties,
+ css::uno::Reference<css::deployment::XPackage> const & xTmpExtension,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ Reference<css::deployment::XPackage> & out_existingExtension )
+{
+ try
+ {
+ Reference<css::deployment::XPackage> xOldExtension;
+ const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension);
+ const OUString sFileName = xTmpExtension->getName();
+ const OUString sDisplayName = xTmpExtension->getDisplayName();
+ const OUString sVersion = xTmpExtension->getVersion();
+
+ try
+ {
+ xOldExtension = xPackageMgr->getDeployedPackage(
+ sIdentifier, sFileName, xCmdEnv);
+ out_existingExtension = xOldExtension;
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ }
+ bool bCanInstall = false;
+
+ //This part is not guarded against other threads removing, adding, disabling ...
+ //etc. the same extension.
+ //checkInstall is safe because it notifies the user if the extension is not yet
+ //installed in the same repository. Because addExtension has its own guard
+ //(m_addMutex), another thread cannot add the extension in the meantime.
+ //checkUpdate is called if the same extension exists in the same
+ //repository. The user is asked if they want to replace it. Another
+ //thread
+ //could already remove the extension. So asking the user was not
+ //necessary. No harm is done. The other thread may also ask the user
+ //if he wants to remove the extension. This depends on the
+ //XCommandEnvironment which it passes to removeExtension.
+ if (xOldExtension.is())
+ {
+ //throws a CommandFailedException if the user cancels
+ //the action.
+ checkUpdate(sVersion, sDisplayName,xOldExtension, xCmdEnv);
+ }
+ else
+ {
+ //throws a CommandFailedException if the user cancels
+ //the action.
+ checkInstall(sDisplayName, xCmdEnv);
+ }
+ //Prevent showing the license if requested.
+ Reference<ucb::XCommandEnvironment> _xCmdEnv(xCmdEnv);
+ ExtensionProperties props(std::u16string_view(), properties, Reference<ucb::XCommandEnvironment>(), m_xContext);
+
+ dp_misc::DescriptionInfoset info(dp_misc::getDescriptionInfoset(xTmpExtension->getURL()));
+ const ::std::optional<dp_misc::SimpleLicenseAttributes> licenseAttributes =
+ info.getSimpleLicenseAttributes();
+
+ if (licenseAttributes && licenseAttributes->suppressIfRequired
+ && props.isSuppressedLicense())
+ _xCmdEnv.set(new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler()));
+
+ bCanInstall = xTmpExtension->checkPrerequisites(
+ xAbortChannel, _xCmdEnv, xOldExtension.is() || props.isExtensionUpdate()) == 0;
+
+ return bCanInstall;
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ uno::Any excOccurred = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception in doChecksForAddExtension",
+ static_cast<OWeakObject*>(this), excOccurred);
+ throw exc;
+ } catch (...) {
+ throw uno::RuntimeException(
+ "Extension Manager: unexpected exception in doChecksForAddExtension",
+ static_cast<OWeakObject*>(this));
+ }
+}
+
+// Only add to shared and user repository
+Reference<css::deployment::XPackage> ExtensionManager::addExtension(
+ OUString const & url, uno::Sequence<beans::NamedValue> const & properties,
+ OUString const & repository,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ Reference<css::deployment::XPackage> xNewExtension;
+ //Determine the repository to use
+ Reference<css::deployment::XPackageManager> xPackageManager;
+ if (repository == "user")
+ xPackageManager = getUserRepository();
+ else if (repository == "shared")
+ xPackageManager = getSharedRepository();
+ else
+ throw lang::IllegalArgumentException(
+ "No valid repository name provided.",
+ static_cast<cppu::OWeakObject*>(this), 0);
+ //We must make sure that the xTmpExtension is not create twice, because this
+ //would remove the first one.
+ std::unique_lock addGuard(m_addMutex);
+
+ Reference<css::deployment::XPackageManager> xTmpRepository(getTmpRepository());
+ // make sure xTmpRepository is alive as long as xTmpExtension is; as
+ // the "tmp" manager is only held weakly by m_xPackageManagerFactory, it
+ // could otherwise be disposed early, which would in turn dispose
+ // xTmpExtension's PackageRegistryBackend behind its back
+ Reference<css::deployment::XPackage> xTmpExtension(
+ xTmpRepository->addPackage(
+ url, uno::Sequence<beans::NamedValue>(), OUString(), xAbortChannel,
+ new TmpRepositoryCommandEnv()));
+ if (!xTmpExtension.is()) {
+ throw css::deployment::DeploymentException(
+ ("Extension Manager: Failed to create temporary XPackage for url: "
+ + url),
+ static_cast<OWeakObject*>(this), uno::Any());
+ }
+
+ //Make sure the extension is removed from the tmp repository in case
+ //of an exception
+ ExtensionRemoveGuard tmpExtensionRemoveGuard(xTmpExtension, getTmpRepository());
+ ExtensionRemoveGuard bakExtensionRemoveGuard;
+ const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension);
+ const OUString sFileName = xTmpExtension->getName();
+ Reference<css::deployment::XPackage> xOldExtension;
+ Reference<css::deployment::XPackage> xExtensionBackup;
+
+ uno::Any excOccurred2;
+ bool bCanInstall = doChecksForAddExtension(
+ xPackageManager,
+ properties,
+ xTmpExtension,
+ xAbortChannel,
+ xCmdEnv,
+ xOldExtension );
+
+ {
+ bool bUserDisabled = false;
+ // In this guarded section (getMutex) we must not use the argument xCmdEnv
+ // because it may bring up dialogs (XInteractionHandler::handle) this
+ // may potentially deadlock. See issue
+ // http://qa.openoffice.org/issues/show_bug.cgi?id=114933
+ // By not providing xCmdEnv the underlying APIs will throw an exception if
+ // the XInteractionRequest cannot be handled.
+ ::osl::MutexGuard guard(m_aMutex);
+
+ if (bCanInstall)
+ {
+ try
+ {
+ bUserDisabled = isUserDisabled(sIdentifier, sFileName);
+ if (xOldExtension.is())
+ {
+ try
+ {
+ xOldExtension->revokePackage(
+ false, xAbortChannel, Reference<ucb::XCommandEnvironment>());
+ //save the old user extension in case the user aborts
+ xExtensionBackup = getBakRepository()->importExtension(
+ xOldExtension, Reference<task::XAbortChannel>(),
+ Reference<ucb::XCommandEnvironment>());
+ bakExtensionRemoveGuard.set(xExtensionBackup, getBakRepository());
+ }
+ catch (const lang::DisposedException &)
+ {
+ //Another thread might have removed the extension meanwhile
+ }
+ }
+ //check again dependencies but prevent user interaction,
+ //We can disregard the license, because the user must have already
+ //accepted it, when we called checkPrerequisites the first time
+ rtl::Reference<SilentCheckPrerequisitesCommandEnv> pSilentCommandEnv =
+ new SilentCheckPrerequisitesCommandEnv();
+
+ sal_Int32 failedPrereq = xTmpExtension->checkPrerequisites(
+ xAbortChannel, pSilentCommandEnv, true);
+ if (failedPrereq == 0)
+ {
+ xNewExtension = xPackageManager->addPackage(
+ url, properties, OUString(), xAbortChannel,
+ Reference<ucb::XCommandEnvironment>());
+ //If we add a user extension and there is already one which was
+ //disabled by a user, then the newly installed one is enabled. If we
+ //add to another repository then the user extension remains
+ //disabled.
+ bool bUserDisabled2 = bUserDisabled;
+ if (repository == "user")
+ bUserDisabled2 = false;
+
+ // pass the two values via variables to workaround gcc-4.3.4 specific bug (bnc#655912)
+ OUString sNewExtensionIdentifier = dp_misc::getIdentifier(xNewExtension);
+ OUString sNewExtensionFileName = xNewExtension->getName();
+
+ activateExtension(
+ sNewExtensionIdentifier, sNewExtensionFileName,
+ bUserDisabled2, false, xAbortChannel,
+ Reference<ucb::XCommandEnvironment>());
+
+ // if reached this section,
+ // this means that either the licensedialog.ui didn't popup,
+ // or user accepted the license agreement. otherwise
+ // no need to call fireModified() because user declined
+ // the license agreement therefore no change made.
+ try
+ {
+ fireModified();
+
+ }catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ uno::Any excOccurred = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: Exception on fireModified() "
+ "in the scope of 'if (failedPrereq == 0)'",
+ static_cast<OWeakObject*>(this), excOccurred);
+ throw exc;
+ } catch (...) {
+ throw uno::RuntimeException(
+ "Extension Manager: RuntimeException on fireModified() "
+ "in the scope of 'if (failedPrereq == 0)'",
+ static_cast<OWeakObject*>(this));
+ }
+ }
+ else
+ {
+ if (pSilentCommandEnv->m_Exception.hasValue())
+ ::cppu::throwException(pSilentCommandEnv->m_Exception);
+ else if ( pSilentCommandEnv->m_UnknownException.hasValue())
+ ::cppu::throwException(pSilentCommandEnv->m_UnknownException);
+ else
+ throw css::deployment::DeploymentException (
+ "Extension Manager: exception during addExtension, ckeckPrerequisites failed",
+ static_cast<OWeakObject*>(this), uno::Any());
+ }
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ excOccurred2 = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandFailedException & ) {
+ excOccurred2 = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandAbortedException & ) {
+ excOccurred2 = ::cppu::getCaughtException();
+ } catch (const lang::IllegalArgumentException &) {
+ excOccurred2 = ::cppu::getCaughtException();
+ } catch (const uno::RuntimeException &) {
+ excOccurred2 = ::cppu::getCaughtException();
+ } catch (...) {
+ excOccurred2 = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception during addExtension, url: "
+ + url, static_cast<OWeakObject*>(this), excOccurred2);
+ excOccurred2 <<= exc;
+ }
+ }
+
+ if (excOccurred2.hasValue())
+ {
+ //It does not matter what exception is thrown. We try to
+ //recover the original status.
+ //If the user aborted installation then a ucb::CommandAbortedException
+ //is thrown.
+ //Use a private AbortChannel so the user cannot interrupt.
+ try
+ {
+ if (xExtensionBackup.is())
+ {
+ xPackageManager->importExtension(
+ xExtensionBackup, Reference<task::XAbortChannel>(),
+ Reference<ucb::XCommandEnvironment>());
+ }
+ activateExtension(
+ sIdentifier, sFileName, bUserDisabled, false,
+ Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>());
+ }
+ catch (...)
+ {
+ }
+ ::cppu::throwException(excOccurred2);
+ }
+ } // leaving the guarded section (getMutex())
+
+ return xNewExtension;
+}
+
+void ExtensionManager::removeExtension(
+ OUString const & identifier, OUString const & fileName,
+ OUString const & repository,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ uno::Any excOccurred1;
+ Reference<css::deployment::XPackage> xExtensionBackup;
+ Reference<css::deployment::XPackageManager> xPackageManager;
+ bool bUserDisabled = false;
+ ::osl::MutexGuard guard(m_aMutex);
+ try
+ {
+//Determine the repository to use
+ if (repository == "user")
+ xPackageManager = getUserRepository();
+ else if (repository == "shared")
+ xPackageManager = getSharedRepository();
+ else
+ throw lang::IllegalArgumentException(
+ "No valid repository name provided.",
+ static_cast<cppu::OWeakObject*>(this), 0);
+
+ bUserDisabled = isUserDisabled(identifier, fileName);
+ //Backup the extension, in case the user cancels the action
+ xExtensionBackup = backupExtension(
+ identifier, fileName, xPackageManager, xCmdEnv);
+
+ //revoke the extension if it is active
+ Reference<css::deployment::XPackage> xOldExtension =
+ xPackageManager->getDeployedPackage(
+ identifier, fileName, xCmdEnv);
+ xOldExtension->revokePackage(false, xAbortChannel, xCmdEnv);
+
+ xPackageManager->removePackage(
+ identifier, fileName, xAbortChannel, xCmdEnv);
+ activateExtension(identifier, fileName, bUserDisabled, false,
+ xAbortChannel, xCmdEnv);
+ fireModified();
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ excOccurred1 = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandFailedException & ) {
+ excOccurred1 = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandAbortedException & ) {
+ excOccurred1 = ::cppu::getCaughtException();
+ } catch (const lang::IllegalArgumentException &) {
+ excOccurred1 = ::cppu::getCaughtException();
+ } catch (const uno::RuntimeException &) {
+ excOccurred1 = ::cppu::getCaughtException();
+ } catch (...) {
+ excOccurred1 = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception during removeExtension",
+ static_cast<OWeakObject*>(this), excOccurred1);
+ excOccurred1 <<= exc;
+ }
+
+ if (excOccurred1.hasValue())
+ {
+ //User aborted installation, restore the previous situation.
+ //Use a private AbortChannel so the user cannot interrupt.
+ try
+ {
+ Reference<ucb::XCommandEnvironment> tmpCmdEnv(
+ new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler()));
+ if (xExtensionBackup.is())
+ {
+ xPackageManager->importExtension(
+ xExtensionBackup, Reference<task::XAbortChannel>(),
+ tmpCmdEnv);
+ activateExtension(
+ identifier, fileName, bUserDisabled, false,
+ Reference<task::XAbortChannel>(),
+ tmpCmdEnv);
+
+ getTmpRepository()->removePackage(
+ dp_misc::getIdentifier(xExtensionBackup),
+ xExtensionBackup->getName(), xAbortChannel, xCmdEnv);
+ fireModified();
+ }
+ }
+ catch (...)
+ {
+ }
+ ::cppu::throwException(excOccurred1);
+ }
+
+ if (xExtensionBackup.is())
+ getTmpRepository()->removePackage(
+ dp_misc::getIdentifier(xExtensionBackup),
+ xExtensionBackup->getName(), xAbortChannel, xCmdEnv);
+}
+
+// Only enable extensions from shared and user repository
+void ExtensionManager::enableExtension(
+ Reference<css::deployment::XPackage> const & extension,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ ::osl::MutexGuard guard(m_aMutex);
+ bool bUserDisabled = false;
+ uno::Any excOccurred;
+ try
+ {
+ if (!extension.is())
+ return;
+ OUString repository = extension->getRepositoryName();
+ if (repository != "user")
+ throw lang::IllegalArgumentException(
+ "No valid repository name provided.",
+ static_cast<cppu::OWeakObject*>(this), 0);
+
+ bUserDisabled = isUserDisabled(dp_misc::getIdentifier(extension),
+ extension->getName());
+
+ activateExtension(dp_misc::getIdentifier(extension),
+ extension->getName(), false, false,
+ xAbortChannel, xCmdEnv);
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandFailedException & ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandAbortedException & ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (const lang::IllegalArgumentException &) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (const uno::RuntimeException &) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (...) {
+ excOccurred = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception during enableExtension",
+ static_cast<OWeakObject*>(this), excOccurred);
+ excOccurred <<= exc;
+ }
+
+ if (!excOccurred.hasValue())
+ return;
+
+ try
+ {
+ activateExtension(dp_misc::getIdentifier(extension),
+ extension->getName(), bUserDisabled, false,
+ xAbortChannel, xCmdEnv);
+ }
+ catch (...)
+ {
+ }
+ ::cppu::throwException(excOccurred);
+}
+
+sal_Int32 ExtensionManager::checkPrerequisitesAndEnable(
+ Reference<css::deployment::XPackage> const & extension,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ try
+ {
+ if (!extension.is())
+ return 0;
+ ::osl::MutexGuard guard(m_aMutex);
+ sal_Int32 ret = 0;
+ Reference<css::deployment::XPackageManager> mgr =
+ getPackageManager(extension->getRepositoryName());
+ ret = mgr->checkPrerequisites(extension, xAbortChannel, xCmdEnv);
+ if (ret)
+ {
+ //There are some unfulfilled prerequisites, try to revoke
+ extension->revokePackage(false, xAbortChannel, xCmdEnv);
+ }
+ const OUString id(dp_misc::getIdentifier(extension));
+ activateExtension(id, extension->getName(),
+ isUserDisabled(id, extension->getName()), false,
+ xAbortChannel, xCmdEnv);
+ return ret;
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (...) {
+ uno::Any excOccurred = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception during disableExtension",
+ static_cast<OWeakObject*>(this), excOccurred);
+ throw exc;
+ }
+}
+
+void ExtensionManager::disableExtension(
+ Reference<css::deployment::XPackage> const & extension,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ ::osl::MutexGuard guard(m_aMutex);
+ uno::Any excOccurred;
+ bool bUserDisabled = false;
+ try
+ {
+ if (!extension.is())
+ return;
+ const OUString repository( extension->getRepositoryName());
+ if (repository != "user")
+ throw lang::IllegalArgumentException(
+ "No valid repository name provided.",
+ static_cast<cppu::OWeakObject*>(this), 0);
+
+ const OUString id(dp_misc::getIdentifier(extension));
+ bUserDisabled = isUserDisabled(id, extension->getName());
+
+ activateExtension(id, extension->getName(), true, false,
+ xAbortChannel, xCmdEnv);
+ }
+ catch ( const css::deployment::DeploymentException& ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandFailedException & ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch ( const ucb::CommandAbortedException & ) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (const lang::IllegalArgumentException &) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (const uno::RuntimeException &) {
+ excOccurred = ::cppu::getCaughtException();
+ } catch (...) {
+ excOccurred = ::cppu::getCaughtException();
+ css::deployment::DeploymentException exc(
+ "Extension Manager: exception during disableExtension",
+ static_cast<OWeakObject*>(this), excOccurred);
+ excOccurred <<= exc;
+ }
+
+ if (!excOccurred.hasValue())
+ return;
+
+ try
+ {
+ activateExtension(dp_misc::getIdentifier(extension),
+ extension->getName(), bUserDisabled, false,
+ xAbortChannel, xCmdEnv);
+ }
+ catch (...)
+ {
+ }
+ ::cppu::throwException(excOccurred);
+}
+
+uno::Sequence< Reference<css::deployment::XPackage> >
+ ExtensionManager::getDeployedExtensions(
+ OUString const & repository,
+ Reference<task::XAbortChannel> const &xAbort,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ return getPackageManager(repository)->getDeployedPackages(
+ xAbort, xCmdEnv);
+}
+
+Reference<css::deployment::XPackage>
+ ExtensionManager::getDeployedExtension(
+ OUString const & repository,
+ OUString const & identifier,
+ OUString const & filename,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ return getPackageManager(repository)->getDeployedPackage(
+ identifier, filename, xCmdEnv);
+}
+
+uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > >
+ ExtensionManager::getAllExtensions(
+ Reference<task::XAbortChannel> const & xAbort,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ try
+ {
+ id2extensions mapExt;
+
+ uno::Sequence<Reference<css::deployment::XPackage> > userExt =
+ getUserRepository()->getDeployedPackages(xAbort, xCmdEnv);
+ addExtensionsToMap(mapExt, userExt, u"user");
+ uno::Sequence<Reference<css::deployment::XPackage> > sharedExt =
+ getSharedRepository()->getDeployedPackages(xAbort, xCmdEnv);
+ addExtensionsToMap(mapExt, sharedExt, u"shared");
+ uno::Sequence<Reference<css::deployment::XPackage> > bundledExt =
+ getBundledRepository()->getDeployedPackages(xAbort, xCmdEnv);
+ addExtensionsToMap(mapExt, bundledExt, u"bundled");
+
+ // Create the tmp repository to trigger its clean up (deletion
+ // of old temporary data.)
+ getTmpRepository();
+
+ //copy the values of the map to a vector for sorting
+ std::vector< std::vector<Reference<css::deployment::XPackage> > >
+ vecExtensions;
+ for (auto const& elem : mapExt)
+ vecExtensions.push_back(elem.second);
+
+ //sort the element according to the identifier
+ std::sort(vecExtensions.begin(), vecExtensions.end(), CompIdentifiers());
+
+ sal_Int32 j = 0;
+ uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > > seqSeq(vecExtensions.size());
+ auto seqSeqRange = asNonConstRange(seqSeq);
+ for (auto const& elem : vecExtensions)
+ {
+ seqSeqRange[j++] = comphelper::containerToSequence(elem);
+ }
+ return seqSeq;
+
+ } catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (...) {
+ uno::Any exc = ::cppu::getCaughtException();
+ throw css::deployment::DeploymentException(
+ "Extension Manager: exception during enableExtension",
+ static_cast<OWeakObject*>(this), exc);
+ }
+}
+
+// Only to be called from unopkg or soffice bootstrap (with force=true in the
+// latter case):
+void ExtensionManager::reinstallDeployedExtensions(
+ sal_Bool force, OUString const & repository,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ try
+ {
+ Reference<css::deployment::XPackageManager>
+ xPackageManager = getPackageManager(repository);
+
+ std::set< OUString > disabledExts;
+ {
+ const uno::Sequence< Reference<css::deployment::XPackage> > extensions(
+ xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv));
+ for ( const Reference<css::deployment::XPackage>& package : extensions )
+ {
+ try
+ {
+ beans::Optional< beans::Ambiguous< sal_Bool > > registered(
+ package->isRegistered(xAbortChannel, xCmdEnv));
+ if (registered.IsPresent &&
+ !(registered.Value.IsAmbiguous ||
+ registered.Value.Value))
+ {
+ const OUString id = dp_misc::getIdentifier(package);
+ OSL_ASSERT(!id.isEmpty());
+ disabledExts.insert(id);
+ }
+ }
+ catch (const lang::DisposedException &)
+ {
+ }
+ }
+ }
+
+ ::osl::MutexGuard guard(m_aMutex);
+ xPackageManager->reinstallDeployedPackages(
+ force, xAbortChannel, xCmdEnv);
+ //We must sync here, otherwise we will get exceptions when extensions
+ //are removed.
+ dp_misc::syncRepositories(force, xCmdEnv);
+ const uno::Sequence< Reference<css::deployment::XPackage> > extensions(
+ xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv));
+
+ for ( const Reference<css::deployment::XPackage>& package : extensions )
+ {
+ try
+ {
+ const OUString id = dp_misc::getIdentifier(package);
+ const OUString fileName = package->getName();
+ OSL_ASSERT(!id.isEmpty());
+ activateExtension(
+ id, fileName, disabledExts.find(id) != disabledExts.end(),
+ true, xAbortChannel, xCmdEnv );
+ }
+ catch (const lang::DisposedException &)
+ {
+ }
+ }
+ } catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (...) {
+ uno::Any exc = ::cppu::getCaughtException();
+ throw css::deployment::DeploymentException(
+ "Extension Manager: exception during enableExtension",
+ static_cast<OWeakObject*>(this), exc);
+ }
+}
+
+sal_Bool ExtensionManager::synchronize(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ try
+ {
+ ::osl::MutexGuard guard(m_aMutex);
+ OUString sSynchronizingShared(StrSyncRepository());
+ sSynchronizingShared = sSynchronizingShared.replaceAll("%NAME", "shared");
+ dp_misc::ProgressLevel progressShared(xCmdEnv, sSynchronizingShared);
+ bool bModified = getSharedRepository()->synchronize(xAbortChannel, xCmdEnv);
+ progressShared.update("\n\n");
+
+ OUString sSynchronizingBundled(StrSyncRepository());
+ sSynchronizingBundled = sSynchronizingBundled.replaceAll("%NAME", "bundled");
+ dp_misc::ProgressLevel progressBundled(xCmdEnv, sSynchronizingBundled);
+ bModified |= static_cast<bool>(getBundledRepository()->synchronize(xAbortChannel, xCmdEnv));
+ progressBundled.update("\n\n");
+
+ //Always determine the active extension.
+ //TODO: Is this still necessary? (It used to be necessary for the
+ // first-start optimization: The setup created the registration data
+ // for the bundled extensions (share/prereg/bundled) which was copied to
+ // the user installation when a user started OOo for the first time
+ // after running setup. All bundled extensions were registered at that
+ // moment. However, extensions with the same identifier could be in the
+ // shared or user repository, in which case the respective bundled
+ // extensions had to be revoked.)
+ try
+ {
+ const uno::Sequence<uno::Sequence<Reference<css::deployment::XPackage> > >
+ seqSeqExt = getAllExtensions(xAbortChannel, xCmdEnv);
+ for (uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt : seqSeqExt)
+ {
+ activateExtension(seqExt, isUserDisabled(seqExt), true,
+ xAbortChannel, xCmdEnv);
+ }
+ }
+ catch (...)
+ {
+ //We catch the exception, so we can write the lastmodified file
+ //so we will no repeat this every time OOo starts.
+ OSL_FAIL("Extensions Manager: synchronize");
+ }
+ OUString lastSyncBundled("$BUNDLED_EXTENSIONS_USER/lastsynchronized");
+ writeLastModified(lastSyncBundled, xCmdEnv, m_xContext);
+ OUString lastSyncShared("$SHARED_EXTENSIONS_USER/lastsynchronized");
+ writeLastModified(lastSyncShared, xCmdEnv, m_xContext);
+ return bModified;
+ } catch ( const css::deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (...) {
+ uno::Any exc = ::cppu::getCaughtException();
+ throw css::deployment::DeploymentException(
+ "Extension Manager: exception in synchronize",
+ static_cast<OWeakObject*>(this), exc);
+ }
+}
+
+// Notify the user when a new extension is to be installed. This is only the
+// case when one uses the system integration to install an extension (double
+// clicking on .oxt file etc.)). The function must only be called if there is no
+// extension with the same identifier already deployed. Then the checkUpdate
+// function will inform the user that the extension is about to be installed In
+// case the user cancels the installation a CommandFailed exception is
+// thrown.
+void ExtensionManager::checkInstall(
+ OUString const & displayName,
+ Reference<ucb::XCommandEnvironment> const & cmdEnv)
+{
+ uno::Any request(
+ css::deployment::InstallException(
+ "Extension " + displayName +
+ " is about to be installed.",
+ static_cast<OWeakObject *>(this), displayName));
+ bool approve = false, abort = false;
+ if (! dp_misc::interactContinuation(
+ request, cppu::UnoType<task::XInteractionApprove>::get(),
+ cmdEnv, &approve, &abort ))
+ {
+ OSL_ASSERT( !approve && !abort );
+ throw css::deployment::DeploymentException(
+ DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName,
+ static_cast<OWeakObject *>(this), request );
+ }
+ if (abort || !approve)
+ throw ucb::CommandFailedException(
+ DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName,
+ static_cast<OWeakObject *>(this), request );
+}
+
+/* The function will make the user interaction in case there is an extension
+installed with the same id. This function may only be called if there is already
+an extension.
+*/
+void ExtensionManager::checkUpdate(
+ OUString const & newVersion,
+ OUString const & newDisplayName,
+ Reference<css::deployment::XPackage> const & oldExtension,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ // package already deployed, interact --force:
+ uno::Any request(
+ (css::deployment::VersionException(
+ DpResId(
+ RID_STR_PACKAGE_ALREADY_ADDED ) + newDisplayName,
+ static_cast<OWeakObject *>(this), newVersion, newDisplayName,
+ oldExtension ) ) );
+ bool replace = false, abort = false;
+ if (! dp_misc::interactContinuation(
+ request, cppu::UnoType<task::XInteractionApprove>::get(),
+ xCmdEnv, &replace, &abort )) {
+ OSL_ASSERT( !replace && !abort );
+ throw css::deployment::DeploymentException(
+ DpResId(
+ RID_STR_ERROR_WHILE_ADDING) + newDisplayName,
+ static_cast<OWeakObject *>(this), request );
+ }
+ if (abort || !replace)
+ throw ucb::CommandFailedException(
+ DpResId(
+ RID_STR_PACKAGE_ALREADY_ADDED) + newDisplayName,
+ static_cast<OWeakObject *>(this), request );
+}
+
+uno::Sequence<Reference<css::deployment::XPackage> > SAL_CALL
+ExtensionManager::getExtensionsWithUnacceptedLicenses(
+ OUString const & repository,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ Reference<css::deployment::XPackageManager>
+ xPackageManager = getPackageManager(repository);
+ ::osl::MutexGuard guard(m_aMutex);
+ return xPackageManager->getExtensionsWithUnacceptedLicenses(xCmdEnv);
+}
+
+sal_Bool ExtensionManager::isReadOnlyRepository(OUString const & repository)
+{
+ return getPackageManager(repository)->isReadOnly();
+}
+
+
+// XModifyBroadcaster
+
+void ExtensionManager::addModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+
+void ExtensionManager::removeModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+void ExtensionManager::check()
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed) {
+ throw lang::DisposedException(
+ "ExtensionManager instance has already been disposed!",
+ static_cast<OWeakObject *>(this) );
+ }
+}
+
+void ExtensionManager::fireModified()
+{
+ ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer(
+ cppu::UnoType<util::XModifyListener>::get() );
+ if (pContainer != nullptr) {
+ pContainer->forEach<util::XModifyListener>(
+ [this] (uno::Reference<util::XModifyListener> const& xListener)
+ { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); });
+ }
+}
+
+} // namespace dp_manager
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_ExtensionManager_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new dp_manager::ExtensionManager(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_extensionmanager.hxx b/desktop/source/deployment/manager/dp_extensionmanager.hxx
new file mode 100644
index 0000000000..a70f4fbd2e
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_extensionmanager.hxx
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <strings.hrc>
+#include <dp_shared.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/deployment/XExtensionManager.hpp>
+#include <com/sun/star/deployment/XPackageManager.hpp>
+#include <com/sun/star/deployment/XPackageManagerFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <mutex>
+#include <vector>
+#include <unordered_map>
+
+namespace dp_manager {
+
+typedef std::unordered_map<
+ OUString,
+ std::vector<css::uno::Reference<css::deployment::XPackage> > > id2extensions;
+
+class ExtensionManager : private cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo >
+{
+public:
+ explicit ExtensionManager( css::uno::Reference< css::uno::XComponentContext >const& xContext);
+ virtual ~ExtensionManager() override;
+
+ void check();
+ void fireModified();
+
+public:
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+// XModifyBroadcaster
+ virtual void SAL_CALL addModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+ virtual void SAL_CALL removeModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+
+//XExtensionManager
+ virtual css::uno::Sequence<
+ css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+
+ virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL
+ createAbortChannel() override;
+
+ virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addExtension(
+ OUString const & url,
+ css::uno::Sequence<css::beans::NamedValue> const & properties,
+ OUString const & repository,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL removeExtension(
+ OUString const & identifier,
+ OUString const & filename,
+ OUString const & repository,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL enableExtension(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL disableExtension(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual sal_Int32 SAL_CALL checkPrerequisitesAndEnable(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> >
+ SAL_CALL getDeployedExtensions(
+ OUString const & repository,
+ css::uno::Reference<css::task::XAbortChannel> const &,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Reference< css::deployment::XPackage>
+ SAL_CALL getDeployedExtension(
+ OUString const & repository,
+ OUString const & identifier,
+ OUString const & filename,
+ css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> >
+ SAL_CALL getExtensionsWithSameIdentifier(
+ OUString const & identifier,
+ OUString const & filename,
+ css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Sequence< css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > >
+ SAL_CALL getAllExtensions(
+ css::uno::Reference<css::task::XAbortChannel> const &,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL reinstallDeployedExtensions(
+ sal_Bool force, OUString const & repository,
+ css::uno::Reference< css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual sal_Bool SAL_CALL synchronize(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL
+ getExtensionsWithUnacceptedLicenses(
+ OUString const & repository,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override;
+
+ virtual sal_Bool SAL_CALL isReadOnlyRepository(OUString const & repository) override;
+
+private:
+
+ static OUString StrSyncRepository() { return DpResId(RID_STR_SYNCHRONIZING_REPOSITORY); }
+
+ css::uno::Reference< css::uno::XComponentContext> m_xContext;
+ css::uno::Reference<css::deployment::XPackageManagerFactory> m_xPackageManagerFactory;
+
+ //only to be used within addExtension
+ std::mutex m_addMutex;
+ /* contains the names of all repositories (except tmp) in order of there
+ priority. That is, the first element is "user" followed by "shared" and
+ then "bundled"
+ */
+ std::vector< OUString > m_repositoryNames;
+
+ css::uno::Reference<css::deployment::XPackageManager> getUserRepository();
+ css::uno::Reference<css::deployment::XPackageManager> getSharedRepository();
+ css::uno::Reference<css::deployment::XPackageManager> getBundledRepository();
+ css::uno::Reference<css::deployment::XPackageManager> getTmpRepository();
+ css::uno::Reference<css::deployment::XPackageManager> getBakRepository();
+
+ bool isUserDisabled(OUString const & identifier,
+ OUString const & filename);
+
+ static bool isUserDisabled(
+ css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExtSameId);
+
+ void activateExtension(
+ OUString const & identifier,
+ OUString const & fileName,
+ bool bUserDisabled, bool bStartup,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+
+ static void activateExtension(
+ css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt,
+ bool bUserDisabled, bool bStartup,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv );
+
+ std::vector<css::uno::Reference<css::deployment::XPackage> >
+ getExtensionsWithSameId(OUString const & identifier,
+ OUString const & fileName);
+
+ css::uno::Reference<css::deployment::XPackage> backupExtension(
+ OUString const & identifier, OUString const & fileName,
+ css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+
+ void checkInstall(
+ OUString const & displayName,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & cmdEnv);
+
+ void checkUpdate(
+ OUString const & newVersion,
+ OUString const & newDisplayName,
+ css::uno::Reference<css::deployment::XPackage> const & oldExtension,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+
+ void addExtensionsToMap(
+ id2extensions & mapExt,
+ css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt,
+ std::u16string_view repository);
+
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference<css::deployment::XPackageManager>
+ getPackageManager(std::u16string_view repository);
+
+ /// @throws css::deployment::DeploymentException
+ /// @throws css::ucb::CommandFailedException
+ /// @throws css::ucb::CommandAbortedException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ bool doChecksForAddExtension(
+ css::uno::Reference<css::deployment::XPackageManager> const & xPackageMgr,
+ css::uno::Sequence<css::beans::NamedValue> const & properties,
+ css::uno::Reference<css::deployment::XPackage> const & xTmpExtension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ css::uno::Reference<css::deployment::XPackage> & out_existingExtension );
+
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_informationprovider.cxx b/desktop/source/deployment/manager/dp_informationprovider.cxx
new file mode 100644
index 0000000000..5b6d6a92b9
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_informationprovider.cxx
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/deployment/UpdateInformationProvider.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/deployment/XPackageInformationProvider.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/XUpdateInformationProvider.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/task/XAbortChannel.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/xml/dom/XElement.hpp>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustring.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <dp_dependencies.hxx>
+#include <dp_descriptioninfoset.hxx>
+#include <dp_identifier.hxx>
+#include <dp_version.hxx>
+#include <dp_update.hxx>
+
+namespace beans = com::sun::star::beans ;
+namespace deployment = com::sun::star::deployment ;
+namespace lang = com::sun::star::lang ;
+namespace task = com::sun::star::task ;
+namespace css_ucb = com::sun::star::ucb ;
+namespace uno = com::sun::star::uno ;
+namespace xml = com::sun::star::xml ;
+
+
+namespace dp_info {
+
+namespace {
+
+class PackageInformationProvider :
+ public ::cppu::WeakImplHelper< deployment::XPackageInformationProvider, lang::XServiceInfo >
+
+{
+ public:
+ explicit PackageInformationProvider( uno::Reference< uno::XComponentContext >const& xContext);
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageInformationProvider
+ virtual OUString SAL_CALL getPackageLocation( const OUString& extensionId ) override;
+ virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL isUpdateAvailable( const OUString& extensionId ) override;
+ virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL getExtensionList() override;
+
+private:
+
+ uno::Reference< uno::XComponentContext> mxContext;
+
+ OUString getPackageLocation( const OUString& repository,
+ std::u16string_view _sExtensionId );
+
+ uno::Reference< deployment::XUpdateInformationProvider > mxUpdateInformation;
+};
+
+}
+
+PackageInformationProvider::PackageInformationProvider( uno::Reference< uno::XComponentContext > const& xContext) :
+ mxContext( xContext ),
+ mxUpdateInformation( deployment::UpdateInformationProvider::create( xContext ) )
+{
+}
+
+// XServiceInfo
+OUString PackageInformationProvider::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.PackageInformationProvider";
+}
+
+sal_Bool PackageInformationProvider::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > PackageInformationProvider::getSupportedServiceNames()
+{
+ // a private one:
+ return { "com.sun.star.comp.deployment.PackageInformationProvider" };
+}
+
+OUString PackageInformationProvider::getPackageLocation(
+ const OUString & repository,
+ std::u16string_view _rExtensionId )
+{
+ OUString aLocationURL;
+ uno::Reference<deployment::XExtensionManager> xManager =
+ deployment::ExtensionManager::get(mxContext);
+
+ if ( xManager.is() )
+ {
+ const uno::Sequence< uno::Reference< deployment::XPackage > > packages(
+ xManager->getDeployedExtensions(
+ repository,
+ uno::Reference< task::XAbortChannel >(),
+ uno::Reference< css_ucb::XCommandEnvironment > () ) );
+
+ for ( int pos = packages.getLength(); pos--; )
+ {
+ try
+ {
+ const beans::Optional< OUString > aID = packages[ pos ]->getIdentifier();
+ if ( aID.IsPresent && (aID.Value == _rExtensionId ) )
+ {
+ aLocationURL = packages[ pos ]->getURL();
+ break;
+ }
+ }
+ catch ( uno::RuntimeException & ) {}
+ }
+ }
+
+ return aLocationURL;
+}
+
+
+OUString SAL_CALL
+PackageInformationProvider::getPackageLocation( const OUString& _sExtensionId )
+{
+ OUString aLocationURL = getPackageLocation( "user", _sExtensionId );
+
+ if ( aLocationURL.isEmpty() )
+ {
+ aLocationURL = getPackageLocation( "shared", _sExtensionId );
+ }
+ if ( aLocationURL.isEmpty() )
+ {
+ aLocationURL = getPackageLocation( "bundled", _sExtensionId );
+ }
+ if ( !aLocationURL.isEmpty() )
+ {
+ try
+ {
+ ::ucbhelper::Content aContent( aLocationURL, nullptr, mxContext );
+ aLocationURL = aContent.getURL();
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring");
+ }
+ }
+ return aLocationURL;
+}
+
+uno::Sequence< uno::Sequence< OUString > > SAL_CALL
+PackageInformationProvider::isUpdateAvailable( const OUString& _sExtensionId )
+{
+ uno::Sequence< uno::Sequence< OUString > > aList;
+
+ uno::Reference<deployment::XExtensionManager> extMgr =
+ deployment::ExtensionManager::get(mxContext);
+
+ if (!extMgr.is())
+ {
+ OSL_ASSERT(false);
+ return aList;
+ }
+ std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors;
+ dp_misc::UpdateInfoMap updateInfoMap;
+ if (!_sExtensionId.isEmpty())
+ {
+ std::vector<uno::Reference<deployment::XPackage> > vecExtensions;
+ uno::Reference<deployment::XPackage> extension;
+ try
+ {
+ extension = dp_misc::getExtensionWithHighestVersion(
+ extMgr->getExtensionsWithSameIdentifier(
+ _sExtensionId, _sExtensionId, uno::Reference<css_ucb::XCommandEnvironment>()));
+ vecExtensions.push_back(extension);
+ }
+ catch (lang::IllegalArgumentException &)
+ {
+ OSL_ASSERT(false);
+ }
+ updateInfoMap = dp_misc::getOnlineUpdateInfos(
+ mxContext, extMgr, mxUpdateInformation, &vecExtensions, errors);
+ }
+ else
+ {
+ updateInfoMap = dp_misc::getOnlineUpdateInfos(
+ mxContext, extMgr, mxUpdateInformation, nullptr, errors);
+ }
+
+ int nCount = 0;
+ for (auto const& updateInfo : updateInfoMap)
+ {
+ dp_misc::UpdateInfo const & info = updateInfo.second;
+
+ OUString sOnlineVersion;
+ if (info.info.is())
+ {
+ // check, if there are unsatisfied dependencies and ignore this online update
+ dp_misc::DescriptionInfoset infoset(mxContext, info.info);
+ uno::Sequence< uno::Reference< xml::dom::XElement > >
+ ds( dp_misc::Dependencies::check( infoset ) );
+ if ( ! ds.hasElements() )
+ sOnlineVersion = info.version;
+ }
+
+ OUString sVersionUser;
+ OUString sVersionShared;
+ OUString sVersionBundled;
+ uno::Sequence< uno::Reference< deployment::XPackage> > extensions;
+ try {
+ extensions = extMgr->getExtensionsWithSameIdentifier(
+ dp_misc::getIdentifier(info.extension), info.extension->getName(),
+ uno::Reference<css_ucb::XCommandEnvironment>());
+ } catch (const lang::IllegalArgumentException&) {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring");
+ continue;
+ }
+ OSL_ASSERT(extensions.getLength() == 3);
+ if (extensions[0].is() )
+ sVersionUser = extensions[0]->getVersion();
+ if (extensions[1].is() )
+ sVersionShared = extensions[1]->getVersion();
+ if (extensions[2].is() )
+ sVersionBundled = extensions[2]->getVersion();
+
+ bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared");
+
+ dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension(
+ bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion);
+ dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension(
+ bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion);
+
+ OUString updateVersionUser;
+ OUString updateVersionShared;
+ if (sourceUser != dp_misc::UPDATE_SOURCE_NONE)
+ updateVersionUser = dp_misc::getHighestVersion(
+ sVersionShared, sVersionBundled, sOnlineVersion);
+ if (sourceShared != dp_misc::UPDATE_SOURCE_NONE)
+ updateVersionShared = dp_misc::getHighestVersion(
+ OUString(), sVersionBundled, sOnlineVersion);
+ OUString updateVersion;
+ if (dp_misc::compareVersions(updateVersionUser, updateVersionShared) == dp_misc::GREATER)
+ updateVersion = updateVersionUser;
+ else
+ updateVersion = updateVersionShared;
+ if (!updateVersion.isEmpty())
+ {
+
+ OUString aNewEntry[2];
+ aNewEntry[0] = updateInfo.first;
+ aNewEntry[1] = updateVersion;
+ aList.realloc( ++nCount );
+ aList.getArray()[ nCount-1 ] = ::uno::Sequence< OUString >( aNewEntry, 2 );
+ }
+ }
+ return aList;
+}
+
+
+uno::Sequence< uno::Sequence< OUString > > SAL_CALL PackageInformationProvider::getExtensionList()
+{
+ const uno::Reference<deployment::XExtensionManager> mgr =
+ deployment::ExtensionManager::get(mxContext);
+
+ if (!mgr.is())
+ return uno::Sequence< uno::Sequence< OUString > >();
+
+ const uno::Sequence< uno::Sequence< uno::Reference<deployment::XPackage > > >
+ allExt = mgr->getAllExtensions(
+ uno::Reference< task::XAbortChannel >(),
+ uno::Reference< css_ucb::XCommandEnvironment > () );
+
+ uno::Sequence< uno::Sequence< OUString > > retList;
+
+ sal_Int32 cAllIds = allExt.getLength();
+ retList.realloc(cAllIds);
+ auto pretList = retList.getArray();
+
+ for (sal_Int32 i = 0; i < cAllIds; i++)
+ {
+ //The inner sequence contains extensions with the same identifier from
+ //all the different repositories, that is user, share, bundled.
+ const uno::Sequence< uno::Reference< deployment::XPackage > > &
+ seqExtension = allExt[i];
+ sal_Int32 cExt = seqExtension.getLength();
+ OSL_ASSERT(cExt == 3);
+ for (sal_Int32 j = 0; j < cExt; j++)
+ {
+ //ToDo according to the old code the first found extension is used
+ //even if another one with the same id has a better version.
+ uno::Reference< deployment::XPackage > const & xExtension( seqExtension[j] );
+ if (xExtension.is())
+ {
+ pretList[i] = { dp_misc::getIdentifier(xExtension), xExtension->getVersion() };
+ break;
+ }
+ }
+ }
+ return retList;
+}
+
+
+} // namespace dp_info
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_PackageInformationProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new dp_info::PackageInformationProvider(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_manager.cxx b/desktop/source/deployment/manager/dp_manager.cxx
new file mode 100644
index 0000000000..d882b77baf
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_manager.cxx
@@ -0,0 +1,1597 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <dp_interact.h>
+#include <dp_misc.h>
+#include <dp_registry.hxx>
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include <dp_ucb.h>
+#include <dp_platform.hxx>
+#include "dp_manager.h"
+#include <dp_identifier.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/string.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <osl/diagnose.h>
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <comphelper/logging.hxx>
+#include <comphelper/sequence.hxx>
+#include <utility>
+#include <xmlscript/xml_helper.hxx>
+#include <svl/inettype.hxx>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/beans/UnknownPropertyException.hpp>
+#include <com/sun/star/logging/LogLevel.hpp>
+#include <com/sun/star/logging/FileHandler.hpp>
+#include <com/sun/star/logging/SimpleTextFormatter.hpp>
+#include <com/sun/star/logging/XLogger.hpp>
+#include <com/sun/star/util/XUpdatable.hpp>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp>
+#include <com/sun/star/deployment/Prerequisites.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <unotools/tempfile.hxx>
+
+#include <dp_descriptioninfoset.hxx>
+#include "dp_commandenvironments.hxx"
+#include "dp_properties.hxx"
+
+#include <vector>
+#include <algorithm>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::logging;
+
+
+namespace dp_manager {
+
+namespace {
+
+struct MatchTempDir
+{
+ OUString m_str;
+ explicit MatchTempDir( OUString str ) : m_str(std::move( str )) {}
+ bool operator () ( ActivePackages::Entries::value_type const & v ) const {
+ return v.second.temporaryName.equalsIgnoreAsciiCase( m_str );
+ }
+};
+
+OUString getExtensionFolder(OUString const & parentFolder,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ Reference<uno::XComponentContext> const & xContext)
+{
+ ::ucbhelper::Content tempFolder( parentFolder, xCmdEnv, xContext );
+ Reference<sdbc::XResultSet> xResultSet(
+ StrTitle::createCursor (tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) );
+
+ OUString title;
+ if (xResultSet->next())
+ {
+ title = Reference<sdbc::XRow>(
+ xResultSet, UNO_QUERY_THROW )->getString(1 /* Title */ ) ;
+ }
+ return title;
+}
+}
+
+void PackageManagerImpl::initActivationLayer(
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (m_activePackages.isEmpty())
+ {
+ OSL_ASSERT( m_registryCache.isEmpty() );
+ // documents temp activation:
+ m_activePackagesDB.reset( new ActivePackages );
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, m_context, xCmdEnv,
+ false /* no throw */ ))
+ {
+ // scan for all entries in m_packagesDir:
+ Reference<sdbc::XResultSet> xResultSet(
+ StrTitle::createCursor (ucbContent, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS ) );
+
+ while (xResultSet->next())
+ {
+ Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW );
+ OUString title( xRow->getString( 1 /* Title */ ) );
+ // xxx todo: remove workaround for tdoc
+ if ( title == "this_is_a_dummy_stream_just_there_as_a_workaround_for_a_temporary_limitation_of_the_storage_api_implementation" )
+ continue;
+ if ( title == "META-INF" )
+ continue;
+
+ ::ucbhelper::Content sourceContent(
+ Reference<XContentAccess>(
+ xResultSet, UNO_QUERY_THROW )->queryContent(),
+ xCmdEnv, m_xComponentContext );
+
+ OUString mediaType( detectMediaType( sourceContent,
+ false /* no throw */) );
+ if (!mediaType.isEmpty())
+ {
+ ActivePackages::Data dbData;
+ insertToActivationLayer(
+ Sequence<css::beans::NamedValue>(),mediaType, sourceContent,
+ title, &dbData );
+
+ insertToActivationLayerDB( title, dbData );
+ //TODO #i73136#: insertToActivationLayerDB needs id not
+ // title, but the whole m_activePackages.getLength()==0
+ // case (i.e., document-relative deployment) currently
+ // does not work, anyway.
+ }
+ }
+ }
+ }
+ else
+ {
+ // user|share:
+ OSL_ASSERT( !m_activePackages.isEmpty() );
+ m_activePackages_expanded = expandUnoRcUrl( m_activePackages );
+ m_registrationData_expanded = expandUnoRcUrl(m_registrationData);
+ if (!m_readOnly)
+ create_folder( nullptr, m_activePackages_expanded, xCmdEnv);
+
+ OUString dbName;
+ if (m_context == "user")
+ dbName = m_activePackages_expanded + ".pmap";
+ else
+ {
+ // Create the extension data base in the user installation
+ create_folder( nullptr, m_registrationData_expanded, xCmdEnv);
+ dbName = m_registrationData_expanded + "/extensions.pmap";
+ }
+ // The data base can always be written because it is always in the user installation
+ m_activePackagesDB.reset( new ActivePackages( dbName ) );
+
+ if (! m_readOnly && m_context != "bundled")
+ {
+ // clean up activation layer, scan for zombie temp dirs:
+ ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() );
+
+ ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext );
+ Reference<sdbc::XResultSet> xResultSet(
+ StrTitle::createCursor (tempFolder,
+ ::ucbhelper::INCLUDE_DOCUMENTS_ONLY ) );
+
+ // get all temp directories:
+ std::vector<OUString> tempEntries;
+ std::vector<OUString> removedEntries;
+ while (xResultSet->next())
+ {
+ OUString title(
+ Reference<sdbc::XRow>(
+ xResultSet, UNO_QUERY_THROW )->getString(
+ 1 /* Title */ ) );
+ if (title.endsWith("removed", &title))
+ {
+ //save the file name without the "removed" part
+ removedEntries.push_back(::rtl::Uri::encode(
+ title, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ }
+ else
+ {
+ tempEntries.push_back( ::rtl::Uri::encode(
+ title, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ }
+ }
+
+ bool bShared = (m_context == "shared");
+ for (const OUString & tempEntry : tempEntries)
+ {
+ const MatchTempDir match( tempEntry );
+ if (std::none_of( id2temp.begin(), id2temp.end(), match ))
+ {
+ const OUString url(
+ makeURL(m_activePackages_expanded, tempEntry ) );
+
+ //In case of shared extensions, new entries are regarded as
+ //added extensions if there is no xxx.tmpremoved file.
+ if (bShared)
+ {
+ if (std::find(removedEntries.begin(), removedEntries.end(), tempEntry) ==
+ removedEntries.end())
+ {
+ continue;
+ }
+ else
+ {
+ //Make sure only the same user removes the extension, who
+ //previously unregistered it. This is avoid races if multiple instances
+ //of OOo are running which all have write access to the shared installation.
+ //For example, a user removes the extension, but keeps OOo
+ //running. Parts of the extension may still be loaded and used by OOo.
+ //Therefore the extension is only deleted the next time the extension manager is
+ //run after restarting OOo. While OOo is still running, another user starts OOo
+ //which would deleted the extension files. If the same user starts another
+ //instance of OOo then the lock file will prevent this.
+ OUString aUserName;
+ ::osl::Security aSecurity;
+ aSecurity.getUserName( aUserName );
+ ucbhelper::Content remFileContent(
+ url + "removed", Reference<XCommandEnvironment>(), m_xComponentContext);
+ std::vector<sal_Int8> data = dp_misc::readFile(remFileContent);
+ std::string_view osData(reinterpret_cast<const char*>(data.data()),
+ data.size());
+ OUString sData = OStringToOUString(
+ osData, RTL_TEXTENCODING_UTF8);
+ if (sData != aUserName)
+ continue;
+ }
+ }
+ // temp entry not needed anymore:
+ erase_path( url + "_",
+ Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+ erase_path( url, Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+ //delete the xxx.tmpremoved file
+ erase_path(url + "removed",
+ Reference<XCommandEnvironment>(), false);
+ }
+ }
+ }
+ }
+}
+
+
+void PackageManagerImpl::initRegistryBackends()
+{
+ if (!m_registryCache.isEmpty())
+ create_folder( nullptr, m_registryCache,
+ Reference<XCommandEnvironment>(), false);
+ m_xRegistry.set( ::dp_registry::create(
+ m_context, m_registryCache,
+ m_xComponentContext ) );
+}
+
+namespace {
+
+osl::FileBase::RC createDirectory(OUString const & url) {
+ auto e = osl::Directory::create(url);
+ if (e != osl::FileBase::E_NOENT) {
+ return e;
+ }
+ INetURLObject o(url);
+ if (!o.removeSegment()) {
+ return osl::FileBase::E_INVAL; // anything but E_None/E_EXIST
+ }
+ e = createDirectory(o.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) {
+ return e;
+ }
+ return osl::Directory::create(url);
+}
+
+bool isMacroURLReadOnly( const OUString &rMacro )
+{
+ OUString aDirURL( rMacro );
+ ::rtl::Bootstrap::expandMacros( aDirURL );
+
+ ::osl::FileBase::RC aErr = createDirectory( aDirURL );
+ if ( aErr == ::osl::FileBase::E_None )
+ return false; // it will be writeable
+ if ( aErr != ::osl::FileBase::E_EXIST )
+ return true; // some serious problem creating it
+
+ bool bError;
+ sal_uInt64 nWritten = 0;
+ OUString aFileURL( aDirURL + "/stamp.sys" );
+ ::osl::File aFile( aFileURL );
+
+ bError = aFile.open( osl_File_OpenFlag_Read |
+ osl_File_OpenFlag_Write |
+ osl_File_OpenFlag_Create ) != ::osl::FileBase::E_None;
+ if (!bError)
+ bError = aFile.write( "1", 1, nWritten ) != ::osl::FileBase::E_None;
+ if (aFile.close() != ::osl::FileBase::E_None)
+ bError = true;
+ if (osl::File::remove( aFileURL ) != ::osl::FileBase::E_None)
+ bError = true;
+
+ SAL_INFO(
+ "desktop.deployment",
+ "local url '" << rMacro << "' -> '" << aFileURL << "' "
+ << (bError ? "is" : "is not") << " readonly\n");
+ return bError;
+}
+
+}
+
+Reference<deployment::XPackageManager> PackageManagerImpl::create(
+ Reference<XComponentContext> const & xComponentContext,
+ OUString const & context )
+{
+ rtl::Reference<PackageManagerImpl> that = new PackageManagerImpl(
+ xComponentContext, context );
+
+ OUString logFile, stamp;
+ if ( context == "user" ) {
+ that->m_activePackages = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages";
+ that->m_registrationData = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE";
+ that->m_registryCache = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/registry";
+ logFile = "$UNO_USER_PACKAGES_CACHE/log.txt";
+ //We use the extension .sys for the file because on Windows Vista a sys
+ //(as well as exe and dll) file
+ //will not be written in the VirtualStore. For example if the process has no
+ //admin right once cannot write to the %programfiles% folder. However, when
+ //virtualization is used, the file will be written into the VirtualStore and
+ //it appears as if one could write to %programfiles%. When we test for write
+ //access to the office/shared folder for shared extensions then this typically
+ //fails because a normal user typically cannot write to this folder. However,
+ //using virtualization it appears that he/she can. Then a shared extension can
+ //be installed but is only visible for the user (because the extension is in
+ //the virtual store).
+ stamp = "$UNO_USER_PACKAGES_CACHE";
+ }
+ else if ( context == "shared" ) {
+ that->m_activePackages = "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages";
+ that->m_registrationData = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER";
+ that->m_registryCache = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER/registry";
+ logFile = "$SHARED_EXTENSIONS_USER/log.txt";
+#if !HAVE_FEATURE_READONLY_INSTALLSET
+ // The "shared" extensions are read-only when we have a
+ // read-only installset.
+ stamp = "$UNO_SHARED_PACKAGES_CACHE";
+#endif
+ }
+ else if ( context == "bundled" ) {
+ that->m_activePackages = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS";
+ that->m_registrationData = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER";
+ that->m_registryCache = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER/registry";
+ logFile = "$BUNDLED_EXTENSIONS_USER/log.txt";
+ //No stamp file. We assume that bundled is always readonly. It must not be
+ //modified from ExtensionManager but only by the installer
+ }
+ else if ( context == "tmp" ) {
+ that->m_activePackages = "vnd.sun.star.expand:$TMP_EXTENSIONS/extensions";
+ that->m_registrationData = "vnd.sun.star.expand:$TMP_EXTENSIONS";
+ that->m_registryCache = "vnd.sun.star.expand:$TMP_EXTENSIONS/registry";
+ stamp = "$TMP_EXTENSIONS";
+ }
+ else if (context == "bak") {
+ that->m_activePackages = "vnd.sun.star.expand:$BAK_EXTENSIONS/extensions";
+ that->m_registrationData = "vnd.sun.star.expand:$BAK_EXTENSIONS";
+ that->m_registryCache = "vnd.sun.star.expand:$BAK_EXTENSIONS/registry";
+ stamp = "$BAK_EXTENSIONS";
+ }
+
+ else if (! context.match("vnd.sun.star.tdoc:/")) {
+ throw lang::IllegalArgumentException(
+ "invalid context given: " + context,
+ Reference<XInterface>(), static_cast<sal_Int16>(-1) );
+ }
+
+ Reference<XCommandEnvironment> xCmdEnv;
+
+ try {
+ // There is no stamp for the bundled folder:
+ if (!stamp.isEmpty())
+ that->m_readOnly = isMacroURLReadOnly( stamp );
+
+ if (!that->m_readOnly && !logFile.isEmpty())
+ {
+ // Initialize logger which will be used in ProgressLogImpl (created below)
+ rtl::Bootstrap::expandMacros(logFile);
+ comphelper::EventLogger logger(xComponentContext, "unopkg");
+ const Reference<XLogger> xLogger(logger.getLogger());
+ Reference<XLogFormatter> xLogFormatter(SimpleTextFormatter::create(xComponentContext));
+ Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} };
+ Reference<XLogHandler> xFileHandler(css::logging::FileHandler::createWithSettings(xComponentContext, aSeq2));
+ xFileHandler->setLevel(LogLevel::WARNING);
+ xLogger->addLogHandler(xFileHandler);
+
+ that->m_xLogFile.set(
+ that->m_xComponentContext->getServiceManager()
+ ->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.deployment.ProgressLog",
+ Sequence<Any>(),
+ that->m_xComponentContext ),
+ UNO_QUERY_THROW );
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv, that->m_xLogFile ) );
+ }
+
+ that->initRegistryBackends();
+ that->initActivationLayer( xCmdEnv );
+
+ return that;
+
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception & e) {
+ Any exc( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException(
+ ("[context=\"" + context + "\"] caught unexpected "
+ + exc.getValueType().getTypeName() + ": " + e.Message),
+ Reference<XInterface>(), exc );
+ }
+}
+
+
+PackageManagerImpl::~PackageManagerImpl()
+{
+}
+
+
+void PackageManagerImpl::fireModified()
+{
+ ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer(
+ cppu::UnoType<util::XModifyListener>::get() );
+ if (pContainer != nullptr) {
+ pContainer->forEach<util::XModifyListener>(
+ [this] (uno::Reference<util::XModifyListener> const& xListener)
+ { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); });
+ }
+}
+
+
+void PackageManagerImpl::disposing()
+{
+ try {
+// // xxx todo: guarding?
+// ::osl::MutexGuard guard( getMutex() );
+ try_dispose( m_xLogFile );
+ m_xLogFile.clear();
+ try_dispose( m_xRegistry );
+ m_xRegistry.clear();
+ m_activePackagesDB.reset();
+ m_xComponentContext.clear();
+
+ t_pm_helper::disposing();
+
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException(
+ "caught unexpected exception while disposing...",
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+// XComponent
+
+void PackageManagerImpl::dispose()
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::dispose();
+}
+
+
+void PackageManagerImpl::addEventListener(
+ Reference<lang::XEventListener> const & xListener )
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::addEventListener( xListener );
+}
+
+
+void PackageManagerImpl::removeEventListener(
+ Reference<lang::XEventListener> const & xListener )
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::removeEventListener( xListener );
+}
+
+// XPackageManager
+
+OUString PackageManagerImpl::getContext()
+{
+ check();
+ return m_context;
+}
+
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+PackageManagerImpl::getSupportedPackageTypes()
+{
+ OSL_ASSERT( m_xRegistry.is() );
+ return m_xRegistry->getSupportedPackageTypes();
+}
+
+
+Reference<task::XAbortChannel> PackageManagerImpl::createAbortChannel()
+{
+ check();
+ return new AbortChannel;
+}
+
+// XModifyBroadcaster
+
+void PackageManagerImpl::addModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+
+void PackageManagerImpl::removeModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+
+OUString PackageManagerImpl::detectMediaType(
+ ::ucbhelper::Content const & ucbContent_, bool throw_exc )
+{
+ ::ucbhelper::Content ucbContent(ucbContent_);
+ OUString url( ucbContent.getURL() );
+ OUString mediaType;
+ if (url.match( "vnd.sun.star.tdoc:" ) || url.match( "vnd.sun.star.pkg:" ))
+ {
+ try {
+ ucbContent.getPropertyValue( "MediaType" ) >>= mediaType;
+ }
+ catch (const beans::UnknownPropertyException &) {
+ }
+ OSL_ENSURE( !mediaType.isEmpty(), "### no media-type?!" );
+ }
+ if (mediaType.isEmpty())
+ {
+ try {
+ Reference<deployment::XPackage> xPackage(
+ m_xRegistry->bindPackage(
+ url, OUString(), false, OUString(), ucbContent.getCommandEnvironment() ) );
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is())
+ mediaType = xPackageType->getMediaType();
+ }
+ catch (const lang::IllegalArgumentException &) {
+ if (throw_exc)
+ throw;
+ css::uno::Any ex( cppu::getCaughtException() );
+ SAL_WARN( "desktop", exceptionToString(ex) );
+ }
+ }
+ return mediaType;
+}
+
+
+OUString PackageManagerImpl::insertToActivationLayer(
+ Sequence<beans::NamedValue> const & properties,
+ OUString const & mediaType, ::ucbhelper::Content const & sourceContent_,
+ OUString const & title, ActivePackages::Data * dbData )
+{
+ ::ucbhelper::Content sourceContent(sourceContent_);
+ Reference<XCommandEnvironment> xCmdEnv(
+ sourceContent.getCommandEnvironment() );
+
+ OUString tempEntry = ::utl::CreateTempURL(&m_activePackages_expanded, false);
+ tempEntry = tempEntry.copy(tempEntry.lastIndexOf('/') + 1);
+ OUString destFolder = makeURL( m_activePackages, tempEntry) + "_";
+
+ // prepare activation folder:
+ ::ucbhelper::Content destFolderContent;
+ create_folder( &destFolderContent, destFolder, xCmdEnv );
+
+ // copy content into activation temp dir:
+ if (mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.package-bundle") ||
+ // xxx todo: more sophisticated parsing
+ mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.legacy-package-bundle"))
+ {
+ // inflate content:
+ OUStringBuffer buf;
+ if (!sourceContent.isFolder())
+ {
+ buf.append( "vnd.sun.star.zip://" );
+ buf.append( ::rtl::Uri::encode( sourceContent.getURL(),
+ rtl_UriCharClassRegName,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ }
+ else
+ {
+ //Folder. No need to unzip, just copy
+ buf.append(sourceContent.getURL());
+ }
+ buf.append( '/' );
+ sourceContent = ::ucbhelper::Content(
+ buf.makeStringAndClear(), xCmdEnv, m_xComponentContext );
+ }
+ destFolderContent.transferContent(
+ sourceContent, ::ucbhelper::InsertOperation::Copy,
+ title, NameClash::OVERWRITE );
+
+
+ // write to DB:
+ //bundled extensions should only be added by the synchronizeAddedExtensions
+ //functions. Moreover, there is no "temporary folder" for bundled extensions.
+ OSL_ASSERT(!(m_context == "bundled"));
+ OUString sFolderUrl = makeURLAppendSysPathSegment(destFolderContent.getURL(), title);
+ DescriptionInfoset info =
+ dp_misc::getDescriptionInfoset(sFolderUrl);
+ dbData->temporaryName = tempEntry;
+ dbData->fileName = title;
+ dbData->mediaType = mediaType;
+ dbData->version = info.getVersion();
+
+ //No write the properties file next to the extension
+ ExtensionProperties props(sFolderUrl, properties, xCmdEnv, m_xComponentContext);
+ props.write();
+ return destFolder;
+}
+
+
+void PackageManagerImpl::insertToActivationLayerDB(
+ OUString const & id, ActivePackages::Data const & dbData )
+{
+ //access to the database must be guarded. See removePackage
+ const ::osl::MutexGuard guard( m_aMutex );
+ m_activePackagesDB->put( id, dbData );
+}
+
+
+/* The function returns true if there is an extension with the same id already
+ installed which needs to be uninstalled, before the new extension can be installed.
+*/
+bool PackageManagerImpl::isInstalled(
+ Reference<deployment::XPackage> const & package)
+{
+ OUString id(dp_misc::getIdentifier(package));
+ OUString fn(package->getName());
+ bool bInstalled = false;
+ if (m_activePackagesDB->has( id, fn ))
+ {
+ bInstalled = true;
+ }
+ return bInstalled;
+}
+
+// XPackageManager
+
+Reference<deployment::XPackage> PackageManagerImpl::importExtension(
+ Reference<deployment::XPackage> const & extension,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ return addPackage(extension->getURL(), Sequence<beans::NamedValue>(),
+ OUString(), xAbortChannel, xCmdEnv_);
+}
+
+/* The function adds an extension but does not register it!!!
+ It may not do any user interaction. This is done in XExtensionManager::addExtension
+*/
+Reference<deployment::XPackage> PackageManagerImpl::addPackage(
+ OUString const & url,
+ css::uno::Sequence<css::beans::NamedValue> const & properties,
+ OUString const & mediaType_,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ check();
+ if (m_readOnly)
+ {
+ OUString message;
+ if (m_context == "shared")
+ message = "You need write permissions to install a shared extension!";
+ else
+ message = "You need write permissions to install this extension!";
+ throw deployment::DeploymentException(
+ message, static_cast<OWeakObject *>(this), Any() );
+ }
+ Reference<XCommandEnvironment> xCmdEnv;
+ if (m_xLogFile.is())
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) );
+ else
+ xCmdEnv.set( xCmdEnv_ );
+
+ try {
+ ::ucbhelper::Content sourceContent;
+ (void)create_ucb_content( &sourceContent, url, xCmdEnv ); // throws exc
+ const OUString title( StrTitle::getTitle( sourceContent ) );
+ const OUString title_enc( ::rtl::Uri::encode(
+ title, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ OUString destFolder;
+
+ OUString mediaType(mediaType_);
+ if (mediaType.isEmpty())
+ mediaType = detectMediaType( sourceContent );
+
+ Reference<deployment::XPackage> xPackage;
+ // copy file:
+ progressUpdate(
+ DpResId(RID_STR_COPYING_PACKAGE) + title, xCmdEnv );
+ if (m_activePackages.isEmpty())
+ {
+ ::ucbhelper::Content docFolderContent;
+ create_folder( &docFolderContent, m_context, xCmdEnv );
+ // copy into document, first:
+ docFolderContent.transferContent(
+ sourceContent, ::ucbhelper::InsertOperation::Copy,
+ OUString(),
+ NameClash::ASK /* xxx todo: ASK not needed? */);
+ // set media-type:
+ ::ucbhelper::Content docContent(
+ makeURL( m_context, title_enc ), xCmdEnv, m_xComponentContext );
+ //TODO #i73136#: using title instead of id can lead to
+ // clashes, but the whole m_activePackages.getLength()==0
+ // case (i.e., document-relative deployment) currently does
+ // not work, anyway.
+ docContent.setPropertyValue("MediaType", Any(mediaType) );
+
+ // xxx todo: obsolete in the future
+ try {
+ docFolderContent.executeCommand( "flush", Any() );
+ }
+ catch (const UnsupportedCommandException &) {
+ }
+ }
+ ActivePackages::Data dbData;
+ destFolder = insertToActivationLayer(
+ properties, mediaType, sourceContent, title, &dbData );
+
+
+ // bind activation package:
+ //Because every shared/user extension will be unpacked in a folder,
+ //which was created with a unique name we will always have two different
+ //XPackage objects, even if the second extension is the same.
+ //Therefore bindPackage does not need a guard here.
+ xPackage = m_xRegistry->bindPackage(
+ makeURL( destFolder, title_enc ), mediaType, false, OUString(), xCmdEnv );
+
+ OSL_ASSERT( xPackage.is() );
+ if (xPackage.is())
+ {
+ bool install = false;
+ try
+ {
+ OUString const id = dp_misc::getIdentifier( xPackage );
+
+ std::unique_lock g(m_addMutex);
+ if (isInstalled(xPackage))
+ {
+ //Do not guard the complete function with the getMutex
+ removePackage(id, xPackage->getName(), xAbortChannel,
+ xCmdEnv);
+ }
+ install = true;
+ insertToActivationLayerDB(id, dbData);
+ }
+ catch (...)
+ {
+ deletePackageFromCache( xPackage, destFolder );
+ throw;
+ }
+ if (!install)
+ {
+ deletePackageFromCache( xPackage, destFolder );
+ }
+ //ToDo: We should notify only if the extension is registered
+ fireModified();
+ }
+ return xPackage;
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const CommandAbortedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const deployment::DeploymentException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ logIntern( exc );
+ throw deployment::DeploymentException(
+ DpResId(RID_STR_ERROR_WHILE_ADDING) + url,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+void PackageManagerImpl::deletePackageFromCache(
+ Reference<deployment::XPackage> const & xPackage,
+ OUString const & destFolder)
+{
+ try_dispose( xPackage );
+
+ //we remove the package from the uno cache
+ //no service from the package may be loaded at this time!!!
+ erase_path( destFolder, Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+ //rm last character '_'
+ OUString url = destFolder.copy(0, destFolder.getLength() - 1);
+ erase_path( url, Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+
+}
+
+void PackageManagerImpl::removePackage(
+ OUString const & id, OUString const & fileName,
+ Reference<task::XAbortChannel> const & /*xAbortChannel*/,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ check();
+
+ Reference<XCommandEnvironment> xCmdEnv;
+ if (m_xLogFile.is())
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) );
+ else
+ xCmdEnv.set( xCmdEnv_ );
+
+ try {
+ Reference<deployment::XPackage> xPackage;
+ {
+ const ::osl::MutexGuard guard(m_aMutex);
+ //Check if this extension exist and throw an IllegalArgumentException
+ //if it does not
+ //If the files of the extension are already removed, or there is a
+ //different extension at the same place, for example after updating the
+ //extension, then the returned object is that which uses the database data.
+ xPackage = getDeployedPackage_(id, fileName, xCmdEnv );
+
+
+ //Because the extension is only removed the next time the extension
+ //manager runs after restarting OOo, we need to indicate that a
+ //shared extension was "deleted". When a user starts OOo, then it
+ //will check if something changed in the shared repository. Based on
+ //the flag file it will then recognize, that the extension was
+ //deleted and can then update the extension database of the shared
+ //extensions in the user installation.
+ if ( xPackage.is() && !m_readOnly && !xPackage->isRemoved() && (m_context == "shared"))
+ {
+ ActivePackages::Data val;
+ m_activePackagesDB->get( & val, id, fileName);
+ OSL_ASSERT(!val.temporaryName.isEmpty());
+ OUString url(makeURL(m_activePackages_expanded,
+ val.temporaryName + "removed"));
+ ::ucbhelper::Content contentRemoved(url, xCmdEnv, m_xComponentContext);
+ OUString aUserName;
+ ::osl::Security aSecurity;
+ aSecurity.getUserName( aUserName );
+
+ OString stamp = OUStringToOString(aUserName, RTL_TEXTENCODING_UTF8);
+ Reference<css::io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(stamp.getStr()),
+ stamp.getLength() ) );
+ contentRemoved.writeStream( xData, true /* replace existing */ );
+ }
+ m_activePackagesDB->erase( id, fileName ); // to be removed upon next start
+ //remove any cached data hold by the backend
+ m_xRegistry->packageRemoved(xPackage->getURL(), xPackage->getPackageType()->getMediaType());
+ }
+ try_dispose( xPackage );
+
+ fireModified();
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const CommandAbortedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const deployment::DeploymentException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ logIntern( exc );
+ throw deployment::DeploymentException(
+ DpResId(RID_STR_ERROR_WHILE_REMOVING) + id,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+OUString PackageManagerImpl::getDeployPath( ActivePackages::Data const & data )
+{
+ OUStringBuffer buf( data.temporaryName );
+ //The bundled extensions are not contained in an additional folder
+ //with a unique name. data.temporaryName contains already the
+ //UTF8 encoded folder name. See PackageManagerImpl::synchronize
+ if (m_context != "bundled")
+ {
+ buf.append( "_/"
+ + ::rtl::Uri::encode( data.fileName, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ }
+ return makeURL( m_activePackages, buf.makeStringAndClear() );
+}
+
+
+Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_(
+ OUString const & id, OUString const & fileName,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ ActivePackages::Data val;
+ if (m_activePackagesDB->get( &val, id, fileName ))
+ {
+ return getDeployedPackage_( id, val, xCmdEnv );
+ }
+ throw lang::IllegalArgumentException(
+ DpResId(RID_STR_NO_SUCH_PACKAGE) + id,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+}
+
+
+Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_(
+ std::u16string_view id, ActivePackages::Data const & data,
+ Reference<XCommandEnvironment> const & xCmdEnv, bool ignoreAlienPlatforms )
+{
+ if (ignoreAlienPlatforms)
+ {
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( data.mediaType, type, subType, &params ))
+ {
+ auto const iter = params.find("platform"_ostr);
+ if (iter != params.end() && !platform_fits(iter->second.m_sValue))
+ throw lang::IllegalArgumentException(
+ DpResId(RID_STR_NO_SUCH_PACKAGE) + id,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+ }
+ }
+ Reference<deployment::XPackage> xExtension;
+ try
+ {
+ //Ignore extensions where XPackage::checkPrerequisites failed.
+ //They must not be usable for this user.
+ if (data.failedPrerequisites == "0")
+ {
+ xExtension = m_xRegistry->bindPackage(
+ getDeployPath( data ), data.mediaType, false, OUString(), xCmdEnv );
+ }
+ }
+ catch (const deployment::InvalidRemovedParameterException& e)
+ {
+ xExtension = e.Extension;
+ }
+ return xExtension;
+}
+
+
+Sequence< Reference<deployment::XPackage> >
+PackageManagerImpl::getDeployedPackages_(
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ std::vector< Reference<deployment::XPackage> > packages;
+ ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() );
+ for (auto const& elem : id2temp)
+ {
+ if (elem.second.failedPrerequisites != "0")
+ continue;
+ try {
+ packages.push_back(
+ getDeployedPackage_(
+ elem.first, elem.second, xCmdEnv,
+ true /* xxx todo: think of GUI:
+ ignore other platforms than the current one */ ) );
+ }
+ catch (const lang::IllegalArgumentException &) {
+ // ignore
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ catch (const deployment::DeploymentException&) {
+ // ignore
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ }
+ return comphelper::containerToSequence(packages);
+}
+
+
+Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage(
+ OUString const & id, OUString const & fileName,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ check();
+ Reference<XCommandEnvironment> xCmdEnv;
+ if (m_xLogFile.is())
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) );
+ else
+ xCmdEnv.set( xCmdEnv_ );
+
+ try {
+ const ::osl::MutexGuard guard( m_aMutex );
+ return getDeployedPackage_( id, fileName, xCmdEnv );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const deployment::DeploymentException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ logIntern( exc );
+ throw deployment::DeploymentException(
+ // ought never occur...
+ "error while accessing deployed package: " + id,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+Sequence< Reference<deployment::XPackage> >
+PackageManagerImpl::getDeployedPackages(
+ Reference<task::XAbortChannel> const &,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ check();
+ Reference<XCommandEnvironment> xCmdEnv;
+ if (m_xLogFile.is())
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) );
+ else
+ xCmdEnv.set( xCmdEnv_ );
+
+ try {
+ const ::osl::MutexGuard guard( m_aMutex );
+ return getDeployedPackages_( xCmdEnv );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const CommandAbortedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const deployment::DeploymentException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ logIntern( exc );
+ throw deployment::DeploymentException(
+ // ought never occur...
+ "error while getting all deployed packages: " + m_context,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+//ToDo: the function must not call registerPackage, do this in
+//XExtensionManager.reinstallDeployedExtensions
+void PackageManagerImpl::reinstallDeployedPackages(
+ sal_Bool force, Reference<task::XAbortChannel> const & /*xAbortChannel*/,
+ Reference<XCommandEnvironment> const & xCmdEnv_ )
+{
+ check();
+ if (!force && office_is_running())
+ throw RuntimeException(
+ "You must close any running Office process before reinstalling packages!",
+ static_cast<OWeakObject *>(this) );
+
+ Reference<XCommandEnvironment> xCmdEnv;
+ if (m_xLogFile.is())
+ xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) );
+ else
+ xCmdEnv.set( xCmdEnv_ );
+
+ try {
+ ProgressLevel progress(
+ xCmdEnv, "Reinstalling all deployed packages..." );
+
+ try_dispose( m_xRegistry );
+ m_xRegistry.clear();
+ if (!m_registryCache.isEmpty())
+ erase_path( m_registryCache, xCmdEnv );
+ initRegistryBackends();
+ Reference<util::XUpdatable> xUpdatable( m_xRegistry, UNO_QUERY );
+ if (xUpdatable.is())
+ xUpdatable->update();
+
+ //registering is done by the ExtensionManager service.
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const CommandAbortedException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const deployment::DeploymentException & exc) {
+ logIntern( Any(exc) );
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ logIntern( exc );
+ throw deployment::DeploymentException(
+ "Error while reinstalling all previously deployed packages of context " + m_context,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+sal_Bool SAL_CALL PackageManagerImpl::isReadOnly( )
+{
+ return m_readOnly;
+}
+bool PackageManagerImpl::synchronizeRemovedExtensions(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<css::ucb::XCommandEnvironment> const & xCmdEnv)
+{
+
+ //find all which are in the extension data base but which
+ //are removed already.
+ OSL_ASSERT(!(m_context == "user"));
+ bool bModified = false;
+ ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() );
+
+ bool bShared = (m_context == "shared");
+
+ for (auto const& elem : id2temp)
+ {
+ try
+ {
+ //Get the URL to the extensions folder, first make the url for the
+ //shared repository including the temporary name
+ OUString url = makeURL(m_activePackages, elem.second.temporaryName);
+ if (bShared)
+ url = makeURLAppendSysPathSegment( Concat2View(url + "_"), elem.second.fileName);
+
+ bool bRemoved = false;
+ //Check if the URL to the extension is still the same
+ ::ucbhelper::Content contentExtension;
+
+ if (!create_ucb_content(
+ &contentExtension, url,
+ Reference<XCommandEnvironment>(), false))
+ {
+ bRemoved = true;
+ }
+
+ //The folder is in the extension database, but it can still be deleted.
+ //look for the xxx.tmpremoved file
+ //There can also be the case that a different extension was installed
+ //in a "temp" folder with name that is already used.
+ if (!bRemoved && bShared)
+ {
+ ::ucbhelper::Content contentRemoved;
+
+ if (create_ucb_content(
+ &contentRemoved,
+ m_activePackages_expanded + "/" +
+ elem.second.temporaryName + "removed",
+ Reference<XCommandEnvironment>(), false))
+ {
+ bRemoved = true;
+ }
+ }
+
+ if (!bRemoved)
+ {
+ //There may be another extensions at the same place
+ dp_misc::DescriptionInfoset infoset =
+ dp_misc::getDescriptionInfoset(url);
+ OSL_ENSURE(infoset.hasDescription() && infoset.getIdentifier(),
+ "Extension Manager: bundled and shared extensions "
+ "must have an identifier and a version");
+ if (infoset.hasDescription() &&
+ infoset.getIdentifier() &&
+ ( elem.first != *(infoset.getIdentifier())
+ || elem.second.version != infoset.getVersion()))
+ {
+ bRemoved = true;
+ }
+
+ }
+ if (bRemoved)
+ {
+ Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage(
+ url, elem.second.mediaType, true, elem.first, xCmdEnv );
+ OSL_ASSERT(xPackage.is()); //Even if the files are removed, we must get the object.
+ xPackage->revokePackage(true, xAbortChannel, xCmdEnv);
+ removePackage(xPackage->getIdentifier().Value, xPackage->getName(),
+ xAbortChannel, xCmdEnv);
+ bModified = true;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "");
+ }
+ }
+ return bModified;
+}
+
+
+bool PackageManagerImpl::synchronizeAddedExtensions(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<css::ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ bool bModified = false;
+ OSL_ASSERT(!(m_context == "user"));
+
+ ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() );
+ //check if the folder exist at all. The shared extension folder
+ //may not exist for a normal user.
+ bool bOk=true;
+ try
+ {
+ bOk = create_ucb_content(
+ nullptr, m_activePackages_expanded, Reference<css::ucb::XCommandEnvironment>(), false);
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ bOk = false;
+ }
+
+ if (!bOk)
+ return bModified;
+
+ ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext );
+ Reference<sdbc::XResultSet> xResultSet(
+ StrTitle::createCursor( tempFolder,
+ ::ucbhelper::INCLUDE_FOLDERS_ONLY ) );
+
+ while (xResultSet->next())
+ {
+ try
+ {
+ OUString title(
+ Reference<sdbc::XRow>(
+ xResultSet, UNO_QUERY_THROW )->getString(
+ 1 /* Title */ ) );
+ //The temporary folders of user and shared have an '_' at then end.
+ //But the name in ActivePackages.temporaryName is saved without.
+ OUString title2 = title;
+ bool bShared = (m_context == "shared");
+ if (bShared)
+ {
+ OSL_ASSERT(title2.endsWith("_"));
+ title2 = title2.copy(0, title2.getLength() -1);
+ }
+ OUString titleEncoded = ::rtl::Uri::encode(
+ title2, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8);
+
+ //It is sufficient to check for the folder name, because when the administrator
+ //installed the extension it was already checked if there is one with the
+ //same identifier.
+ const MatchTempDir match(titleEncoded);
+ if (std::none_of( id2temp.begin(), id2temp.end(), match ))
+ {
+
+ // The folder was not found in the data base, so it must be
+ // an added extension
+ OUString url(m_activePackages_expanded + "/" + titleEncoded);
+ OUString sExtFolder;
+ if (bShared) //that is, shared
+ {
+ //Check if the extension was not "deleted" already which is indicated
+ //by a xxx.tmpremoved file
+ ::ucbhelper::Content contentRemoved;
+ if (create_ucb_content(&contentRemoved, url + "removed",
+ Reference<XCommandEnvironment>(), false))
+ continue;
+ sExtFolder = getExtensionFolder(
+ m_activePackages_expanded + "/" + titleEncoded + "_",
+ xCmdEnv, m_xComponentContext);
+ url = makeURLAppendSysPathSegment(m_activePackages_expanded, title);
+ url = makeURLAppendSysPathSegment(url, sExtFolder);
+ }
+ Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage(
+ url, OUString(), false, OUString(), xCmdEnv );
+ if (xPackage.is())
+ {
+ OUString id = dp_misc::getIdentifier( xPackage );
+
+ //Prepare the database entry
+ ActivePackages::Data dbData;
+
+ dbData.temporaryName = titleEncoded;
+ if (bShared)
+ dbData.fileName = sExtFolder;
+ else
+ dbData.fileName = title;
+ dbData.mediaType = xPackage->getPackageType()->getMediaType();
+ dbData.version = xPackage->getVersion();
+ SAL_WARN_IF(
+ dbData.version.isEmpty(), "desktop.deployment",
+ "bundled/shared extension " << id << " at <" << url
+ << "> has no explicit version");
+
+ //We provide a special command environment that will prevent
+ //showing a license if simple-license/@accept-by = "admin"
+ //It will also prevent showing the license for bundled extensions
+ //which is not supported.
+ OSL_ASSERT(!(m_context == "user"));
+
+ // shall the license be suppressed?
+ DescriptionInfoset info =
+ dp_misc::getDescriptionInfoset(url);
+ ::std::optional<dp_misc::SimpleLicenseAttributes>
+ attr = info.getSimpleLicenseAttributes();
+ ExtensionProperties props(url, xCmdEnv, m_xComponentContext);
+ bool bNoLicense = false;
+ if (attr && attr->suppressIfRequired && props.isSuppressedLicense())
+ bNoLicense = true;
+
+ Reference<ucb::XCommandEnvironment> licCmdEnv(
+ new LicenseCommandEnv(xCmdEnv->getInteractionHandler(),
+ bNoLicense, m_context));
+ sal_Int32 failedPrereq = xPackage->checkPrerequisites(
+ xAbortChannel, licCmdEnv, false);
+ //Remember that this failed. For example, the user
+ //could have declined the license. Then the next time the
+ //extension folder is investigated we do not want to
+ //try to install the extension again.
+ dbData.failedPrerequisites = OUString::number(failedPrereq);
+ insertToActivationLayerDB(id, dbData);
+ bModified = true;
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ // Looks like exceptions being caught here is not an uncommon case.
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "");
+ }
+ }
+ return bModified;
+}
+
+sal_Bool PackageManagerImpl::synchronize(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<css::ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ check();
+ bool bModified = false;
+ if (m_context == "user")
+ return bModified;
+ bModified |=
+ synchronizeRemovedExtensions(xAbortChannel, xCmdEnv);
+ bModified |= synchronizeAddedExtensions(xAbortChannel, xCmdEnv);
+
+ return bModified;
+}
+
+Sequence< Reference<deployment::XPackage> > PackageManagerImpl::getExtensionsWithUnacceptedLicenses(
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ std::vector<Reference<deployment::XPackage> > vec;
+
+ try
+ {
+ const ::osl::MutexGuard guard( m_aMutex );
+ // clean up activation layer, scan for zombie temp dirs:
+ ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() );
+
+ bool bShared = (m_context == "shared");
+
+ for (auto const& elem : id2temp)
+ {
+ //Get the database entry
+ ActivePackages::Data const & dbData = elem.second;
+ sal_Int32 failedPrereq = dbData.failedPrerequisites.toInt32();
+ //If the installation failed for other reason then the license then we
+ //ignore it.
+ if (failedPrereq ^ deployment::Prerequisites::LICENSE)
+ continue;
+
+ //Prepare the URL to the extension
+ OUString url = makeURL(m_activePackages, elem.second.temporaryName);
+ if (bShared)
+ url = makeURLAppendSysPathSegment( Concat2View(url + "_"), elem.second.fileName);
+
+ Reference<deployment::XPackage> p = m_xRegistry->bindPackage(
+ url, OUString(), false, OUString(), xCmdEnv );
+
+ if (p.is())
+ vec.push_back(p);
+
+ }
+ return ::comphelper::containerToSequence(vec);
+ }
+ catch (const deployment::DeploymentException &)
+ {
+ throw;
+ }
+ catch (const RuntimeException&)
+ {
+ throw;
+ }
+ catch (...)
+ {
+ Any exc = ::cppu::getCaughtException();
+ deployment::DeploymentException de(
+ "PackageManagerImpl::getExtensionsWithUnacceptedLicenses",
+ static_cast<OWeakObject*>(this), exc);
+ exc <<= de;
+ ::cppu::throwException(exc);
+ }
+
+ return ::comphelper::containerToSequence(vec);
+}
+
+sal_Int32 PackageManagerImpl::checkPrerequisites(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ try
+ {
+ if (!extension.is())
+ return 0;
+ if (m_context != extension->getRepositoryName())
+ throw lang::IllegalArgumentException(
+ "PackageManagerImpl::checkPrerequisites: extension is not from this repository.",
+ nullptr, 0);
+
+ ActivePackages::Data dbData;
+ OUString id = dp_misc::getIdentifier(extension);
+ if (!m_activePackagesDB->get( &dbData, id, OUString()))
+ {
+ throw lang::IllegalArgumentException(
+ "PackageManagerImpl::checkPrerequisites: unknown extension",
+ nullptr, 0);
+
+ }
+ //If the license was already displayed, then do not show it again
+ Reference<ucb::XCommandEnvironment> _xCmdEnv = xCmdEnv;
+ sal_Int32 prereq = dbData.failedPrerequisites.toInt32();
+ if ( !(prereq & deployment::Prerequisites::LICENSE))
+ _xCmdEnv = new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler());
+
+ sal_Int32 failedPrereq = extension->checkPrerequisites(
+ xAbortChannel, _xCmdEnv, false);
+ dbData.failedPrerequisites = OUString::number(failedPrereq);
+ insertToActivationLayerDB(id, dbData);
+ return 0;
+ }
+ catch ( const deployment::DeploymentException& ) {
+ throw;
+ } catch ( const ucb::CommandFailedException & ) {
+ throw;
+ } catch ( const ucb::CommandAbortedException & ) {
+ throw;
+ } catch (const lang::IllegalArgumentException &) {
+ throw;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (...) {
+ uno::Any excOccurred = ::cppu::getCaughtException();
+ deployment::DeploymentException exc(
+ "PackageManagerImpl::checkPrerequisites: exception ",
+ static_cast<OWeakObject*>(this), excOccurred);
+ throw exc;
+ }
+}
+
+
+PackageManagerImpl::CmdEnvWrapperImpl::~CmdEnvWrapperImpl()
+{
+}
+
+
+PackageManagerImpl::CmdEnvWrapperImpl::CmdEnvWrapperImpl(
+ Reference<XCommandEnvironment> const & xUserCmdEnv,
+ Reference<XProgressHandler> const & xLogFile )
+ : m_xLogFile( xLogFile )
+{
+ if (xUserCmdEnv.is()) {
+ m_xUserProgress.set( xUserCmdEnv->getProgressHandler() );
+ m_xUserInteractionHandler.set( xUserCmdEnv->getInteractionHandler() );
+ }
+}
+
+// XCommandEnvironment
+
+Reference<task::XInteractionHandler>
+PackageManagerImpl::CmdEnvWrapperImpl::getInteractionHandler()
+{
+ return m_xUserInteractionHandler;
+}
+
+
+Reference<XProgressHandler>
+PackageManagerImpl::CmdEnvWrapperImpl::getProgressHandler()
+{
+ return this;
+}
+
+// XProgressHandler
+
+void PackageManagerImpl::CmdEnvWrapperImpl::push( Any const & Status )
+{
+ if (m_xLogFile.is())
+ m_xLogFile->push( Status );
+ if (m_xUserProgress.is())
+ m_xUserProgress->push( Status );
+}
+
+
+void PackageManagerImpl::CmdEnvWrapperImpl::update( Any const & Status )
+{
+ if (m_xLogFile.is())
+ m_xLogFile->update( Status );
+ if (m_xUserProgress.is())
+ m_xUserProgress->update( Status );
+}
+
+
+void PackageManagerImpl::CmdEnvWrapperImpl::pop()
+{
+ if (m_xLogFile.is())
+ m_xLogFile->pop();
+ if (m_xUserProgress.is())
+ m_xUserProgress->pop();
+}
+
+} // namespace dp_manager
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_manager.h b/desktop/source/deployment/manager/dp_manager.h
new file mode 100644
index 0000000000..dce57d418e
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_manager.h
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "dp_activepackages.hxx"
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <ucbhelper/content.hxx>
+#include <com/sun/star/deployment/XPackageRegistry.hpp>
+#include <com/sun/star/deployment/XPackageManager.hpp>
+#include <memory>
+#include <mutex>
+#include <string_view>
+#include <utility>
+
+namespace dp_manager {
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::deployment::XPackageManager > t_pm_helper;
+
+
+class PackageManagerImpl final : private cppu::BaseMutex, public t_pm_helper
+{
+ css::uno::Reference<css::uno::XComponentContext> m_xComponentContext;
+ OUString m_context;
+ OUString m_registrationData;
+ OUString m_registrationData_expanded;
+ OUString m_registryCache;
+ bool m_readOnly;
+
+ OUString m_activePackages;
+ OUString m_activePackages_expanded;
+ std::unique_ptr< ActivePackages > m_activePackagesDB;
+ //This mutex is only used for synchronization in addPackage
+ std::mutex m_addMutex;
+ css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile;
+ inline void logIntern( css::uno::Any const & status );
+ void fireModified();
+
+ css::uno::Reference<css::deployment::XPackageRegistry> m_xRegistry;
+
+ void initRegistryBackends();
+ void initActivationLayer(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv );
+ OUString detectMediaType(
+ ::ucbhelper::Content const & ucbContent, bool throw_exc = true );
+ OUString insertToActivationLayer(
+ css::uno::Sequence<css::beans::NamedValue> const & properties,
+ OUString const & mediaType,
+ ::ucbhelper::Content const & sourceContent,
+ OUString const & title, ActivePackages::Data * dbData );
+ void insertToActivationLayerDB(
+ OUString const & id, ActivePackages::Data const & dbData );
+
+ static void deletePackageFromCache(
+ css::uno::Reference<css::deployment::XPackage> const & xPackage,
+ OUString const & destFolder );
+
+ bool isInstalled(
+ css::uno::Reference<css::deployment::XPackage> const & package);
+
+ bool synchronizeRemovedExtensions(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+
+ bool synchronizeAddedExtensions(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+
+ class CmdEnvWrapperImpl
+ : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
+ css::ucb::XProgressHandler >
+ {
+ css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile;
+ css::uno::Reference<css::ucb::XProgressHandler> m_xUserProgress;
+ css::uno::Reference<css::task::XInteractionHandler>
+ m_xUserInteractionHandler;
+
+ public:
+ virtual ~CmdEnvWrapperImpl() override;
+ CmdEnvWrapperImpl(
+ css::uno::Reference<css::ucb::XCommandEnvironment>
+ const & xUserCmdEnv,
+ css::uno::Reference<css::ucb::XProgressHandler> const & xLogFile );
+
+ // XCommandEnvironment
+ virtual css::uno::Reference<css::task::XInteractionHandler> SAL_CALL
+ getInteractionHandler() override;
+ virtual css::uno::Reference<css::ucb::XProgressHandler> SAL_CALL
+ getProgressHandler() override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL update( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+ };
+
+ inline void check();
+ virtual void SAL_CALL disposing() override;
+
+ virtual ~PackageManagerImpl() override;
+ PackageManagerImpl(
+ css::uno::Reference<css::uno::XComponentContext> xComponentContext, OUString context )
+ : t_pm_helper( m_aMutex ),
+ m_xComponentContext(std::move( xComponentContext )),
+ m_context(std::move( context )),
+ m_readOnly( true )
+ {}
+
+public:
+ static css::uno::Reference<css::deployment::XPackageManager> create(
+ css::uno::Reference<css::uno::XComponentContext>
+ const & xComponentContext, OUString const & context );
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener(
+ css::uno::Reference<css::lang::XEventListener> const & xListener ) override;
+ virtual void SAL_CALL removeEventListener(
+ css::uno::Reference<css::lang::XEventListener> const & xListener ) override;
+
+ // XModifyBroadcaster
+ virtual void SAL_CALL addModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+ virtual void SAL_CALL removeModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+
+ // XPackageManager
+ virtual OUString SAL_CALL getContext() override;
+ virtual css::uno::Sequence<
+ css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+
+ virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL
+ createAbortChannel() override;
+
+ virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addPackage(
+ OUString const & url,
+ css::uno::Sequence<css::beans::NamedValue> const & properties,
+ OUString const & mediaType,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL importExtension(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL removePackage(
+ OUString const & id, OUString const & fileName,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ OUString getDeployPath( ActivePackages::Data const & data );
+ css::uno::Reference<css::deployment::XPackage> getDeployedPackage_(
+ OUString const & id, OUString const & fileName,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv );
+ css::uno::Reference<css::deployment::XPackage> getDeployedPackage_(
+ std::u16string_view id, ActivePackages::Data const & data,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ bool ignoreAlienPlatforms = false );
+ virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL
+ getDeployedPackage(
+ OUString const & id, OUString const & fileName,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> >
+ getDeployedPackages_(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv );
+ virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> >
+ SAL_CALL getDeployedPackages(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL reinstallDeployedPackages(
+ sal_Bool force,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual ::sal_Bool SAL_CALL isReadOnly( ) override;
+
+ virtual ::sal_Bool SAL_CALL synchronize(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL
+ getExtensionsWithUnacceptedLicenses(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override;
+
+ virtual sal_Int32 SAL_CALL checkPrerequisites(
+ css::uno::Reference<css::deployment::XPackage> const & extension,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ };
+
+
+inline void PackageManagerImpl::check()
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed)
+ throw css::lang::DisposedException(
+ "PackageManager instance has already been disposed!",
+ static_cast< ::cppu::OWeakObject * >(this) );
+}
+
+
+inline void PackageManagerImpl::logIntern( css::uno::Any const & status )
+{
+ if (m_xLogFile.is())
+ m_xLogFile->update( status );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_managerfac.cxx b/desktop/source/deployment/manager/dp_managerfac.cxx
new file mode 100644
index 0000000000..79e0ea3588
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_managerfac.cxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "dp_manager.h"
+#include <dp_misc.h>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/deployment/XPackageManagerFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <unordered_map>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_manager::factory {
+
+typedef ::cppu::WeakComponentImplHelper<
+ deployment::XPackageManagerFactory, lang::XServiceInfo > t_pmfac_helper;
+
+namespace {
+
+class PackageManagerFactoryImpl : private cppu::BaseMutex, public t_pmfac_helper
+{
+ Reference<XComponentContext> m_xComponentContext;
+
+ Reference<deployment::XPackageManager> m_xUserMgr;
+ Reference<deployment::XPackageManager> m_xSharedMgr;
+ Reference<deployment::XPackageManager> m_xBundledMgr;
+ Reference<deployment::XPackageManager> m_xTmpMgr;
+ Reference<deployment::XPackageManager> m_xBakMgr;
+ typedef std::unordered_map<
+ OUString, WeakReference<deployment::XPackageManager> > t_string2weakref;
+ t_string2weakref m_managers;
+
+protected:
+ inline void check();
+ virtual void SAL_CALL disposing() override;
+
+public:
+ explicit PackageManagerFactoryImpl(
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageManagerFactory
+ virtual Reference<deployment::XPackageManager> SAL_CALL getPackageManager(
+ OUString const & context ) override;
+};
+
+}
+
+PackageManagerFactoryImpl::PackageManagerFactoryImpl(
+ Reference<XComponentContext> const & xComponentContext )
+ : t_pmfac_helper( m_aMutex ),
+ m_xComponentContext( xComponentContext )
+{
+}
+
+// XServiceInfo
+OUString PackageManagerFactoryImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.PackageManagerFactory";
+}
+
+sal_Bool PackageManagerFactoryImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > PackageManagerFactoryImpl::getSupportedServiceNames()
+{
+ // a private one:
+ return { "com.sun.star.comp.deployment.PackageManagerFactory" };
+}
+
+inline void PackageManagerFactoryImpl::check()
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed)
+ {
+ throw lang::DisposedException(
+ "PackageManagerFactory instance has already been disposed!",
+ static_cast<OWeakObject *>(this) );
+ }
+}
+
+
+void PackageManagerFactoryImpl::disposing()
+{
+ // dispose all managers:
+ ::osl::MutexGuard guard( m_aMutex );
+ for (auto const& elem : m_managers)
+ try_dispose( elem.second );
+ m_managers = t_string2weakref();
+ // the below are already disposed:
+ m_xUserMgr.clear();
+ m_xSharedMgr.clear();
+ m_xBundledMgr.clear();
+ m_xTmpMgr.clear();
+ m_xBakMgr.clear();
+}
+
+// XPackageManagerFactory
+
+Reference<deployment::XPackageManager>
+PackageManagerFactoryImpl::getPackageManager( OUString const & context )
+{
+ Reference< deployment::XPackageManager > xRet;
+ ::osl::ResettableMutexGuard guard( m_aMutex );
+ check();
+ t_string2weakref::const_iterator const iFind( m_managers.find( context ) );
+ if (iFind != m_managers.end()) {
+ xRet = iFind->second;
+ if (xRet.is())
+ return xRet;
+ }
+
+ guard.clear();
+ xRet.set( PackageManagerImpl::create( m_xComponentContext, context ) );
+ guard.reset();
+ std::pair< t_string2weakref::iterator, bool > insertion(
+ m_managers.emplace( context, xRet ) );
+ if (insertion.second)
+ {
+ OSL_ASSERT( insertion.first->second.get() == xRet );
+ // hold user, shared mgrs for whole process: live deployment
+ if ( context == "user" )
+ m_xUserMgr = xRet;
+ else if ( context == "shared" )
+ m_xSharedMgr = xRet;
+ else if ( context == "bundled" )
+ m_xBundledMgr = xRet;
+ else if ( context == "tmp" )
+ m_xTmpMgr = xRet;
+ else if ( context == "bak" )
+ m_xBakMgr = xRet;
+ }
+ else
+ {
+ Reference< deployment::XPackageManager > xAlreadyIn(
+ insertion.first->second );
+ if (xAlreadyIn.is())
+ {
+ guard.clear();
+ try_dispose( xRet );
+ xRet = xAlreadyIn;
+ }
+ else
+ {
+ insertion.first->second = xRet;
+ }
+ }
+ return xRet;
+}
+
+} // namespace dp_manager::factory
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_PackageManagerFactory_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new dp_manager::factory::PackageManagerFactoryImpl(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_properties.cxx b/desktop/source/deployment/manager/dp_properties.cxx
new file mode 100644
index 0000000000..6dc1fff303
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_properties.cxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <xmlscript/xml_helper.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <dp_ucb.h>
+#include "dp_properties.hxx"
+
+namespace lang = com::sun::star::lang;
+namespace ucb = com::sun::star::ucb;
+namespace uno = com::sun::star::uno;
+
+
+using ::com::sun::star::uno::Reference;
+
+constexpr OUString PROP_SUPPRESS_LICENSE = u"SUPPRESS_LICENSE"_ustr;
+constexpr OUStringLiteral PROP_EXTENSION_UPDATE = u"EXTENSION_UPDATE";
+
+namespace dp_manager {
+
+//Reading the file
+ExtensionProperties::ExtensionProperties(
+ std::u16string_view urlExtension,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ Reference<uno::XComponentContext> const & xContext) :
+ m_xCmdEnv(xCmdEnv), m_xContext(xContext)
+{
+ m_propFileUrl = OUString::Concat(urlExtension) + "properties";
+
+ std::vector< std::pair< OUString, OUString> > props;
+ if (! dp_misc::create_ucb_content(nullptr, m_propFileUrl, nullptr, false))
+ return;
+
+ ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext);
+ dp_misc::readProperties(props, contentProps);
+
+ for (auto const& prop : props)
+ {
+ if (prop.first == PROP_SUPPRESS_LICENSE)
+ m_prop_suppress_license = prop.second;
+ }
+}
+
+//Writing the file
+ExtensionProperties::ExtensionProperties(
+ std::u16string_view urlExtension,
+ uno::Sequence<css::beans::NamedValue> const & properties,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ Reference<uno::XComponentContext> const & xContext) :
+ m_xCmdEnv(xCmdEnv), m_xContext(xContext)
+{
+ m_propFileUrl = OUString::Concat(urlExtension) + "properties";
+
+ for (css::beans::NamedValue const & v : properties)
+ {
+ if (v.Name == PROP_SUPPRESS_LICENSE)
+ {
+ m_prop_suppress_license = getPropertyValue(v);
+ }
+ else if (v.Name == PROP_EXTENSION_UPDATE)
+ {
+ m_prop_extension_update = getPropertyValue(v);
+ }
+ else
+ {
+ throw lang::IllegalArgumentException(
+ "Extension Manager: unknown property", nullptr, -1);
+ }
+ }
+}
+
+OUString ExtensionProperties::getPropertyValue(css::beans::NamedValue const & v)
+{
+ OUString value("0");
+ if (! (v.Value >>= value) )
+ {
+ throw lang::IllegalArgumentException(
+ "Extension Manager: wrong property value", nullptr, -1);
+ }
+ return value;
+}
+void ExtensionProperties::write()
+{
+ ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext);
+ OUString buf;
+
+ if (m_prop_suppress_license)
+ {
+ buf = OUString::Concat(PROP_SUPPRESS_LICENSE) + "=" + *m_prop_suppress_license;
+ }
+
+ OString stamp = OUStringToOString(buf, RTL_TEXTENCODING_UTF8);
+ Reference<css::io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(stamp.getStr()),
+ stamp.getLength() ) );
+ contentProps.writeStream( xData, true /* replace existing */ );
+}
+
+bool ExtensionProperties::isSuppressedLicense() const
+{
+ bool ret = false;
+ if (m_prop_suppress_license)
+ {
+ if (*m_prop_suppress_license == "1")
+ ret = true;
+ }
+ return ret;
+}
+
+bool ExtensionProperties::isExtensionUpdate() const
+{
+ bool ret = false;
+ if (m_prop_extension_update)
+ {
+ if (*m_prop_extension_update == "1")
+ ret = true;
+ }
+ return ret;
+}
+
+} // namespace dp_manager
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/manager/dp_properties.hxx b/desktop/source/deployment/manager/dp_properties.hxx
new file mode 100644
index 0000000000..06139ece3c
--- /dev/null
+++ b/desktop/source/deployment/manager/dp_properties.hxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <optional>
+#include <string_view>
+
+namespace dp_manager
+{
+class ExtensionProperties final
+{
+ OUString m_propFileUrl;
+ const css::uno::Reference<css::ucb::XCommandEnvironment> m_xCmdEnv;
+ const css::uno::Reference<css::uno::XComponentContext> m_xContext;
+ ::std::optional<OUString> m_prop_suppress_license;
+ ::std::optional<OUString> m_prop_extension_update;
+
+ static OUString getPropertyValue(css::beans::NamedValue const& v);
+
+public:
+ ExtensionProperties(std::u16string_view urlExtension,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv,
+ css::uno::Reference<css::uno::XComponentContext> const& xContext);
+
+ ExtensionProperties(std::u16string_view urlExtension,
+ css::uno::Sequence<css::beans::NamedValue> const& properties,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv,
+ css::uno::Reference<css::uno::XComponentContext> const& xContext);
+
+ void write();
+
+ bool isSuppressedLicense() const;
+
+ bool isExtensionUpdate() const;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_dependencies.cxx b/desktop/source/deployment/misc/dp_dependencies.cxx
new file mode 100644
index 0000000000..eea3cfba9c
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_dependencies.cxx
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/xml/dom/XElement.hpp>
+#include <com/sun/star/xml/dom/XNodeList.hpp>
+#include <osl/diagnose.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <unotools/configmgr.hxx>
+
+#include <strings.hrc>
+#include <dp_shared.hxx>
+
+#include <dp_dependencies.hxx>
+#include <dp_descriptioninfoset.hxx>
+#include <dp_version.hxx>
+
+namespace {
+
+char const namespaceLibreOffice[] =
+ "http://libreoffice.org/extensions/description/2011";
+
+constexpr OUString namespaceOpenOfficeOrg =
+ u"http://openoffice.org/extensions/description/2006"_ustr;
+
+char const minimalVersionLibreOffice[] = "LibreOffice-minimal-version";
+char const maximalVersionLibreOffice[] = "LibreOffice-maximal-version";
+
+constexpr OUString minimalVersionOpenOfficeOrg =
+ u"OpenOffice.org-minimal-version"_ustr;
+
+char const maximalVersionOpenOfficeOrg[] =
+ "OpenOffice.org-maximal-version";
+
+OUString getLibreOfficeMajorMinorMicro() {
+ return utl::ConfigManager::getAboutBoxProductVersion();
+}
+
+OUString getReferenceOpenOfficeOrgMajorMinor() {
+#ifdef ANDROID
+ // just hardcode the version
+ OUString v("4.1");
+#else
+ OUString v(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version")
+ ":Version:ReferenceOOoMajorMinor}");
+ rtl::Bootstrap::expandMacros(v); //TODO: check for failure
+#endif
+ return v;
+}
+
+bool satisfiesMinimalVersion(
+ std::u16string_view actual, std::u16string_view specified)
+{
+ return dp_misc::compareVersions(actual, specified) != dp_misc::LESS;
+}
+
+bool satisfiesMaximalVersion(
+ std::u16string_view actual, std::u16string_view specified)
+{
+ return dp_misc::compareVersions(actual, specified) != dp_misc::GREATER;
+}
+
+OUString produceErrorText(
+ OUString const & reason, OUString const & version)
+{
+ return reason.replaceFirst("%VERSION",
+ (version.isEmpty()
+ ? DpResId(RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN)
+ : version));
+}
+
+}
+
+namespace dp_misc::Dependencies {
+
+css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > >
+check(dp_misc::DescriptionInfoset const & infoset) {
+ css::uno::Reference< css::xml::dom::XNodeList > deps(
+ infoset.getDependencies());
+ sal_Int32 n = deps->getLength();
+ css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > >
+ unsatisfied(n);
+ auto unsatisfiedRange = asNonConstRange(unsatisfied);
+ sal_Int32 unsat = 0;
+ // check first if minimalVersionLibreOffice is specified -- in that case ignore the legacy OOo dependencies
+ bool bIgnoreOoo = false;
+ for (sal_Int32 i = 0; i < n; ++i) {
+ css::uno::Reference< css::xml::dom::XElement > e(
+ deps->item(i), css::uno::UNO_QUERY_THROW);
+ if ( e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == minimalVersionLibreOffice)
+ {
+ bIgnoreOoo = true;
+ break;
+ }
+ }
+ for (sal_Int32 i = 0; i < n; ++i) {
+ css::uno::Reference< css::xml::dom::XElement > e(
+ deps->item(i), css::uno::UNO_QUERY_THROW);
+ bool sat = false;
+ if ( e->getNamespaceURI() == namespaceOpenOfficeOrg && e->getTagName() == minimalVersionOpenOfficeOrg )
+ {
+ sat = bIgnoreOoo || satisfiesMinimalVersion(
+ getReferenceOpenOfficeOrgMajorMinor(),
+ e->getAttribute("value"));
+ } else if ( e->getNamespaceURI() == namespaceOpenOfficeOrg && e->getTagName() == maximalVersionOpenOfficeOrg )
+ {
+ sat = bIgnoreOoo || satisfiesMaximalVersion(
+ getReferenceOpenOfficeOrgMajorMinor(),
+ e->getAttribute("value"));
+ } else if (e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == minimalVersionLibreOffice )
+ {
+ sat = satisfiesMinimalVersion(
+ getLibreOfficeMajorMinorMicro(),
+ e->getAttribute("value"));
+ } else if (e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == maximalVersionLibreOffice )
+ {
+ sat = satisfiesMaximalVersion(getLibreOfficeMajorMinorMicro(), e->getAttribute("value"));
+ } else if (e->hasAttributeNS(namespaceOpenOfficeOrg,
+ minimalVersionOpenOfficeOrg))
+ {
+ sat = satisfiesMinimalVersion(
+ getReferenceOpenOfficeOrgMajorMinor(),
+ e->getAttributeNS(namespaceOpenOfficeOrg,
+ minimalVersionOpenOfficeOrg));
+ }
+ if (!sat) {
+ unsatisfiedRange[unsat++] = e;
+ }
+ }
+ unsatisfied.realloc(unsat);
+ return unsatisfied;
+}
+
+OUString getErrorText(
+ css::uno::Reference< css::xml::dom::XElement > const & dependency)
+{
+ OSL_ASSERT(dependency.is());
+ if ( dependency->getNamespaceURI() == namespaceOpenOfficeOrg && dependency->getTagName() == minimalVersionOpenOfficeOrg )
+ {
+ return produceErrorText(
+ DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN),
+ dependency->getAttribute("value"));
+ } else if (dependency->getNamespaceURI() == namespaceOpenOfficeOrg && dependency->getTagName() == maximalVersionOpenOfficeOrg )
+ {
+ return produceErrorText(
+ DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MAX),
+ dependency->getAttribute("value"));
+ } else if (dependency->getNamespaceURI() == namespaceLibreOffice && dependency->getTagName() == minimalVersionLibreOffice )
+ {
+ return produceErrorText(
+ DpResId(RID_DEPLOYMENT_DEPENDENCIES_LO_MIN),
+ dependency->getAttribute("value"));
+ } else if (dependency->getNamespaceURI() == namespaceLibreOffice && dependency->getTagName() == maximalVersionLibreOffice )
+ {
+ return produceErrorText(
+ DpResId(RID_DEPLOYMENT_DEPENDENCIES_LO_MAX),
+ dependency->getAttribute("value"));
+ } else if (dependency->hasAttributeNS(namespaceOpenOfficeOrg,
+ minimalVersionOpenOfficeOrg))
+ {
+ return produceErrorText(
+ DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN),
+ dependency->getAttributeNS(namespaceOpenOfficeOrg,
+ minimalVersionOpenOfficeOrg));
+ } else {
+ return DpResId(RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN);
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_descriptioninfoset.cxx b/desktop/source/deployment/misc/dp_descriptioninfoset.cxx
new file mode 100644
index 0000000000..00b32c04f2
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_descriptioninfoset.cxx
@@ -0,0 +1,809 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <dp_descriptioninfoset.hxx>
+
+#include <dp_resource.h>
+
+#include <comphelper/sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <optional>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/io/SequenceInputStream.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/xml/dom/DOMException.hpp>
+#include <com/sun/star/xml/dom/XNode.hpp>
+#include <com/sun/star/xml/dom/XNodeList.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/xpath/XPathAPI.hpp>
+#include <com/sun/star/xml/xpath/XPathException.hpp>
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weak.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <ucbhelper/content.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace {
+
+using css::uno::Reference;
+
+class EmptyNodeList:
+ public cppu::WeakImplHelper<css::xml::dom::XNodeList>
+{
+public:
+ EmptyNodeList();
+
+ EmptyNodeList(const EmptyNodeList&) = delete;
+ const EmptyNodeList& operator=(const EmptyNodeList&) = delete;
+
+ virtual ::sal_Int32 SAL_CALL getLength() override;
+
+ virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL
+ item(::sal_Int32 index) override;
+};
+
+EmptyNodeList::EmptyNodeList() {}
+
+::sal_Int32 EmptyNodeList::getLength() {
+ return 0;
+}
+
+css::uno::Reference< css::xml::dom::XNode > EmptyNodeList::item(::sal_Int32)
+{
+ throw css::uno::RuntimeException("bad EmptyNodeList com.sun.star.xml.dom.XNodeList.item call",
+ static_cast< ::cppu::OWeakObject * >(this));
+}
+
+OUString getNodeValue(
+ css::uno::Reference< css::xml::dom::XNode > const & node)
+{
+ OSL_ASSERT(node.is());
+ try {
+ return node->getNodeValue();
+ } catch (const css::xml::dom::DOMException & e) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "com.sun.star.xml.dom.DOMException: " + e.Message,
+ nullptr, anyEx );
+ }
+}
+
+/**The class uses the UCB to access the description.xml file in an
+ extension. The UCB must have been initialized already. It also
+ requires that the extension has already be unzipped to a particular
+ location.
+ */
+class ExtensionDescription
+{
+public:
+ /**throws an exception if the description.xml is not
+ available, cannot be read, does not contain the expected data,
+ or any other error occurred. Therefore it should only be used with
+ new extensions.
+
+ Throws css::uno::RuntimeException,
+ css::deployment::DeploymentException,
+ dp_registry::backend::bundle::NoDescriptionException.
+ */
+ ExtensionDescription(
+ const css::uno::Reference<css::uno::XComponentContext>& xContext,
+ std::u16string_view installDir,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv);
+
+ const css::uno::Reference<css::xml::dom::XNode>& getRootElement() const
+ {
+ return m_xRoot;
+ }
+
+private:
+ css::uno::Reference<css::xml::dom::XNode> m_xRoot;
+};
+
+class NoDescriptionException
+{
+};
+
+class FileDoesNotExistFilter
+ : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
+ css::task::XInteractionHandler >
+
+{
+ bool m_bExist;
+ css::uno::Reference< css::ucb::XCommandEnvironment > m_xCommandEnv;
+
+public:
+ explicit FileDoesNotExistFilter(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv);
+
+ bool exist() { return m_bExist;}
+ // XCommandEnvironment
+ virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual css::uno::Reference<css::ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override;
+};
+
+ExtensionDescription::ExtensionDescription(
+ const Reference<css::uno::XComponentContext>& xContext,
+ std::u16string_view installDir,
+ const Reference< css::ucb::XCommandEnvironment >& xCmdEnv)
+{
+ try {
+ //may throw css::ucb::ContentCreationException
+ //If there is no description.xml then ucb will start an interaction which
+ //brings up a dialog.We want to prevent this. Therefore we wrap the xCmdEnv
+ //and filter the respective exception out.
+ OUString sDescriptionUri(OUString::Concat(installDir) + "/description.xml");
+ Reference<css::ucb::XCommandEnvironment> xFilter = new FileDoesNotExistFilter(xCmdEnv);
+ ::ucbhelper::Content descContent(sDescriptionUri, xFilter, xContext);
+
+ //throws a css::uno::Exception if the file is not available
+ Reference<css::io::XInputStream> xIn;
+ try
+ { //throws com.sun.star.ucb.InteractiveIOException
+ xIn = descContent.openStream();
+ }
+ catch ( const css::uno::Exception& )
+ {
+ if ( ! static_cast<FileDoesNotExistFilter*>(xFilter.get())->exist())
+ throw NoDescriptionException();
+ throw;
+ }
+ if (!xIn.is())
+ {
+ throw css::uno::Exception(
+ "Could not get XInputStream for description.xml of extension " +
+ sDescriptionUri, nullptr);
+ }
+
+ //get root node of description.xml
+ Reference<css::xml::dom::XDocumentBuilder> xDocBuilder(
+ css::xml::dom::DocumentBuilder::create(xContext) );
+
+ if (!xDocBuilder->isNamespaceAware())
+ {
+ throw css::uno::Exception(
+ "Service com.sun.star.xml.dom.DocumentBuilder is not namespace aware.", nullptr);
+ }
+
+ Reference<css::xml::dom::XDocument> xDoc = xDocBuilder->parse(xIn);
+ if (!xDoc.is())
+ {
+ throw css::uno::Exception(sDescriptionUri + " contains data which cannot be parsed. ", nullptr);
+ }
+
+ //check for proper root element and namespace
+ Reference<css::xml::dom::XElement> xRoot = xDoc->getDocumentElement();
+ if (!xRoot.is())
+ {
+ throw css::uno::Exception(
+ sDescriptionUri + " contains no root element.", nullptr);
+ }
+
+ if ( xRoot->getTagName() != "description")
+ {
+ throw css::uno::Exception(
+ sDescriptionUri + " does not contain the root element <description>.", nullptr);
+ }
+
+ m_xRoot.set(xRoot, css::uno::UNO_QUERY_THROW);
+ OUString nsDescription = xRoot->getNamespaceURI();
+
+ //check if this namespace is supported
+ if ( nsDescription != "http://openoffice.org/extensions/description/2006")
+ {
+ throw css::uno::Exception(sDescriptionUri + " contains a root element with an unsupported namespace. ", nullptr);
+ }
+ } catch (const css::uno::RuntimeException &) {
+ throw;
+ } catch (const css::deployment::DeploymentException &) {
+ throw;
+ } catch (const css::uno::Exception & e) {
+ css::uno::Any a(cppu::getCaughtException());
+ throw css::deployment::DeploymentException(
+ e.Message, Reference< css::uno::XInterface >(), a);
+ }
+}
+
+FileDoesNotExistFilter::FileDoesNotExistFilter(
+ const Reference< css::ucb::XCommandEnvironment >& xCmdEnv):
+ m_bExist(true), m_xCommandEnv(xCmdEnv)
+{}
+
+ // XCommandEnvironment
+Reference<css::task::XInteractionHandler >
+ FileDoesNotExistFilter::getInteractionHandler()
+{
+ return static_cast<css::task::XInteractionHandler*>(this);
+}
+
+Reference<css::ucb::XProgressHandler >
+ FileDoesNotExistFilter::getProgressHandler()
+{
+ return m_xCommandEnv.is()
+ ? m_xCommandEnv->getProgressHandler()
+ : Reference<css::ucb::XProgressHandler>();
+}
+
+// XInteractionHandler
+//If the interaction was caused by a non-existing file which is specified in the ctor
+//of FileDoesNotExistFilter, then we do nothing
+void FileDoesNotExistFilter::handle(
+ Reference<css::task::XInteractionRequest > const & xRequest )
+{
+ css::uno::Any request( xRequest->getRequest() );
+
+ css::ucb::InteractiveIOException ioexc;
+ if ((request>>= ioexc)
+ && (ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING
+ || ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING_PATH))
+ {
+ m_bExist = false;
+ return;
+ }
+ Reference<css::task::XInteractionHandler> xInteraction;
+ if (m_xCommandEnv.is()) {
+ xInteraction = m_xCommandEnv->getInteractionHandler();
+ }
+ if (xInteraction.is()) {
+ xInteraction->handle(xRequest);
+ }
+}
+
+}
+
+namespace dp_misc {
+
+DescriptionInfoset getDescriptionInfoset(std::u16string_view sExtensionFolderURL)
+{
+ Reference< css::xml::dom::XNode > root;
+ Reference<css::uno::XComponentContext> context(
+ comphelper::getProcessComponentContext());
+ try {
+ root =
+ ExtensionDescription(
+ context, sExtensionFolderURL,
+ Reference< css::ucb::XCommandEnvironment >()).
+ getRootElement();
+ } catch (const NoDescriptionException &) {
+ } catch (const css::deployment::DeploymentException & e) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "com.sun.star.deployment.DeploymentException: " + e.Message,
+ nullptr, anyEx );
+ }
+ return DescriptionInfoset(context, root);
+}
+
+DescriptionInfoset::DescriptionInfoset(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ css::uno::Reference< css::xml::dom::XNode > const & element):
+ m_context(context),
+ m_element(element)
+{
+ if (m_element.is()) {
+ m_xpath = css::xml::xpath::XPathAPI::create(context);
+ m_xpath->registerNS("desc", element->getNamespaceURI());
+ m_xpath->registerNS("xlink", "http://www.w3.org/1999/xlink");
+ }
+}
+
+DescriptionInfoset::~DescriptionInfoset() {}
+
+::std::optional< OUString > DescriptionInfoset::getIdentifier() const {
+ return getOptionalValue("desc:identifier/@value");
+}
+
+OUString DescriptionInfoset::getNodeValueFromExpression(OUString const & expression) const
+{
+ css::uno::Reference< css::xml::dom::XNode > n;
+ if (m_element.is()) {
+ try {
+ n = m_xpath->selectSingleNode(m_element, expression);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ return n.is() ? getNodeValue(n) : OUString();
+}
+
+void DescriptionInfoset::checkDenylist() const
+{
+ if (!m_element.is())
+ return;
+
+ std::optional< OUString > id(getIdentifier());
+ if (!id)
+ return; // nothing to check
+ OUString currentversion(getVersion());
+ if (currentversion.getLength() == 0)
+ return; // nothing to check
+
+ css::uno::Sequence<css::uno::Any> args(comphelper::InitAnyPropertySequence(
+ {
+ {"nodepath", css::uno::Any(OUString("/org.openoffice.Office.ExtensionDependencies/Extensions"))}
+ }));
+ css::uno::Reference< css::container::XNameAccess > denylist(
+ (css::configuration::theDefaultProvider::get(m_context)
+ ->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess", args)),
+ css::uno::UNO_QUERY_THROW);
+
+ // check first if a denylist entry is available
+ if (!(denylist.is() && denylist->hasByName(*id))) return;
+
+ css::uno::Reference< css::beans::XPropertySet > extProps(
+ denylist->getByName(*id), css::uno::UNO_QUERY_THROW);
+
+ css::uno::Any anyValue = extProps->getPropertyValue("Versions");
+
+ css::uno::Sequence< OUString > blversions;
+ anyValue >>= blversions;
+
+ // check if the current version requires further dependency checks from the denylist
+ if (!checkDenylistVersion(currentversion, blversions)) return;
+
+ anyValue = extProps->getPropertyValue("Dependencies");
+ OUString udeps;
+ anyValue >>= udeps;
+
+ if (udeps.getLength() == 0)
+ return; // nothing todo
+
+ OString xmlDependencies = OUStringToOString(udeps, RTL_TEXTENCODING_UNICODE);
+
+ css::uno::Reference< css::xml::dom::XDocumentBuilder> docbuilder(
+ m_context->getServiceManager()->createInstanceWithContext("com.sun.star.xml.dom.DocumentBuilder", m_context),
+ css::uno::UNO_QUERY_THROW);
+
+ css::uno::Sequence< sal_Int8 > byteSeq(reinterpret_cast<const sal_Int8*>(xmlDependencies.getStr()), xmlDependencies.getLength());
+
+ css::uno::Reference< css::io::XInputStream> inputstream( css::io::SequenceInputStream::createStreamFromSequence(m_context, byteSeq),
+ css::uno::UNO_QUERY_THROW);
+
+ css::uno::Reference< css::xml::dom::XDocument > xDocument(docbuilder->parse(inputstream));
+ css::uno::Reference< css::xml::dom::XElement > xElement(xDocument->getDocumentElement());
+ css::uno::Reference< css::xml::dom::XNodeList > xDeps(xElement->getChildNodes());
+ sal_Int32 nLen = xDeps->getLength();
+
+ // get the parent xml document of current description info for the import
+ css::uno::Reference< css::xml::dom::XDocument > xCurrentDescInfo(m_element->getOwnerDocument());
+
+ // get dependency node of current description info to merge the new dependencies from the denylist
+ css::uno::Reference< css::xml::dom::XNode > xCurrentDeps(
+ m_xpath->selectSingleNode(m_element, "desc:dependencies"));
+
+ // if no dependency node exists, create a new one in the current description info
+ if (!xCurrentDeps.is()) {
+ css::uno::Reference< css::xml::dom::XNode > xNewDepNode(
+ xCurrentDescInfo->createElementNS(
+ "http://openoffice.org/extensions/description/2006",
+ "dependencies"), css::uno::UNO_QUERY_THROW);
+ m_element->appendChild(xNewDepNode);
+ xCurrentDeps = m_xpath->selectSingleNode(m_element, "desc:dependencies");
+ }
+
+ for (sal_Int32 i=0; i<nLen; i++) {
+ css::uno::Reference< css::xml::dom::XNode > xNode(xDeps->item(i));
+ css::uno::Reference< css::xml::dom::XElement > xDep(xNode, css::uno::UNO_QUERY);
+ if (xDep.is()) {
+ // found valid denylist dependency, import the node first and append it to the existing dependency node
+ css::uno::Reference< css::xml::dom::XNode > importedNode = xCurrentDescInfo->importNode(xNode, true);
+ xCurrentDeps->appendChild(importedNode);
+ }
+ }
+}
+
+bool DescriptionInfoset::checkDenylistVersion(
+ std::u16string_view currentversion,
+ css::uno::Sequence< OUString > const & versions)
+{
+ sal_Int32 nLen = versions.getLength();
+ for (sal_Int32 i=0; i<nLen; i++) {
+ if (currentversion == versions[i])
+ return true;
+ }
+
+ return false;
+}
+
+OUString DescriptionInfoset::getVersion() const
+{
+ return getNodeValueFromExpression( "desc:version/@value" );
+}
+
+css::uno::Sequence< OUString > DescriptionInfoset::getSupportedPlatforms() const
+{
+ //When there is no description.xml then we assume that we support all platforms
+ if (! m_element.is())
+ {
+ return { OUString("all") };
+ }
+
+ //Check if the <platform> element was provided. If not the default is "all" platforms
+ css::uno::Reference< css::xml::dom::XNode > nodePlatform(
+ m_xpath->selectSingleNode(m_element, "desc:platform"));
+ if (!nodePlatform.is())
+ {
+ return { OUString("all") };
+ }
+
+ //There is a platform element.
+ const OUString value = getNodeValueFromExpression("desc:platform/@value");
+ //parse the string, it can contained multiple strings separated by commas
+ std::vector< OUString> vec;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ const OUString aToken( o3tl::trim(o3tl::getToken(value, 0, ',', nIndex )) );
+ if (!aToken.isEmpty())
+ vec.push_back(aToken);
+
+ }
+ while (nIndex >= 0);
+
+ return comphelper::containerToSequence(vec);
+}
+
+css::uno::Reference< css::xml::dom::XNodeList >
+DescriptionInfoset::getDependencies() const {
+ if (m_element.is()) {
+ try {
+ // check the extension denylist first and expand the dependencies if applicable
+ checkDenylist();
+
+ return m_xpath->selectNodeList(m_element, "desc:dependencies/*");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ return new EmptyNodeList;
+}
+
+css::uno::Sequence< OUString >
+DescriptionInfoset::getUpdateInformationUrls() const {
+ return getUrls("desc:update-information/desc:src/@xlink:href");
+}
+
+css::uno::Sequence< OUString >
+DescriptionInfoset::getUpdateDownloadUrls() const
+{
+ return getUrls("desc:update-download/desc:src/@xlink:href");
+}
+
+OUString DescriptionInfoset::getIconURL( bool bHighContrast ) const
+{
+ css::uno::Sequence< OUString > aStrList = getUrls( "desc:icon/desc:default/@xlink:href" );
+ css::uno::Sequence< OUString > aStrListHC = getUrls( "desc:icon/desc:high-contrast/@xlink:href" );
+
+ if ( bHighContrast && aStrListHC.hasElements() && !aStrListHC[0].isEmpty() )
+ return aStrListHC[0];
+
+ if ( aStrList.hasElements() && !aStrList[0].isEmpty() )
+ return aStrList[0];
+
+ return OUString();
+}
+
+::std::optional< OUString > DescriptionInfoset::getLocalizedUpdateWebsiteURL()
+ const
+{
+ bool bParentExists = false;
+ const OUString sURL (getLocalizedHREFAttrFromChild("/desc:description/desc:update-website", &bParentExists ));
+
+ if (!sURL.isEmpty())
+ return ::std::optional< OUString >(sURL);
+ else
+ return bParentExists ? ::std::optional< OUString >(OUString()) :
+ ::std::optional< OUString >();
+}
+
+::std::optional< OUString > DescriptionInfoset::getOptionalValue(
+ OUString const & expression) const
+{
+ css::uno::Reference< css::xml::dom::XNode > n;
+ if (m_element.is()) {
+ try {
+ n = m_xpath->selectSingleNode(m_element, expression);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ return n.is()
+ ? ::std::optional< OUString >(getNodeValue(n))
+ : ::std::optional< OUString >();
+}
+
+css::uno::Sequence< OUString > DescriptionInfoset::getUrls(
+ OUString const & expression) const
+{
+ css::uno::Reference< css::xml::dom::XNodeList > ns;
+ if (m_element.is()) {
+ try {
+ ns = m_xpath->selectNodeList(m_element, expression);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ css::uno::Sequence< OUString > urls(ns.is() ? ns->getLength() : 0);
+ auto urlsRange = asNonConstRange(urls);
+ for (::sal_Int32 i = 0; i < urls.getLength(); ++i) {
+ urlsRange[i] = getNodeValue(ns->item(i));
+ }
+ return urls;
+}
+
+std::pair< OUString, OUString > DescriptionInfoset::getLocalizedPublisherNameAndURL() const
+{
+ css::uno::Reference< css::xml::dom::XNode > node =
+ getLocalizedChild("desc:publisher");
+
+ OUString sPublisherName;
+ OUString sURL;
+ if (node.is())
+ {
+ css::uno::Reference< css::xml::dom::XNode > xPathName;
+ try {
+ xPathName = m_xpath->selectSingleNode(node, "text()");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ OSL_ASSERT(xPathName.is());
+ if (xPathName.is())
+ sPublisherName = xPathName->getNodeValue();
+
+ css::uno::Reference< css::xml::dom::XNode > xURL;
+ try {
+ xURL = m_xpath->selectSingleNode(node, "@xlink:href");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ OSL_ASSERT(xURL.is());
+ if (xURL.is())
+ sURL = xURL->getNodeValue();
+ }
+ return std::make_pair(sPublisherName, sURL);
+}
+
+OUString DescriptionInfoset::getLocalizedReleaseNotesURL() const
+{
+ return getLocalizedHREFAttrFromChild("/desc:description/desc:release-notes", nullptr);
+}
+
+OUString DescriptionInfoset::getLocalizedDisplayName() const
+{
+ css::uno::Reference< css::xml::dom::XNode > node =
+ getLocalizedChild("desc:display-name");
+ if (node.is())
+ {
+ css::uno::Reference< css::xml::dom::XNode > xtext;
+ try {
+ xtext = m_xpath->selectSingleNode(node, "text()");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ if (xtext.is())
+ return xtext->getNodeValue();
+ }
+ return OUString();
+}
+
+OUString DescriptionInfoset::getLocalizedLicenseURL() const
+{
+ return getLocalizedHREFAttrFromChild("/desc:description/desc:registration/desc:simple-license", nullptr);
+
+}
+
+::std::optional<SimpleLicenseAttributes>
+DescriptionInfoset::getSimpleLicenseAttributes() const
+{
+ //Check if the node exist
+ css::uno::Reference< css::xml::dom::XNode > n;
+ if (m_element.is()) {
+ try {
+ n = m_xpath->selectSingleNode(m_element, "/desc:description/desc:registration/desc:simple-license/@accept-by");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ if (n.is())
+ {
+ SimpleLicenseAttributes attributes;
+ attributes.acceptBy =
+ getNodeValueFromExpression("/desc:description/desc:registration/desc:simple-license/@accept-by");
+
+ ::std::optional< OUString > suppressOnUpdate = getOptionalValue("/desc:description/desc:registration/desc:simple-license/@suppress-on-update");
+ if (suppressOnUpdate)
+ attributes.suppressOnUpdate = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressOnUpdate), u"true");
+ else
+ attributes.suppressOnUpdate = false;
+
+ ::std::optional< OUString > suppressIfRequired = getOptionalValue("/desc:description/desc:registration/desc:simple-license/@suppress-if-required");
+ if (suppressIfRequired)
+ attributes.suppressIfRequired = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressIfRequired), u"true");
+ else
+ attributes.suppressIfRequired = false;
+
+ return ::std::optional<SimpleLicenseAttributes>(attributes);
+ }
+ }
+ return ::std::optional<SimpleLicenseAttributes>();
+}
+
+OUString DescriptionInfoset::getLocalizedDescriptionURL() const
+{
+ return getLocalizedHREFAttrFromChild("/desc:description/desc:extension-description", nullptr);
+}
+
+css::uno::Reference< css::xml::dom::XNode >
+DescriptionInfoset::getLocalizedChild( const OUString & sParent) const
+{
+ if ( ! m_element.is() || sParent.isEmpty())
+ return css::uno::Reference< css::xml::dom::XNode > ();
+
+ css::uno::Reference< css::xml::dom::XNode > xParent;
+ try {
+ xParent = m_xpath->selectSingleNode(m_element, sParent);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ css::uno::Reference<css::xml::dom::XNode> nodeMatch;
+ if (xParent.is())
+ {
+ nodeMatch = matchLanguageTag(xParent, getOfficeLanguageTag().getBcp47());
+
+ //office: en-DE, en, en-DE-altmark
+ if (! nodeMatch.is())
+ {
+ // Already tried full tag, continue with first fallback.
+ const std::vector< OUString > aFallbacks( getOfficeLanguageTag().getFallbackStrings( false));
+ for (auto const& fallback : aFallbacks)
+ {
+ nodeMatch = matchLanguageTag(xParent, fallback);
+ if (nodeMatch.is())
+ break;
+ }
+ if (! nodeMatch.is())
+ nodeMatch = getChildWithDefaultLocale(xParent);
+ }
+ }
+
+ return nodeMatch;
+}
+
+css::uno::Reference<css::xml::dom::XNode>
+DescriptionInfoset::matchLanguageTag(
+ css::uno::Reference< css::xml::dom::XNode > const & xParent, std::u16string_view rTag) const
+{
+ OSL_ASSERT(xParent.is());
+ css::uno::Reference<css::xml::dom::XNode> nodeMatch;
+
+ //first try exact match for lang
+ const OUString exp1(OUString::Concat("*[@lang=\"") + rTag + "\"]");
+ try {
+ nodeMatch = m_xpath->selectSingleNode(xParent, exp1);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+
+ //try to match in strings that also have a country and/or variant, for
+ //example en matches in en-US-montana, en-US, en-montana
+ if (!nodeMatch.is())
+ {
+ const OUString exp2(
+ OUString::Concat("*[starts-with(@lang,\"") + rTag + "-\")]");
+ try {
+ nodeMatch = m_xpath->selectSingleNode(xParent, exp2);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ return nodeMatch;
+}
+
+css::uno::Reference<css::xml::dom::XNode>
+DescriptionInfoset::getChildWithDefaultLocale(css::uno::Reference< css::xml::dom::XNode >
+ const & xParent) const
+{
+ OSL_ASSERT(xParent.is());
+ if ( xParent->getNodeName() == "simple-license" )
+ {
+ css::uno::Reference<css::xml::dom::XNode> nodeDefault;
+ try {
+ nodeDefault = m_xpath->selectSingleNode(xParent, "@default-license-id");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ if (nodeDefault.is())
+ {
+ //The old way
+ const OUString exp1("desc:license-text[@license-id = \""
+ + nodeDefault->getNodeValue()
+ + "\"]");
+ try {
+ return m_xpath->selectSingleNode(xParent, exp1);
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ }
+ }
+
+ try {
+ return m_xpath->selectSingleNode(xParent, "*[1]");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ return nullptr;
+ }
+}
+
+OUString DescriptionInfoset::getLocalizedHREFAttrFromChild(
+ OUString const & sXPathParent, bool * out_bParentExists)
+ const
+{
+ css::uno::Reference< css::xml::dom::XNode > node =
+ getLocalizedChild(sXPathParent);
+
+ OUString sURL;
+ if (node.is())
+ {
+ if (out_bParentExists)
+ *out_bParentExists = true;
+ css::uno::Reference< css::xml::dom::XNode > xURL;
+ try {
+ xURL = m_xpath->selectSingleNode(node, "@xlink:href");
+ } catch (const css::xml::xpath::XPathException &) {
+ // ignore
+ }
+ OSL_ASSERT(xURL.is());
+ if (xURL.is())
+ sURL = xURL->getNodeValue();
+ }
+ else
+ {
+ if (out_bParentExists)
+ *out_bParentExists = false;
+ }
+ return sURL;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_identifier.cxx b/desktop/source/deployment/misc/dp_identifier.cxx
new file mode 100644
index 0000000000..8669710c7b
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_identifier.cxx
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/config.h>
+
+#include <optional>
+#include <com/sun/star/beans/Optional.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustring.hxx>
+
+#include <dp_identifier.hxx>
+
+namespace dp_misc {
+
+OUString generateIdentifier(
+ ::std::optional< OUString > const & optional,
+ std::u16string_view fileName)
+{
+ return optional ? *optional : generateLegacyIdentifier(fileName);
+}
+
+OUString getIdentifier(
+ css::uno::Reference< css::deployment::XPackage > const & package)
+{
+ OSL_ASSERT(package.is());
+ css::beans::Optional< OUString > id(package->getIdentifier());
+ return id.IsPresent
+ ? id.Value : generateLegacyIdentifier(package->getName());
+}
+
+OUString generateLegacyIdentifier(std::u16string_view fileName) {
+ return OUString::Concat("org.openoffice.legacy.") + fileName;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_interact.cxx b/desktop/source/deployment/misc/dp_interact.cxx
new file mode 100644
index 0000000000..ae928a28e8
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_interact.cxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <dp_interact.h>
+
+#include <comphelper/interaction.hxx>
+
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <osl/diagnose.h>
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_misc {
+namespace {
+
+
+class InteractionContinuationImpl : public ::cppu::OWeakObject,
+ public task::XInteractionContinuation
+{
+ const Type m_type;
+ bool * m_pselect;
+
+public:
+ InteractionContinuationImpl( Type const & type, bool * pselect )
+ : m_type( type ),
+ m_pselect( pselect )
+ { OSL_ASSERT(
+ cppu::UnoType<task::XInteractionContinuation>::get().isAssignableFrom(m_type) ); }
+
+ // XInterface
+ virtual void SAL_CALL acquire() noexcept override;
+ virtual void SAL_CALL release() noexcept override;
+ virtual Any SAL_CALL queryInterface( Type const & type ) override;
+
+ // XInteractionContinuation
+ virtual void SAL_CALL select() override;
+};
+
+// XInterface
+
+void InteractionContinuationImpl::acquire() noexcept
+{
+ OWeakObject::acquire();
+}
+
+
+void InteractionContinuationImpl::release() noexcept
+{
+ OWeakObject::release();
+}
+
+
+Any InteractionContinuationImpl::queryInterface( Type const & type )
+{
+ if (type.isAssignableFrom( m_type )) {
+ Reference<task::XInteractionContinuation> xThis(this);
+ return Any( &xThis, type );
+ }
+ else
+ return OWeakObject::queryInterface(type);
+}
+
+// XInteractionContinuation
+
+void InteractionContinuationImpl::select()
+{
+ *m_pselect = true;
+}
+
+} // anon namespace
+
+
+bool interactContinuation( Any const & request,
+ Type const & continuation,
+ Reference<XCommandEnvironment> const & xCmdEnv,
+ bool * pcont, bool * pabort )
+{
+ OSL_ASSERT(
+ cppu::UnoType<task::XInteractionContinuation>::get().isAssignableFrom(
+ continuation ) );
+ if (!xCmdEnv)
+ return false;
+
+ Reference<task::XInteractionHandler> xInteractionHandler(
+ xCmdEnv->getInteractionHandler() );
+ if (!xInteractionHandler)
+ return false;
+
+ bool cont = false;
+ bool abort = false;
+ std::vector< Reference<task::XInteractionContinuation> > conts {
+ new InteractionContinuationImpl(continuation, &cont ),
+ new InteractionContinuationImpl( cppu::UnoType<task::XInteractionAbort>::get(), &abort ) };
+ xInteractionHandler->handle(
+ new ::comphelper::OInteractionRequest( request, std::move(conts) ) );
+ if (cont || abort) {
+ if (pcont != nullptr)
+ *pcont = cont;
+ if (pabort != nullptr)
+ *pabort = abort;
+ return true;
+ }
+ return false;
+}
+
+// XAbortChannel
+
+void AbortChannel::sendAbort()
+{
+ m_aborted = true;
+ if (m_xNext.is())
+ m_xNext->sendAbort();
+}
+
+} // dp_misc
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_misc.cxx b/desktop/source/deployment/misc/dp_misc.cxx
new file mode 100644
index 0000000000..01fb414a79
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_misc.cxx
@@ -0,0 +1,562 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+#include <config_features.h>
+#include <chrono>
+
+#include <dp_misc.h>
+#include <dp_interact.h>
+#include <dp_shared.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/digest.h>
+#include <rtl/random.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <unotools/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <osl/pipe.hxx>
+#include <osl/security.hxx>
+#include <osl/thread.hxx>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/bridge/BridgeFactory.hpp>
+#include <com/sun/star/bridge/UnoUrlResolver.hpp>
+#include <com/sun/star/bridge/XUnoUrlResolver.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/task/OfficeRestartManager.hpp>
+#include <memory>
+#include <string_view>
+#include <thread>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#ifdef _WIN32
+#include <prewin.h>
+#include <postwin.h>
+#endif
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_misc {
+namespace {
+
+std::shared_ptr<rtl::Bootstrap> & UnoRc()
+{
+ static std::shared_ptr<rtl::Bootstrap> theRc = []()
+ {
+ OUString unorc( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("louno") );
+ ::rtl::Bootstrap::expandMacros( unorc );
+ auto ret = std::make_shared<::rtl::Bootstrap>( unorc );
+ OSL_ASSERT( ret->getHandle() != nullptr );
+ return ret;
+ }();
+ return theRc;
+};
+
+OUString generateOfficePipeId()
+{
+ OUString userPath;
+ ::utl::Bootstrap::PathStatus aLocateResult =
+ ::utl::Bootstrap::locateUserInstallation( userPath );
+ if (aLocateResult != ::utl::Bootstrap::PATH_EXISTS &&
+ aLocateResult != ::utl::Bootstrap::PATH_VALID)
+ {
+ throw Exception("Extension Manager: Could not obtain path for UserInstallation.", nullptr);
+ }
+
+ rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
+ if (!digest) {
+ throw RuntimeException("cannot get digest rtl_Digest_AlgorithmMD5!", nullptr );
+ }
+
+ sal_uInt8 const * data =
+ reinterpret_cast<sal_uInt8 const *>(userPath.getStr());
+ std::size_t size = userPath.getLength() * sizeof (sal_Unicode);
+ sal_uInt32 md5_key_len = rtl_digest_queryLength( digest );
+ std::unique_ptr<sal_uInt8[]> md5_buf( new sal_uInt8 [ md5_key_len ] );
+
+ rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) );
+ rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) );
+ rtl_digest_get( digest, md5_buf.get(), md5_key_len );
+ rtl_digest_destroy( digest );
+
+ // create hex-value string from the MD5 value to keep
+ // the string size minimal
+ OUStringBuffer buf( "SingleOfficeIPC_" );
+ for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) {
+ buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 );
+ }
+ return buf.makeStringAndClear();
+}
+
+bool existsOfficePipe()
+{
+ static OUString OfficePipeId = generateOfficePipeId();
+
+ OUString const & pipeId = OfficePipeId;
+ if (pipeId.isEmpty())
+ return false;
+ ::osl::Security sec;
+ ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec );
+ return pipe.is();
+}
+
+//get modification time
+bool getModifyTimeTargetFile(const OUString &rFileURL, TimeValue &rTime)
+{
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_ModifyTime);
+
+ if (aResolver.fetchFileStatus(rFileURL) != osl::FileBase::E_None)
+ return false;
+
+ rTime = aResolver.m_aStatus.getModifyTime();
+
+ return true;
+}
+
+//Returns true if the Folder was more recently modified then
+//the lastsynchronized file. That is the repository needs to
+//be synchronized.
+bool compareExtensionFolderWithLastSynchronizedFile(
+ OUString const & folderURL, OUString const & fileURL)
+{
+ bool bNeedsSync = false;
+ ::osl::DirectoryItem itemExtFolder;
+ ::osl::File::RC err1 =
+ ::osl::DirectoryItem::get(folderURL, itemExtFolder);
+ //If it does not exist, then there is nothing to be done
+ if (err1 == ::osl::File::E_NOENT)
+ {
+ return false;
+ }
+ else if (err1 != ::osl::File::E_None)
+ {
+ OSL_FAIL("Cannot access extension folder");
+ return true; //sync just in case
+ }
+
+ //If last synchronized does not exist, then OOo is started for the first time
+ ::osl::DirectoryItem itemFile;
+ ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile);
+ if (err2 == ::osl::File::E_NOENT)
+ {
+ return true;
+
+ }
+ else if (err2 != ::osl::File::E_None)
+ {
+ OSL_FAIL("Cannot access file lastsynchronized");
+ return true; //sync just in case
+ }
+
+ //compare the modification time of the extension folder and the last
+ //modified file
+ TimeValue timeFolder;
+ if (getModifyTimeTargetFile(folderURL, timeFolder))
+ {
+ TimeValue timeFile;
+ if (getModifyTimeTargetFile(fileURL, timeFile))
+ {
+ if (timeFile.Seconds < timeFolder.Seconds)
+ bNeedsSync = true;
+ }
+ else
+ {
+ OSL_ASSERT(false);
+ bNeedsSync = true;
+ }
+ }
+ else
+ {
+ OSL_ASSERT(false);
+ bNeedsSync = true;
+ }
+
+ return bNeedsSync;
+}
+
+bool needToSyncRepository(std::u16string_view name)
+{
+ OUString folder;
+ OUString file;
+ if ( name == u"bundled" )
+ {
+ folder = "$BUNDLED_EXTENSIONS";
+ file = "$BUNDLED_EXTENSIONS_USER/lastsynchronized";
+ }
+ else if ( name == u"shared" )
+ {
+ folder = "$UNO_SHARED_PACKAGES_CACHE/uno_packages";
+ file = "$SHARED_EXTENSIONS_USER/lastsynchronized";
+ }
+ else
+ {
+ OSL_ASSERT(false);
+ return true;
+ }
+ ::rtl::Bootstrap::expandMacros(folder);
+ ::rtl::Bootstrap::expandMacros(file);
+ return compareExtensionFolderWithLastSynchronizedFile(
+ folder, file);
+}
+
+
+} // anon namespace
+
+
+namespace {
+OUString encodeForRcFile( std::u16string_view str )
+{
+ // escape $\{} (=> rtl bootstrap files)
+ OUStringBuffer buf(64);
+ size_t pos = 0;
+ const size_t len = str.size();
+ for ( ; pos < len; ++pos ) {
+ sal_Unicode c = str[ pos ];
+ switch (c) {
+ case '$':
+ case '\\':
+ case '{':
+ case '}':
+ buf.append( '\\' );
+ break;
+ }
+ buf.append( c );
+ }
+ return buf.makeStringAndClear();
+}
+}
+
+
+OUString makeURL( std::u16string_view baseURL, OUString const & relPath_ )
+{
+ OUStringBuffer buf(128);
+ if (baseURL.size() > 1 && baseURL[ baseURL.size() - 1 ] == '/')
+ buf.append( baseURL.substr(0, baseURL.size() - 1) );
+ else
+ buf.append( baseURL );
+ OUString relPath(relPath_);
+ if( relPath.startsWith("/") )
+ relPath = relPath.copy( 1 );
+ if (!relPath.isEmpty())
+ {
+ buf.append( '/' );
+ if (o3tl::starts_with(baseURL, u"vnd.sun.star.expand:" )) {
+ // encode for macro expansion: relPath is supposed to have no
+ // macros, so encode $, {} \ (bootstrap mimic)
+ relPath = encodeForRcFile(relPath);
+
+ // encode once more for vnd.sun.star.expand schema:
+ // vnd.sun.star.expand:$UNO_...
+ // will expand to file-url
+ relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ }
+ buf.append( relPath );
+ }
+ return buf.makeStringAndClear();
+}
+
+OUString makeURLAppendSysPathSegment( std::u16string_view baseURL, OUString const & segment )
+{
+ OSL_ASSERT(segment.indexOf(u'/') == -1);
+
+ ::rtl::Uri::encode(
+ segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8);
+ return makeURL(baseURL, segment);
+}
+
+
+OUString expandUnoRcTerm( OUString const & term_ )
+{
+ OUString term(term_);
+ UnoRc()->expandMacrosFrom( term );
+ return term;
+}
+
+OUString makeRcTerm( OUString const & url )
+{
+ OSL_ASSERT( url.match( "vnd.sun.star.expand:" ));
+ if (OUString rcterm; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcterm)) {
+ // decode uric class chars:
+ rcterm = ::rtl::Uri::decode(
+ rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ return rcterm;
+ }
+ else
+ return url;
+}
+
+
+OUString expandUnoRcUrl( OUString const & url )
+{
+ if (OUString rcurl; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcurl)) {
+ // decode uric class chars:
+ rcurl = ::rtl::Uri::decode(
+ rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ // expand macro string:
+ UnoRc()->expandMacrosFrom( rcurl );
+ return rcurl;
+ }
+ else {
+ return url;
+ }
+}
+
+
+bool office_is_running()
+{
+ //We need to check if we run within the office process. Then we must not use the pipe, because
+ //this could cause a deadlock. This is actually a workaround for i82778
+ OUString sFile;
+ oslProcessError err = osl_getExecutableFile(& sFile.pData);
+ bool ret = false;
+ if (osl_Process_E_None == err)
+ {
+ sFile = sFile.copy(sFile.lastIndexOf('/') + 1);
+ if (
+#if defined _WIN32
+ //osl_getExecutableFile should deliver "soffice.bin" on windows
+ //even if swriter.exe, scalc.exe etc. was started. This is a bug
+ //in osl_getExecutableFile
+ sFile == "soffice.bin" || sFile == "soffice.exe" || sFile == "soffice.com"
+ || sFile == "soffice" || sFile == "swriter.exe" || sFile == "swriter"
+ || sFile == "scalc.exe" || sFile == "scalc" || sFile == "simpress.exe"
+ || sFile == "simpress" || sFile == "sdraw.exe" || sFile == "sdraw"
+ || sFile == "sbase.exe" || sFile == "sbase"
+#elif defined MACOSX
+ sFile == "soffice"
+#elif defined UNIX
+ sFile == "soffice.bin"
+#else
+#error "Unsupported platform"
+#endif
+
+ )
+ ret = true;
+ else
+ ret = existsOfficePipe();
+ }
+ else
+ {
+ OSL_FAIL("NOT osl_Process_E_None ");
+ //if osl_getExecutable file then we take the risk of creating a pipe
+ ret = existsOfficePipe();
+ }
+ return ret;
+}
+
+
+oslProcess raiseProcess(
+ OUString const & appURL, Sequence<OUString> const & args )
+{
+ ::osl::Security sec;
+ oslProcess hProcess = nullptr;
+ oslProcessError rc = osl_executeProcess(
+ appURL.pData,
+ reinterpret_cast<rtl_uString **>(
+ const_cast<OUString *>(args.getConstArray()) ),
+ args.getLength(),
+ osl_Process_DETACHED,
+ sec.getHandle(),
+ nullptr, // => current working dir
+ nullptr, 0, // => no env vars
+ &hProcess );
+
+ switch (rc) {
+ case osl_Process_E_None:
+ break;
+ case osl_Process_E_NotFound:
+ throw RuntimeException( "image not found!", nullptr );
+ case osl_Process_E_TimedOut:
+ throw RuntimeException( "timeout occurred!", nullptr );
+ case osl_Process_E_NoPermission:
+ throw RuntimeException( "permission denied!", nullptr );
+ case osl_Process_E_Unknown:
+ throw RuntimeException( "unknown error!", nullptr );
+ case osl_Process_E_InvalidError:
+ default:
+ throw RuntimeException( "unmapped error!", nullptr );
+ }
+
+ return hProcess;
+}
+
+
+OUString generateRandomPipeId()
+{
+ // compute some good pipe id:
+ static rtlRandomPool s_hPool = rtl_random_createPool();
+ if (s_hPool == nullptr)
+ throw RuntimeException( "cannot create random pool!?", nullptr );
+ sal_uInt8 bytes[ 32 ];
+ if (rtl_random_getBytes(
+ s_hPool, bytes, std::size(bytes) ) != rtl_Random_E_None) {
+ throw RuntimeException( "random pool error!?", nullptr );
+ }
+ OUStringBuffer buf;
+ for (unsigned char byte : bytes) {
+ buf.append( static_cast<sal_Int32>(byte), 0x10 );
+ }
+ return buf.makeStringAndClear();
+}
+
+
+Reference<XInterface> resolveUnoURL(
+ OUString const & connectString,
+ Reference<XComponentContext> const & xLocalContext,
+ AbortChannel const * abortChannel )
+{
+ Reference<bridge::XUnoUrlResolver> xUnoUrlResolver(
+ bridge::UnoUrlResolver::create( xLocalContext ) );
+
+ for (int i = 0; i <= 40; ++i) // 20 seconds
+ {
+ if (abortChannel != nullptr && abortChannel->isAborted()) {
+ throw ucb::CommandAbortedException( "abort!" );
+ }
+ try {
+ return xUnoUrlResolver->resolve( connectString );
+ }
+ catch (const connection::NoConnectException &) {
+ if (i < 40)
+ {
+ std::this_thread::sleep_for( std::chrono::milliseconds(500) );
+ }
+ else throw;
+ }
+ }
+ return nullptr; // warning C4715
+}
+
+static void writeConsoleWithStream(std::u16string_view sText, FILE * stream)
+{
+ OString s = OUStringToOString(sText, osl_getThreadTextEncoding());
+ fprintf(stream, "%s", s.getStr());
+ fflush(stream);
+}
+
+void writeConsole(std::u16string_view sText)
+{
+ writeConsoleWithStream(sText, stdout);
+}
+
+void writeConsoleError(std::u16string_view sText)
+{
+ writeConsoleWithStream(sText, stderr);
+}
+
+OUString readConsole()
+{
+ char buf[1024];
+ memset(buf, 0, 1024);
+ // read one char less so that the last char in buf is always zero
+ if (fgets(buf, 1024, stdin) != nullptr)
+ {
+ OUString value = OStringToOUString(std::string_view(buf), osl_getThreadTextEncoding());
+ return value.trim();
+ }
+ throw css::uno::RuntimeException("reading from stdin failed");
+}
+
+void TRACE(OUString const & sText)
+{
+ SAL_INFO("desktop.deployment", sText);
+}
+
+void syncRepositories(
+ bool force, Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ OUString sDisable;
+ ::rtl::Bootstrap::get( "DISABLE_EXTENSION_SYNCHRONIZATION", sDisable, OUString() );
+ if (!sDisable.isEmpty())
+ return;
+
+ Reference<deployment::XExtensionManager> xExtensionManager;
+ //synchronize shared before bundled otherwise there are
+ //more revoke and registration calls.
+ bool bModified = false;
+ if (force || needToSyncRepository(u"shared") || needToSyncRepository(u"bundled"))
+ {
+ xExtensionManager =
+ deployment::ExtensionManager::get(
+ comphelper::getProcessComponentContext());
+
+ if (xExtensionManager.is())
+ {
+ bModified = xExtensionManager->synchronize(
+ Reference<task::XAbortChannel>(), xCmdEnv);
+ }
+ }
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ (void) bModified;
+#else
+ if (bModified && !comphelper::LibreOfficeKit::isActive())
+ {
+ Reference<task::XRestartManager> restarter(task::OfficeRestartManager::get(comphelper::getProcessComponentContext()));
+ if (restarter.is())
+ {
+ restarter->requestRestart(xCmdEnv.is() ? xCmdEnv->getInteractionHandler() :
+ Reference<task::XInteractionHandler>());
+ }
+ }
+#endif
+}
+
+void disposeBridges(Reference<css::uno::XComponentContext> const & ctx)
+{
+ if (!ctx.is())
+ return;
+
+ Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(ctx) );
+
+ const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges();
+ for (const Reference<css::bridge::XBridge>& bridge : seqBridges)
+ {
+ Reference<css::lang::XComponent> comp(bridge, UNO_QUERY);
+ if (comp.is())
+ {
+ try {
+ comp->dispose();
+ }
+ catch ( const css::lang::DisposedException& )
+ {
+ }
+ }
+ }
+}
+
+}
+
+OUString DpResId(TranslateId aId)
+{
+ static std::locale SINGLETON = Translate::Create("dkt");
+ return Translate::get(aId, SINGLETON);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_platform.cxx b/desktop/source/deployment/misc/dp_platform.cxx
new file mode 100644
index 0000000000..b2af59f9b9
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_platform.cxx
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <dp_platform.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/instance.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/diagnose.h>
+#include <o3tl/string_view.hxx>
+
+constexpr OUStringLiteral PLATFORM_ALL = u"all";
+
+
+namespace dp_misc
+{
+namespace
+{
+ const OUString & StrOperatingSystem()
+ {
+ static const OUString theOS = []()
+ {
+ OUString os( "$_OS" );
+ ::rtl::Bootstrap::expandMacros( os );
+ return os;
+ }();
+ return theOS;
+ };
+
+ const OUString & StrCPU()
+ {
+ static const OUString theCPU = []()
+ {
+ OUString arch( "$_ARCH" );
+ ::rtl::Bootstrap::expandMacros( arch );
+ return arch;
+ }();
+ return theCPU;
+ };
+
+
+ const OUString & StrPlatform()
+ {
+ static const OUString thePlatform = StrOperatingSystem() + "_" + StrCPU();
+ return thePlatform;
+ };
+
+ bool checkOSandCPU(std::u16string_view os, std::u16string_view cpu)
+ {
+ return (os == StrOperatingSystem())
+ && (cpu == StrCPU());
+ }
+
+ bool isPlatformSupported( std::u16string_view token )
+ {
+ bool ret = false;
+ if (token == PLATFORM_ALL)
+ ret = true;
+ else if (token == u"windows_x86")
+ ret = checkOSandCPU(u"Windows", u"x86");
+ else if (token == u"windows_x86_64")
+ ret = checkOSandCPU(u"Windows", u"X86_64");
+ else if (token == u"windows_aarch64")
+ ret = checkOSandCPU(u"Windows", u"AARCH64");
+ else if (token == u"solaris_sparc")
+ ret = checkOSandCPU(u"Solaris", u"SPARC");
+ else if (token == u"solaris_sparc64")
+ ret = checkOSandCPU(u"Solaris", u"SPARC64");
+ else if (token == u"solaris_x86")
+ ret = checkOSandCPU(u"Solaris", u"x86");
+ else if (token == u"macosx_aarch64")
+ ret = checkOSandCPU(u"MacOSX", u"AARCH64");
+ else if (token == u"macosx_x86_64")
+ ret = checkOSandCPU(u"MacOSX", u"X86_64");
+ else if (token == u"linux_x86")
+ ret = checkOSandCPU(u"Linux", u"x86");
+ else if (token == u"linux_x86_64")
+ ret = checkOSandCPU(u"Linux", u"X86_64");
+ else if (token == u"linux_sparc")
+ ret = checkOSandCPU(u"Linux", u"SPARC");
+ else if (token == u"linux_sparc64")
+ ret = checkOSandCPU(u"Linux", u"SPARC64");
+ else if (token == u"linux_powerpc")
+ ret = checkOSandCPU(u"Linux", u"PowerPC");
+ else if (token == u"linux_powerpc64")
+ ret = checkOSandCPU(u"Linux", u"PowerPC_64");
+ else if (token == u"linux_powerpc64_le")
+ ret = checkOSandCPU(u"Linux", u"PowerPC_64_LE");
+ else if (token == u"linux_arm_eabi")
+ ret = checkOSandCPU(u"Linux", u"ARM_EABI");
+ else if (token == u"linux_arm_oabi")
+ ret = checkOSandCPU(u"Linux", u"ARM_OABI");
+ else if (token == u"linux_mips_el")
+ ret = checkOSandCPU(u"Linux", u"MIPS_EL");
+ else if (token == u"linux_mips64_el")
+ ret = checkOSandCPU(u"Linux", u"MIPS64_EL");
+ else if (token == u"linux_mips_eb")
+ ret = checkOSandCPU(u"Linux", u"MIPS_EB");
+ else if (token == u"linux_mips64_eb")
+ ret = checkOSandCPU(u"Linux", u"MIPS64_EB");
+ else if (token == u"linux_ia64")
+ ret = checkOSandCPU(u"Linux", u"IA64");
+ else if (token == u"linux_m68k")
+ ret = checkOSandCPU(u"Linux", u"M68K");
+ else if (token == u"linux_s390x")
+ ret = checkOSandCPU(u"Linux", u"S390x");
+ else if (token == u"linux_hppa")
+ ret = checkOSandCPU(u"Linux", u"HPPA");
+ else if (token == u"linux_alpha")
+ ret = checkOSandCPU(u"Linux", u"ALPHA");
+ else if (token == u"linux_aarch64")
+ ret = checkOSandCPU(u"Linux", u"AARCH64");
+ else if (token == u"linux_riscv64")
+ ret = checkOSandCPU(u"Linux", u"RISCV64");
+ else if (token == u"linux_loongarch64")
+ ret = checkOSandCPU(u"Linux", u"LOONGARCH64");
+ else if (token == u"freebsd_x86")
+ ret = checkOSandCPU(u"FreeBSD", u"x86");
+ else if (token == u"freebsd_x86_64")
+ ret = checkOSandCPU(u"FreeBSD", u"X86_64");
+ else if (token == u"freebsd_powerpc")
+ ret = checkOSandCPU(u"FreeBSD", u"PowerPC");
+ else if (token == u"freebsd_powerpc64")
+ ret = checkOSandCPU(u"FreeBSD", u"PowerPC64");
+ else if (token == u"kfreebsd_x86")
+ ret = checkOSandCPU(u"kFreeBSD", u"x86");
+ else if (token == u"kfreebsd_x86_64")
+ ret = checkOSandCPU(u"kFreeBSD", u"X86_64");
+ else if (token == u"netbsd_x86")
+ ret = checkOSandCPU(u"NetBSD", u"x86");
+ else if (token == u"netbsd_x86_64")
+ ret = checkOSandCPU(u"NetBSD", u"X86_64");
+ else if (token == u"openbsd_x86")
+ ret = checkOSandCPU(u"OpenBSD", u"x86");
+ else if (token == u"openbsd_x86_64")
+ ret = checkOSandCPU(u"OpenBSD", u"X86_64");
+ else if (token == u"dragonfly_x86")
+ ret = checkOSandCPU(u"DragonFly", u"x86");
+ else if (token == u"dragonfly_x86_64")
+ ret = checkOSandCPU(u"DragonFly", u"X86_64");
+ else
+ {
+ OSL_FAIL("Extension Manager: The extension supports an unknown platform. "
+ "Check the platform in the description.xml");
+ ret = false;
+ }
+ return ret;
+ }
+
+} // anon namespace
+
+
+OUString const & getPlatformString()
+{
+ return StrPlatform();
+}
+
+bool platform_fits( std::u16string_view platform_string )
+{
+ sal_Int32 index = 0;
+ for (;;)
+ {
+ const std::u16string_view token(
+ o3tl::trim(o3tl::getToken(platform_string, 0, ',', index )) );
+ // check if this platform:
+ if (o3tl::equalsIgnoreAsciiCase( token, StrPlatform() ) ||
+ (token.find( '_' ) == std::u16string_view::npos && /* check OS part only */
+ o3tl::equalsIgnoreAsciiCase( token, StrOperatingSystem() )))
+ {
+ return true;
+ }
+ if (index < 0)
+ break;
+ }
+ return false;
+}
+
+bool hasValidPlatform( css::uno::Sequence<OUString> const & platformStrings)
+{
+ bool ret = false;
+ for (const OUString& s : platformStrings)
+ {
+ if ( isPlatformSupported( s ) )
+ {
+ ret = true;
+ break;
+ }
+ }
+ return ret;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_resource.cxx b/desktop/source/deployment/misc/dp_resource.cxx
new file mode 100644
index 0000000000..682c90e524
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_resource.cxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <dp_resource.h>
+#include <unotools/configmgr.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace dp_misc
+{
+const LanguageTag& getOfficeLanguageTag()
+{
+ static const LanguageTag OFFICE_LANG = []() {
+ OUString slang(utl::ConfigManager::getUILocale());
+ //fallback, the locale is currently only set when the user starts the
+ //office for the first time.
+ if (slang.isEmpty())
+ slang = "en-US";
+ return LanguageTag(slang);
+ }();
+ return OFFICE_LANG;
+}
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_ucb.cxx b/desktop/source/deployment/misc/dp_ucb.cxx
new file mode 100644
index 0000000000..5ca42f31ae
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_ucb.cxx
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <ucbhelper/content.hxx>
+#include <xmlscript/xml_helper.hxx>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/ContentInfo.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <comphelper/processfactory.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_misc
+{
+
+
+bool create_ucb_content(
+ ::ucbhelper::Content * ret_ucbContent, OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv,
+ bool throw_exc )
+{
+ try {
+ // Existence check...
+ // content ctor/isFolder() will throw exception in case the resource
+ // does not exist.
+
+ // dilemma: no chance to use the given handler here, because it would
+ // raise no such file dialogs, else no interaction for
+ // passwords, ...? xxx todo
+ ::ucbhelper::Content ucbContent(
+ url, Reference<XCommandEnvironment>(),
+ comphelper::getProcessComponentContext() );
+
+ ucbContent.isFolder();
+
+ if (ret_ucbContent != nullptr)
+ {
+ ucbContent.setCommandEnvironment( xCmdEnv );
+ *ret_ucbContent = ucbContent;
+ }
+ return true;
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ if (throw_exc)
+ throw;
+ }
+ return false;
+}
+
+
+bool create_folder(
+ ::ucbhelper::Content * ret_ucb_content, OUString const & url_,
+ Reference<XCommandEnvironment> const & xCmdEnv, bool throw_exc )
+{
+ ::ucbhelper::Content ucb_content;
+ if (create_ucb_content(
+ &ucb_content, url_, xCmdEnv, false /* no throw */ ))
+ {
+ if (ucb_content.isFolder()) {
+ if (ret_ucb_content != nullptr)
+ *ret_ucb_content = ucb_content;
+ return true;
+ }
+ }
+
+ OUString url( url_ );
+ // xxx todo: find parent
+ sal_Int32 slash = url.lastIndexOf( '/' );
+ if (slash < 0) {
+ // fallback:
+ url = expandUnoRcUrl( url );
+ slash = url.lastIndexOf( '/' );
+ }
+ if (slash < 0) {
+ // invalid: has to be at least "auth:/..."
+ if (throw_exc)
+ throw ContentCreationException(
+ "Cannot create folder (invalid path): '" + url + "'",
+ Reference<XInterface>(), ContentCreationError_UNKNOWN );
+ return false;
+ }
+ ::ucbhelper::Content parentContent;
+ if (! create_folder(
+ &parentContent, url.copy( 0, slash ), xCmdEnv, throw_exc ))
+ return false;
+ const Any title( ::rtl::Uri::decode( url.copy( slash + 1 ),
+ rtl_UriDecodeWithCharset,
+ RTL_TEXTENCODING_UTF8 ) );
+ const Sequence<ContentInfo> infos(
+ parentContent.queryCreatableContentsInfo() );
+ for ( ContentInfo const & info : infos )
+ {
+ // look KIND_FOLDER:
+ if ((info.Attributes & ContentInfoAttribute::KIND_FOLDER) != 0)
+ {
+ // make sure the only required bootstrap property is "Title":
+ Sequence<beans::Property> const & rProps = info.Properties;
+ if ( rProps.getLength() != 1 || rProps[ 0 ].Name != "Title" )
+ continue;
+
+ try {
+ if (parentContent.insertNewContent(
+ info.Type,
+ StrTitle::getTitleSequence(),
+ Sequence<Any>( &title, 1 ),
+ ucb_content )) {
+ if (ret_ucb_content != nullptr)
+ *ret_ucb_content = ucb_content;
+ return true;
+ }
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException &) {
+ // Interaction Handler already handled the error
+ // that has occurred...
+ }
+ catch (const Exception &) {
+ if (throw_exc)
+ throw;
+ return false;
+ }
+ }
+ }
+ if (throw_exc)
+ throw ContentCreationException(
+ "Cannot create folder: '" + url + "'",
+ Reference<XInterface>(), ContentCreationError_UNKNOWN );
+ return false;
+}
+
+
+bool erase_path( OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv,
+ bool throw_exc )
+{
+ ::ucbhelper::Content ucb_content;
+ if (create_ucb_content( &ucb_content, url, xCmdEnv, false /* no throw */ ))
+ {
+ try {
+ ucb_content.executeCommand(
+ "delete", Any( true /* delete physically */ ) );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ if (throw_exc)
+ throw;
+ return false;
+ }
+ }
+ return true;
+}
+
+
+std::vector<sal_Int8> readFile( ::ucbhelper::Content & ucb_content )
+{
+ std::vector<sal_Int8> bytes;
+ Reference<io::XOutputStream> xStream(
+ ::xmlscript::createOutputStream( &bytes ) );
+ if (! ucb_content.openStream( xStream ))
+ throw RuntimeException(
+ "::ucbhelper::Content::openStream( XOutputStream ) failed!",
+ nullptr );
+ return bytes;
+}
+
+
+bool readLine( OUString * res, std::u16string_view startingWith,
+ ::ucbhelper::Content & ucb_content, rtl_TextEncoding textenc )
+{
+ // read whole file:
+ std::vector<sal_Int8> bytes( readFile( ucb_content ) );
+ OUString file( reinterpret_cast<char const *>(bytes.data()),
+ bytes.size(), textenc );
+ sal_Int32 pos = 0;
+ for (;;)
+ {
+ if (file.match( startingWith, pos ))
+ {
+ OUStringBuffer buf;
+ sal_Int32 start = pos;
+ pos += startingWith.size();
+ for (;;)
+ {
+ pos = file.indexOf( LF, pos );
+ if (pos < 0) { // EOF
+ buf.append( file.subView(start) );
+ }
+ else
+ {
+ if (pos > 0 && file[ pos - 1 ] == CR)
+ {
+ // consume extra CR
+ buf.append( file.subView(start, pos - start - 1) );
+ ++pos;
+ }
+ else
+ buf.append( file.subView(start, pos - start) );
+ ++pos; // consume LF
+ // check next line:
+ if (pos < file.getLength() &&
+ (file[ pos ] == ' ' || file[ pos ] == '\t'))
+ {
+ buf.append( ' ' );
+ ++pos;
+ start = pos;
+ continue;
+ }
+ }
+ break;
+ }
+ *res = buf.makeStringAndClear();
+ return true;
+ }
+ // next line:
+ sal_Int32 next_lf = file.indexOf( LF, pos );
+ if (next_lf < 0) // EOF
+ break;
+ pos = next_lf + 1;
+ }
+ return false;
+}
+
+bool readProperties( std::vector< std::pair< OUString, OUString> > & out_result,
+ ::ucbhelper::Content & ucb_content )
+{
+ // read whole file:
+ std::vector<sal_Int8> bytes( readFile( ucb_content ) );
+ OUString file( reinterpret_cast<char const *>(bytes.data()),
+ bytes.size(), RTL_TEXTENCODING_UTF8);
+ sal_Int32 pos = 0;
+
+ for (;;)
+ {
+
+ OUStringBuffer buf;
+ sal_Int32 start = pos;
+
+ bool bEOF = false;
+ pos = file.indexOf( LF, pos );
+ if (pos < 0) { // EOF
+ buf.append( file.subView(start) );
+ bEOF = true;
+ }
+ else
+ {
+ if (pos > 0 && file[ pos - 1 ] == CR)
+ // consume extra CR
+ buf.append( file.subView(start, pos - start - 1) );
+ else
+ buf.append( file.subView(start, pos - start) );
+ pos++;
+ }
+ OUString aLine = buf.makeStringAndClear();
+
+ sal_Int32 posEqual = aLine.indexOf('=');
+ if (posEqual > 0 && (posEqual + 1) < aLine.getLength())
+ {
+ OUString name = aLine.copy(0, posEqual);
+ OUString value = aLine.copy(posEqual + 1);
+ out_result.emplace_back(name, value);
+ }
+
+ if (bEOF)
+ break;
+ }
+ return false;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_update.cxx b/desktop/source/deployment/misc/dp_update.cxx
new file mode 100644
index 0000000000..650d648e8a
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_update.cxx
@@ -0,0 +1,408 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <dp_update.hxx>
+#include <dp_version.hxx>
+#include <dp_identifier.hxx>
+#include <dp_descriptioninfoset.hxx>
+
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <osl/diagnose.h>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+namespace dp_misc {
+namespace {
+
+int determineHighestVersion(
+ OUString const & userVersion,
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ std::u16string_view onlineVersion)
+{
+ int index = 0;
+ OUString greatest = userVersion;
+ if (dp_misc::compareVersions(sharedVersion, greatest) == dp_misc::GREATER)
+ {
+ index = 1;
+ greatest = sharedVersion;
+ }
+ if (dp_misc::compareVersions(bundledVersion, greatest) == dp_misc::GREATER)
+ {
+ index = 2;
+ greatest = bundledVersion;
+ }
+ if (dp_misc::compareVersions(onlineVersion, greatest) == dp_misc::GREATER)
+ {
+ index = 3;
+ }
+ return index;
+}
+
+Sequence< Reference< xml::dom::XElement > >
+getUpdateInformation( Reference<deployment::XUpdateInformationProvider > const & updateInformation,
+ Sequence< OUString > const & urls,
+ OUString const & identifier,
+ uno::Any & out_error)
+{
+ try {
+ return updateInformation->getUpdateInformation(urls, identifier);
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const ucb::CommandFailedException & e) {
+ out_error = e.Reason;
+ } catch (const ucb::CommandAbortedException &) {
+ } catch (const uno::Exception & e) {
+ out_error <<= e;
+ }
+ return
+ Sequence<Reference< xml::dom::XElement > >();
+}
+
+void getOwnUpdateInfos(
+ Reference<uno::XComponentContext> const & xContext,
+ Reference<deployment::XUpdateInformationProvider > const & updateInformation,
+ UpdateInfoMap& inout_map, std::vector<std::pair<Reference<deployment::XPackage>, uno::Any> > & out_errors,
+ bool & out_allFound)
+{
+ bool bAllHaveOwnUpdateInformation = true;
+ for (auto & inout : inout_map)
+ {
+ OSL_ASSERT(inout.second.extension.is());
+ Sequence<OUString> urls(inout.second.extension->getUpdateInformationURLs());
+ if (urls.hasElements())
+ {
+ const OUString search_id = dp_misc::getIdentifier(inout.second.extension);
+ SAL_INFO( "extensions.update", "Searching update for " << search_id );
+ uno::Any anyError;
+ //It is unclear from the idl if there can be a null reference returned.
+ //However all valid information should be the same
+ const Sequence<Reference< xml::dom::XElement > >
+ infos(getUpdateInformation(updateInformation, urls, search_id, anyError));
+ if (anyError.hasValue())
+ out_errors.emplace_back(inout.second.extension, anyError);
+
+ for (const Reference< xml::dom::XElement >& element : infos)
+ {
+ dp_misc::DescriptionInfoset infoset(
+ xContext,
+ Reference< xml::dom::XNode >(element, UNO_QUERY_THROW));
+ if (!infoset.hasDescription())
+ continue;
+ std::optional< OUString > result_id(infoset.getIdentifier());
+ if (!result_id)
+ continue;
+ SAL_INFO( "extensions.update", " found version "
+ << infoset.getVersion() << " for " << *result_id );
+ if (*result_id != search_id)
+ continue;
+ inout.second.version = infoset.getVersion();
+ inout.second.info.set(element, UNO_QUERY_THROW);
+ break;
+ }
+ }
+ else
+ {
+ bAllHaveOwnUpdateInformation = false;
+ }
+ }
+ out_allFound = bAllHaveOwnUpdateInformation;
+}
+
+void getDefaultUpdateInfos(
+ Reference<uno::XComponentContext> const & xContext,
+ Reference<deployment::XUpdateInformationProvider > const & updateInformation,
+ UpdateInfoMap& inout_map,
+ std::vector<std::pair<Reference<deployment::XPackage>, uno::Any> > & out_errors)
+{
+ const OUString sDefaultURL(dp_misc::getExtensionDefaultUpdateURL());
+ OSL_ASSERT(!sDefaultURL.isEmpty());
+
+ Any anyError;
+ const Sequence< Reference< xml::dom::XElement > >
+ infos(
+ getUpdateInformation(
+ updateInformation,
+ Sequence< OUString >(&sDefaultURL, 1), OUString(), anyError));
+ if (anyError.hasValue())
+ out_errors.emplace_back(Reference<deployment::XPackage>(), anyError);
+ for (const Reference< xml::dom::XElement >& element : infos)
+ {
+ Reference< xml::dom::XNode > node(element, UNO_QUERY_THROW);
+ dp_misc::DescriptionInfoset infoset(xContext, node);
+ std::optional< OUString > id(infoset.getIdentifier());
+ if (!id) {
+ continue;
+ }
+ UpdateInfoMap::iterator j = inout_map.find(*id);
+ if (j != inout_map.end())
+ {
+ //skip those extension which provide its own update urls
+ if (j->second.extension->getUpdateInformationURLs().getLength())
+ continue;
+ OUString v(infoset.getVersion());
+ //look for the highest version in the online repository
+ if (dp_misc::compareVersions(v, j->second.version) ==
+ dp_misc::GREATER)
+ {
+ j->second.version = v;
+ j->second.info = node;
+ }
+ }
+ }
+}
+
+bool containsBundledOnly(Sequence<Reference<deployment::XPackage> > const & sameIdExtensions)
+{
+ OSL_ASSERT(sameIdExtensions.getLength() == 3);
+ return !sameIdExtensions[0].is() && !sameIdExtensions[1].is() && sameIdExtensions[2].is();
+}
+
+/** Returns true if the list of extensions are bundled extensions and there are no
+ other extensions with the same identifier in the shared or user repository.
+ If extensionList is NULL, then it is checked if there are only bundled extensions.
+*/
+bool onlyBundledExtensions(
+ Reference<deployment::XExtensionManager> const & xExtMgr,
+ std::vector< Reference<deployment::XPackage > > const * extensionList)
+{
+ OSL_ASSERT(xExtMgr.is());
+ bool bOnlyBundled = true;
+ if (extensionList)
+ {
+ for (auto const& elem : *extensionList)
+ {
+ Sequence<Reference<deployment::XPackage> > seqExt = xExtMgr->getExtensionsWithSameIdentifier(
+ dp_misc::getIdentifier(elem), elem->getName(), Reference<ucb::XCommandEnvironment>());
+
+ bOnlyBundled = containsBundledOnly(seqExt);
+ if (!bOnlyBundled)
+ break;
+ }
+ }
+ else
+ {
+ const uno::Sequence< uno::Sequence< Reference<deployment::XPackage > > > seqAllExt =
+ xExtMgr->getAllExtensions(Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>());
+
+ for (int pos(0), nLen(seqAllExt.getLength()); bOnlyBundled && pos != nLen; ++pos)
+ {
+ bOnlyBundled = containsBundledOnly(seqAllExt[pos]);
+ }
+ }
+ return bOnlyBundled;
+}
+
+} // anon namespace
+
+
+OUString getExtensionDefaultUpdateURL()
+{
+ OUString sUrl(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version")
+ ":Version:ExtensionUpdateURL}");
+ ::rtl::Bootstrap::expandMacros(sUrl);
+ return sUrl;
+}
+
+/* returns the index of the greatest version, starting with 0
+
+ */
+UPDATE_SOURCE isUpdateUserExtension(
+ bool bReadOnlyShared,
+ OUString const & userVersion,
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ std::u16string_view onlineVersion)
+{
+ UPDATE_SOURCE retVal = UPDATE_SOURCE_NONE;
+ if (bReadOnlyShared)
+ {
+ if (!userVersion.isEmpty())
+ {
+ int index = determineHighestVersion(
+ userVersion, sharedVersion, bundledVersion, onlineVersion);
+ if (index == 1)
+ retVal = UPDATE_SOURCE_SHARED;
+ else if (index == 2)
+ retVal = UPDATE_SOURCE_BUNDLED;
+ else if (index == 3)
+ retVal = UPDATE_SOURCE_ONLINE;
+ }
+ else if (!sharedVersion.isEmpty())
+ {
+ int index = determineHighestVersion(
+ OUString(), sharedVersion, bundledVersion, onlineVersion);
+ if (index == 2)
+ retVal = UPDATE_SOURCE_BUNDLED;
+ else if (index == 3)
+ retVal = UPDATE_SOURCE_ONLINE;
+
+ }
+ }
+ else
+ {
+ if (!userVersion.isEmpty())
+ {
+ int index = determineHighestVersion(
+ userVersion, sharedVersion, bundledVersion, onlineVersion);
+ if (index == 1)
+ retVal = UPDATE_SOURCE_SHARED;
+ else if (index == 2)
+ retVal = UPDATE_SOURCE_BUNDLED;
+ else if (index == 3)
+ retVal = UPDATE_SOURCE_ONLINE;
+ }
+ }
+
+ return retVal;
+}
+
+UPDATE_SOURCE isUpdateSharedExtension(
+ bool bReadOnlyShared,
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ std::u16string_view onlineVersion)
+{
+ if (bReadOnlyShared)
+ return UPDATE_SOURCE_NONE;
+ UPDATE_SOURCE retVal = UPDATE_SOURCE_NONE;
+
+ if (!sharedVersion.isEmpty())
+ {
+ int index = determineHighestVersion(
+ OUString(), sharedVersion, bundledVersion, onlineVersion);
+ if (index == 2)
+ retVal = UPDATE_SOURCE_BUNDLED;
+ else if (index == 3)
+ retVal = UPDATE_SOURCE_ONLINE;
+ }
+ return retVal;
+}
+
+Reference<deployment::XPackage>
+getExtensionWithHighestVersion(
+ Sequence<Reference<deployment::XPackage> > const & seqExt)
+{
+ if (!seqExt.hasElements())
+ return Reference<deployment::XPackage>();
+
+ Reference<deployment::XPackage> greatest;
+ sal_Int32 len = seqExt.getLength();
+
+ for (sal_Int32 i = 0; i < len; i++)
+ {
+ if (!greatest.is())
+ {
+ greatest = seqExt[i];
+ continue;
+ }
+ Reference<deployment::XPackage> const & current = seqExt[i];
+ //greatest has a value
+ if (! current.is())
+ continue;
+
+ if (dp_misc::compareVersions(current->getVersion(), greatest->getVersion()) == dp_misc::GREATER)
+ greatest = current;
+ }
+ return greatest;
+}
+
+UpdateInfo::UpdateInfo( Reference< deployment::XPackage> const & ext):
+extension(ext)
+{
+}
+
+
+UpdateInfoMap getOnlineUpdateInfos(
+ Reference<uno::XComponentContext> const &xContext,
+ Reference<deployment::XExtensionManager> const & xExtMgr,
+ Reference<deployment::XUpdateInformationProvider > const & updateInformation,
+ std::vector<Reference<deployment::XPackage > > const * extensionList,
+ std::vector<std::pair< Reference<deployment::XPackage>, uno::Any> > & out_errors)
+{
+ OSL_ASSERT(xExtMgr.is());
+ UpdateInfoMap infoMap;
+ if (!xExtMgr.is() || onlyBundledExtensions(xExtMgr, extensionList))
+ return infoMap;
+
+ if (!extensionList)
+ {
+ const uno::Sequence< uno::Sequence< Reference<deployment::XPackage > > > seqAllExt = xExtMgr->getAllExtensions(
+ Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>());
+
+ //fill the UpdateInfoMap. key = extension identifier, value = UpdateInfo
+ for (int pos = seqAllExt.getLength(); pos --; )
+ {
+ uno::Sequence<Reference<deployment::XPackage> > const & seqExt = seqAllExt[pos];
+
+ Reference<deployment::XPackage> extension = getExtensionWithHighestVersion(seqExt);
+ OSL_ASSERT(extension.is());
+
+ std::pair<UpdateInfoMap::iterator, bool> insertRet = infoMap.emplace(
+ dp_misc::getIdentifier(extension), UpdateInfo(extension));
+ OSL_ASSERT(insertRet.second);
+ }
+ }
+ else
+ {
+ for (auto const& elem : *extensionList)
+ {
+ OSL_ASSERT(elem.is());
+ std::pair<UpdateInfoMap::iterator, bool> insertRet = infoMap.emplace(
+ dp_misc::getIdentifier(elem), UpdateInfo(elem));
+ OSL_ASSERT(insertRet.second);
+ }
+ }
+
+ //Now find the update information for the extensions which provide their own
+ //URLs to update information.
+ bool bAllInfosObtained = false;
+ getOwnUpdateInfos(xContext, updateInformation, infoMap, out_errors, bAllInfosObtained);
+
+ if (!bAllInfosObtained)
+ getDefaultUpdateInfos(xContext, updateInformation, infoMap, out_errors);
+ return infoMap;
+}
+OUString getHighestVersion(
+ OUString const & sharedVersion,
+ OUString const & bundledVersion,
+ OUString const & onlineVersion)
+{
+ int index = determineHighestVersion(OUString(), sharedVersion, bundledVersion, onlineVersion);
+ switch (index)
+ {
+ case 1: return sharedVersion;
+ case 2: return bundledVersion;
+ case 3: return onlineVersion;
+ default: OSL_ASSERT(false);
+ }
+
+ return OUString();
+}
+} //namespace dp_misc
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/dp_version.cxx b/desktop/source/deployment/misc/dp_version.cxx
new file mode 100644
index 0000000000..8006e7b6cf
--- /dev/null
+++ b/desktop/source/deployment/misc/dp_version.cxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/config.h>
+
+#include <o3tl/string_view.hxx>
+#include <rtl/ustring.hxx>
+
+#include <dp_version.hxx>
+
+namespace {
+
+std::u16string_view getElement(std::u16string_view version, std::size_t * index)
+{
+ while (*index < version.size() && version[*index] == '0') {
+ ++*index;
+ }
+ return o3tl::getToken(version, u'.', *index);
+}
+
+}
+
+namespace dp_misc {
+
+::dp_misc::Order compareVersions(
+ std::u16string_view version1, std::u16string_view version2)
+{
+ for (size_t i1 = 0, i2 = 0; i1 != std::u16string_view::npos || i2 != std::u16string_view::npos;) {
+ std::u16string_view e1(i1 != std::u16string_view::npos ? getElement(version1, &i1) : std::u16string_view());
+ std::u16string_view e2(i2 != std::u16string_view::npos ? getElement(version2, &i2) : std::u16string_view());
+ if (e1.size() < e2.size()) {
+ return ::dp_misc::LESS;
+ } else if (e1.size() > e2.size()) {
+ return ::dp_misc::GREATER;
+ } else if (e1 < e2) {
+ return ::dp_misc::LESS;
+ } else if (e1 > e2) {
+ return ::dp_misc::GREATER;
+ }
+ }
+ return ::dp_misc::EQUAL;
+}
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/misc/lockfile.cxx b/desktop/source/deployment/misc/lockfile.cxx
new file mode 100644
index 0000000000..1a87e8bc0f
--- /dev/null
+++ b/desktop/source/deployment/misc/lockfile.cxx
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <memory>
+
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+#include <comphelper/random.hxx>
+#include <sal/types.h>
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+#include <unotools/bootstrap.hxx>
+#include <tools/config.hxx>
+
+#include <lockfile.hxx>
+
+using namespace ::osl;
+using namespace ::utl;
+
+
+static OString impl_getHostname()
+{
+ OString aHost;
+#ifdef _WIN32
+ /*
+ prevent windows from connecting to the net to get its own
+ hostname by using the netbios name
+ */
+ DWORD sz = MAX_COMPUTERNAME_LENGTH + 1;
+ TCHAR szHost[MAX_COMPUTERNAME_LENGTH + 1];
+ if (GetComputerNameA(szHost, &sz))
+ aHost = OString(szHost);
+ else
+ aHost = OString("UNKNOWN");
+#else
+ /* Don't do dns lookup on Linux either */
+ char pHostName[1024];
+
+ if ( gethostname( pHostName, sizeof( pHostName ) - 1 ) == 0 )
+ {
+ pHostName[sizeof( pHostName ) - 1] = '\0';
+ aHost = OString( pHostName );
+ }
+ else
+ aHost = "UNKNOWN"_ostr;
+#endif
+
+ return aHost;
+}
+
+namespace desktop {
+
+ Lockfile::Lockfile( bool bIPCserver )
+ :m_bIPCserver(bIPCserver)
+ ,m_bRemove(false)
+ ,m_bIsLocked(false)
+ {
+ // build the file-url to use for the lock
+ OUString aUserPath;
+ utl::Bootstrap::locateUserInstallation( aUserPath );
+ m_aLockname = aUserPath + "/.lock";
+
+ // generate ID
+ const int nIdBytes = 16;
+ char tmpId[nIdBytes*2+1];
+ time_t t = time(nullptr);
+ for (int i = 0; i<nIdBytes; i++) {
+ int tmpByte = comphelper::rng::uniform_int_distribution(0, 0xFF);
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH // sprintf (macOS 13 SDK)
+ sprintf( tmpId+i*2, "%02X", tmpByte );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ tmpId[nIdBytes*2]=0x00;
+ m_aId = OUString::createFromAscii( tmpId );
+
+ // generate date string
+ char *tmpTime = ctime( &t );
+ if (tmpTime != nullptr) {
+ m_aDate = OUString::createFromAscii( tmpTime );
+ sal_Int32 i = m_aDate.indexOf('\n');
+ if (i > 0)
+ m_aDate = m_aDate.copy(0, i);
+ }
+
+
+ // try to create file
+ File aFile(m_aLockname);
+ if (aFile.open( osl_File_OpenFlag_Create ) == File::E_EXIST) {
+ m_bIsLocked = true;
+ } else {
+ // new lock created
+ aFile.close( );
+ syncToFile( );
+ m_bRemove = true;
+ }
+ }
+
+ bool Lockfile::check( fpExecWarning execWarning )
+ {
+
+ if (m_bIsLocked) {
+ // lock existed, ask user what to do
+ if (isStale() ||
+ (execWarning != nullptr && (*execWarning)( this ))) {
+ // remove file and create new
+ File::remove( m_aLockname );
+ File aFile(m_aLockname);
+ (void)aFile.open( osl_File_OpenFlag_Create );
+ aFile.close( );
+ syncToFile( );
+ m_bRemove = true;
+ return true;
+ } else {
+ //leave alone and return false
+ m_bRemove = false;
+ return false;
+ }
+ } else {
+ // lock was created by us
+ return true;
+ }
+ }
+
+ bool Lockfile::isStale() const
+ {
+ // this checks whether the lockfile was created on the same
+ // host by the same user. Should this be the case it is safe
+ // to assume that it is a stale lockfile which can be overwritten
+ OUString aLockname = m_aLockname;
+ Config aConfig(aLockname);
+ aConfig.SetGroup(LOCKFILE_GROUP ""_ostr);
+ OString aIPCserver = aConfig.ReadKey( LOCKFILE_IPCKEY ""_ostr );
+ if (!aIPCserver.equalsIgnoreAsciiCase("true"))
+ return false;
+
+ OString aHost = aConfig.ReadKey( LOCKFILE_HOSTKEY ""_ostr );
+ OString aUser = aConfig.ReadKey( LOCKFILE_USERKEY ""_ostr );
+
+ // lockfile from same host?
+ OString myHost( impl_getHostname() );
+ if (aHost == myHost) {
+ // lockfile by same UID
+ OUString myUserName;
+ Security aSecurity;
+ aSecurity.getUserName( myUserName );
+ OString myUser(OUStringToOString(myUserName, RTL_TEXTENCODING_ASCII_US));
+ if (aUser == myUser)
+ return true;
+ }
+ return false;
+ }
+
+ void Lockfile::syncToFile() const
+ {
+ OUString aLockname = m_aLockname;
+ Config aConfig(aLockname);
+ aConfig.SetGroup(LOCKFILE_GROUP ""_ostr);
+
+ // get information
+ OString aHost( impl_getHostname() );
+ OUString aUserName;
+ Security aSecurity;
+ aSecurity.getUserName( aUserName );
+ OString aUser = OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US );
+ OString aTime = OUStringToOString( m_aDate, RTL_TEXTENCODING_ASCII_US );
+ OString aStamp = OUStringToOString( m_aId, RTL_TEXTENCODING_ASCII_US );
+
+ // write information
+ aConfig.WriteKey( LOCKFILE_USERKEY ""_ostr, aUser );
+ aConfig.WriteKey( LOCKFILE_HOSTKEY ""_ostr, aHost );
+ aConfig.WriteKey( LOCKFILE_STAMPKEY ""_ostr, aStamp );
+ aConfig.WriteKey( LOCKFILE_TIMEKEY ""_ostr, aTime );
+ aConfig.WriteKey(
+ LOCKFILE_IPCKEY ""_ostr,
+ m_bIPCserver ? "true"_ostr : "false"_ostr );
+ aConfig.Flush( );
+ }
+
+ Lockfile::~Lockfile()
+ {
+ // unlock userdata by removing file
+ if ( m_bRemove )
+ File::remove( m_aLockname );
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.cxx b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx
new file mode 100644
index 0000000000..fd9bb2c61b
--- /dev/null
+++ b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "dp_compbackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/component-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"comp";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"component-backend-db";
+constexpr OUStringLiteral KEY_ELEMENT_NAME = u"component";
+
+namespace dp_registry::backend::component {
+
+ComponentBackendDb::ComponentBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):BackendDb(xContext, url)
+{
+
+}
+
+OUString ComponentBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString ComponentBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString ComponentBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString ComponentBackendDb::getKeyElementName()
+{
+ return KEY_ELEMENT_NAME;
+}
+
+void ComponentBackendDb::addEntry(OUString const & url, Data const & data)
+{
+ try{
+ if (!activateEntry(url))
+ {
+ Reference<css::xml::dom::XNode> componentNode = writeKeyElement(url);
+ writeSimpleElement(u"java-type-library",
+ OUString::boolean(data.javaTypeLibrary),
+ componentNode);
+
+ writeSimpleList(
+ data.implementationNames,
+ u"implementation-names",
+ u"name",
+ componentNode);
+
+ writeVectorOfPair(
+ data.singletons,
+ u"singletons",
+ u"item",
+ u"key",
+ u"value",
+ componentNode);
+
+ save();
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+ComponentBackendDb::Data ComponentBackendDb::getEntry(std::u16string_view url)
+{
+ try
+ {
+ ComponentBackendDb::Data retData;
+ Reference<css::xml::dom::XNode> aNode = getKeyElement(url);
+ if (aNode.is())
+ {
+ bool bJava = readSimpleElement(u"java-type-library", aNode) == "true";
+ retData.javaTypeLibrary = bJava;
+
+ retData.implementationNames =
+ readList( aNode, u"implementation-names", u"name");
+
+ retData.singletons =
+ readVectorOfPair( aNode, u"singletons", u"item", u"key", u"value");
+ }
+ return retData;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+
+} // namespace dp_registry::backend::component
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.hxx b/desktop/source/deployment/registry/component/dp_compbackenddb.hxx
new file mode 100644
index 0000000000..84153b6fa2
--- /dev/null
+++ b/desktop/source/deployment/registry/component/dp_compbackenddb.hxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <rtl/string.hxx>
+#include <vector>
+#include <deque>
+#include <string_view>
+
+#include <dp_backenddb.hxx>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+namespace dp_registry::backend::component {
+
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ The format looks like this:
+
+<?xml version="1.0"?>
+<component-backend-db xmlns="http://openoffice.org/extensionmanager/component-registry/2010">
+ <component url="vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/5CD5.tmp_/leaves1.oxt/extensionoptions.jar">
+ <name>FileName</name>
+ <java-type-library>true</java-type-library>
+ <implementation-names>
+ <name>com.sun.star.comp.extensionoptions.OptionsEventHandler$_OptionsEventHandler</name>
+ ...
+ </implementation-names>
+ <singletons>
+ <item>
+ <key>com.sun.star.java.theJavaVirtualMachine</key>
+ <value>com.sun.star.java.JavaVirtualMachine</value>
+ </item>
+ ...
+ </singletons>
+ </component>
+
+ <component ...>
+ ...
+</component-backend-db>
+ */
+class ComponentBackendDb: public dp_registry::backend::BackendDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+ virtual OUString getNSPrefix() override;
+ virtual OUString getRootElementName() override;
+ virtual OUString getKeyElementName() override;
+
+public:
+ struct Data
+ {
+ Data(): javaTypeLibrary(false) {};
+
+ std::deque< OUString> implementationNames;
+ std::vector< std::pair< OUString, OUString> >singletons;
+ // map from singleton names to implementation names
+ bool javaTypeLibrary;
+ };
+
+public:
+
+ ComponentBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext,
+ OUString const & url);
+
+ void addEntry(OUString const & url, Data const & data);
+
+ Data getEntry(std::u16string_view url);
+
+
+};
+
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/component/dp_component.cxx b/desktop/source/deployment/registry/component/dp_component.cxx
new file mode 100644
index 0000000000..7a692ec8c6
--- /dev/null
+++ b/desktop/source/deployment/registry/component/dp_component.cxx
@@ -0,0 +1,1714 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <strings.hrc>
+#include <dp_misc.h>
+#include <dp_shared.hxx>
+#include <dp_backend.h>
+#include <dp_platform.hxx>
+#include <dp_ucb.h>
+#include <rtl/string.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <ucbhelper/content.hxx>
+#include <comphelper/sequence.hxx>
+#include <utility>
+#include <xmlscript/xml_helper.hxx>
+#include <svl/inettype.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/container/XSet.hpp>
+#include <com/sun/star/registry/XSimpleRegistry.hpp>
+#include <com/sun/star/registry/XImplementationRegistration.hpp>
+#include <com/sun/star/loader/XImplementationLoader.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/util/theMacroExpander.hpp>
+#include <algorithm>
+#include <deque>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+#include "dp_compbackenddb.hxx"
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend::component {
+namespace {
+
+/** return a vector of bootstrap variables which have been provided
+ as command arguments.
+*/
+std::vector<OUString> getCmdBootstrapVariables()
+{
+ std::vector<OUString> ret;
+ sal_uInt32 count = osl_getCommandArgCount();
+ for (sal_uInt32 i = 0; i < count; i++)
+ {
+ OUString arg;
+ osl_getCommandArg(i, &arg.pData);
+ if (arg.startsWith("-env:"))
+ ret.push_back(arg);
+ }
+ return ret;
+}
+
+bool jarManifestHeaderPresent(
+ OUString const & url, std::u16string_view name,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ OUString buf = "vnd.sun.star.zip://"
+ + ::rtl::Uri::encode(
+ url, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 )
+ + "/META-INF/MANIFEST.MF";
+ ::ucbhelper::Content manifestContent;
+ OUString line;
+ return
+ create_ucb_content(
+ &manifestContent, buf, xCmdEnv,
+ false /* no throw */ )
+ && readLine( &line, name, manifestContent, RTL_TEXTENCODING_ASCII_US );
+}
+
+
+class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
+{
+ class ComponentPackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ const OUString m_loader;
+
+ enum class Reg { Uninit, Void, Registered, NotRegistered, MaybeRegistered };
+ Reg m_registered;
+
+ void getComponentInfo(
+ ComponentBackendDb::Data * data,
+ std::vector< css::uno::Reference< css::uno::XInterface > > *
+ factories,
+ Reference<XComponentContext> const & xContext );
+
+ void componentLiveInsertion(
+ ComponentBackendDb::Data const & data,
+ std::vector< css::uno::Reference< css::uno::XInterface > > const &
+ factories);
+
+ void componentLiveRemoval(ComponentBackendDb::Data const & data);
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ Reference<registry::XSimpleRegistry> getRDB() const;
+
+ public:
+ ComponentPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ OUString loader, bool bRemoved,
+ OUString const & identifier);
+ };
+ friend class ComponentPackageImpl;
+
+ class ComponentsPackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ public:
+ ComponentsPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier);
+ };
+ friend class ComponentsPackageImpl;
+
+ class TypelibraryPackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ const bool m_jarFile;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ public:
+ TypelibraryPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool jarFile, bool bRemoved,
+ OUString const & identifier);
+ };
+ friend class TypelibraryPackageImpl;
+
+ /** Serves for unregistering packages that were registered on a
+ different platform. This can happen if one has remotely mounted
+ /home, for example.
+ */
+ class OtherPlatformPackageImpl : public ::dp_registry::backend::Package
+ {
+ public:
+ OtherPlatformPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier, OUString platform);
+
+ private:
+ BackendImpl * getMyBackend() const;
+
+ Reference<registry::XSimpleRegistry> impl_openRDB() const;
+ Reference<XInterface> impl_createInstance(OUString const& rService) const;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ private:
+ OUString const m_aPlatform;
+ };
+ friend class OtherPlatformPackageImpl;
+
+ std::deque<OUString> m_jar_typelibs;
+ std::deque<OUString> m_rdb_typelibs;
+ std::deque<OUString> m_components;
+
+ enum RcItem { RCITEM_JAR_TYPELIB, RCITEM_RDB_TYPELIB, RCITEM_COMPONENTS };
+
+ std::deque<OUString> & getRcItemList( RcItem kind ) {
+ switch (kind)
+ {
+ case RCITEM_JAR_TYPELIB:
+ return m_jar_typelibs;
+ case RCITEM_RDB_TYPELIB:
+ return m_rdb_typelibs;
+ default: // case RCITEM_COMPONENTS
+ return m_components;
+ }
+ }
+
+ bool m_unorc_inited;
+ bool m_unorc_modified;
+ bool bSwitchedRdbFiles;
+
+ typedef std::unordered_map< OUString, Reference<XInterface> > t_string2object;
+ t_string2object m_backendObjects;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL disposing() override;
+
+ const Reference<deployment::XPackageTypeInfo> m_xDynComponentTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xJavaComponentTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xPythonComponentTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xComponentsTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xRDBTypelibTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xJavaTypelibTypeInfo;
+ Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
+
+ OUString m_commonRDB;
+ OUString m_nativeRDB;
+
+ //URLs of the original rdbs (before any switching):
+ OUString m_commonRDB_orig;
+ OUString m_nativeRDB_orig;
+
+ std::unique_ptr<ComponentBackendDb> m_backendDb;
+
+ void addDataToDb(OUString const & url, ComponentBackendDb::Data const & data);
+ ComponentBackendDb::Data readDataFromDb(std::u16string_view url);
+ void revokeEntryFromDb(std::u16string_view url);
+
+ Reference<registry::XSimpleRegistry> m_xCommonRDB;
+ Reference<registry::XSimpleRegistry> m_xNativeRDB;
+
+ void unorc_verify_init( Reference<XCommandEnvironment> const & xCmdEnv );
+ void unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv );
+
+ Reference<XInterface> getObject( OUString const & id );
+ Reference<XInterface> insertObject(
+ OUString const & id, Reference<XInterface> const & xObject );
+ void releaseObject( OUString const & id );
+
+ void addToUnoRc( RcItem kind, OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv );
+ void removeFromUnoRc( RcItem kind, OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv );
+ bool hasInUnoRc( RcItem kind, OUString const & url );
+
+ css::uno::Reference< css::uno::XComponentContext > getRootContext() const;
+
+public:
+ BackendImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+ using PackageRegistryBackend::disposing;
+
+ //Will be called from ComponentPackageImpl
+ void initServiceRdbFiles();
+};
+
+
+BackendImpl::ComponentPackageImpl::ComponentPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ OUString loader, bool bRemoved,
+ OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier),
+ m_loader(std::move( loader )),
+ m_registered( Reg::Uninit )
+{}
+
+Reference<registry::XSimpleRegistry>
+BackendImpl::ComponentPackageImpl::getRDB() const
+{
+ BackendImpl * that = getMyBackend();
+
+ //Late "initialization" of the services rdb files
+ //This is to prevent problems when running several
+ //instances of OOo with root rights in parallel. This
+ //would otherwise cause problems when copying the rdbs.
+ //See http://qa.openoffice.org/issues/show_bug.cgi?id=99257
+ {
+ const ::osl::MutexGuard guard( m_aMutex );
+ if (!that->bSwitchedRdbFiles)
+ {
+ that->bSwitchedRdbFiles = true;
+ that->initServiceRdbFiles();
+ }
+ }
+ if ( m_loader == "com.sun.star.loader.SharedLibrary" )
+ return that->m_xNativeRDB;
+ else
+ return that->m_xCommonRDB;
+}
+
+BackendImpl * BackendImpl::ComponentPackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //Throws a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException(
+ "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<ComponentPackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+
+void BackendImpl::disposing()
+{
+ try {
+ m_backendObjects = t_string2object();
+ if (m_xNativeRDB.is()) {
+ m_xNativeRDB->close();
+ m_xNativeRDB.clear();
+ }
+ if (m_xCommonRDB.is()) {
+ m_xCommonRDB->close();
+ m_xCommonRDB.clear();
+ }
+ unorc_flush( Reference<XCommandEnvironment>() );
+
+ PackageRegistryBackend::disposing();
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException(
+ "caught unexpected exception while disposing...",
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+void BackendImpl::initServiceRdbFiles()
+{
+ const Reference<XCommandEnvironment> xCmdEnv;
+
+ ::ucbhelper::Content cacheDir( getCachePath(), xCmdEnv, m_xComponentContext );
+ ::ucbhelper::Content oldRDB;
+ // switch common rdb:
+ if (!m_commonRDB_orig.isEmpty())
+ {
+ (void)create_ucb_content(
+ &oldRDB, makeURL( getCachePath(), m_commonRDB_orig),
+ xCmdEnv, false /* no throw */ );
+ }
+ m_commonRDB = m_commonRDB_orig == "common.rdb" ? std::u16string_view(u"common_.rdb") : std::u16string_view(u"common.rdb");
+ if (oldRDB.get().is())
+ {
+ cacheDir.transferContent(
+ oldRDB, ::ucbhelper::InsertOperation::Copy,
+ m_commonRDB, NameClash::OVERWRITE );
+ oldRDB = ::ucbhelper::Content();
+ }
+ // switch native rdb:
+ if (!m_nativeRDB_orig.isEmpty())
+ {
+ (void)create_ucb_content(
+ &oldRDB, makeURL(getCachePath(), m_nativeRDB_orig),
+ xCmdEnv, false /* no throw */ );
+ }
+ const OUString plt_rdb( getPlatformString() + ".rdb" );
+ const OUString plt_rdb_( getPlatformString() + "_.rdb" );
+ m_nativeRDB = (m_nativeRDB_orig == plt_rdb ) ? plt_rdb_ : plt_rdb;
+ if (oldRDB.get().is())
+ {
+ cacheDir.transferContent(
+ oldRDB, ::ucbhelper::InsertOperation::Copy,
+ m_nativeRDB, NameClash::OVERWRITE );
+ }
+
+ // UNO is bootstrapped, flush for next process start:
+ m_unorc_modified = true;
+ unorc_flush( Reference<XCommandEnvironment>() );
+
+
+ // common rdb for java, native rdb for shared lib components
+ if (!m_commonRDB.isEmpty()) {
+ m_xCommonRDB.set(
+ m_xComponentContext->getServiceManager()
+ ->createInstanceWithContext(
+ "com.sun.star.registry.SimpleRegistry",
+ m_xComponentContext ), UNO_QUERY_THROW );
+ m_xCommonRDB->open(
+ makeURL( expandUnoRcUrl(getCachePath()), m_commonRDB ),
+ false, true);
+ }
+ if (!m_nativeRDB.isEmpty()) {
+ m_xNativeRDB.set(
+ m_xComponentContext->getServiceManager()
+ ->createInstanceWithContext(
+ "com.sun.star.registry.SimpleRegistry",
+ m_xComponentContext ), UNO_QUERY_THROW );
+ m_xNativeRDB->open(
+ makeURL( expandUnoRcUrl(getCachePath()), m_nativeRDB ),
+ false, true);
+ }
+}
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : PackageRegistryBackend( args, xComponentContext ),
+ m_unorc_inited( false ),
+ m_unorc_modified( false ),
+ bSwitchedRdbFiles(false),
+ m_xDynComponentTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-component;type=native;platform=" +
+ getPlatformString(),
+ "*" SAL_DLLEXTENSION,
+ DpResId(RID_STR_DYN_COMPONENT)
+ ) ),
+ m_xJavaComponentTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-component;type=Java",
+ "*.jar",
+ DpResId(RID_STR_JAVA_COMPONENT)
+ ) ),
+ m_xPythonComponentTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-component;type=Python",
+ "*.py",
+ DpResId(
+ RID_STR_PYTHON_COMPONENT)
+ ) ),
+ m_xComponentsTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-components",
+ "*.components",
+ DpResId(RID_STR_COMPONENTS)
+ ) ),
+ m_xRDBTypelibTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-typelibrary;type=RDB",
+ "*.rdb",
+ DpResId(RID_STR_RDB_TYPELIB)
+ ) ),
+ m_xJavaTypelibTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.uno-typelibrary;type=Java",
+ "*.jar",
+ DpResId(RID_STR_JAVA_TYPELIB)
+ ) ),
+ m_typeInfos{ m_xDynComponentTypeInfo, m_xJavaComponentTypeInfo, m_xPythonComponentTypeInfo,
+ m_xComponentsTypeInfo, m_xRDBTypelibTypeInfo, m_xJavaTypelibTypeInfo }
+{
+ const Reference<XCommandEnvironment> xCmdEnv;
+
+ if (transientMode())
+ {
+ // in-mem rdbs:
+ // common rdb for java, native rdb for shared lib components
+ m_xCommonRDB.set(
+ xComponentContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.registry.SimpleRegistry",
+ xComponentContext ), UNO_QUERY_THROW );
+ m_xCommonRDB->open( OUString() /* in-mem */,
+ false /* ! read-only */, true /* create */ );
+ m_xNativeRDB.set(
+ xComponentContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.registry.SimpleRegistry",
+ xComponentContext ), UNO_QUERY_THROW );
+ m_xNativeRDB->open( OUString() /* in-mem */,
+ false /* ! read-only */, true /* create */ );
+ }
+ else
+ {
+ unorc_verify_init( xCmdEnv );
+ OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
+ m_backendDb.reset(
+ new ComponentBackendDb(getComponentContext(), dbFile));
+ }
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.component.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+void BackendImpl::addDataToDb(
+ OUString const & url, ComponentBackendDb::Data const & data)
+{
+ if (m_backendDb)
+ m_backendDb->addEntry(url, data);
+}
+
+ComponentBackendDb::Data BackendImpl::readDataFromDb(std::u16string_view url)
+{
+ ComponentBackendDb::Data data;
+ if (m_backendDb)
+ data = m_backendDb->getEntry(url);
+ return data;
+}
+
+void BackendImpl::revokeEntryFromDb(std::u16string_view url)
+{
+ if (m_backendDb)
+ m_backendDb->revokeEntry(url);
+}
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return m_typeInfos;
+}
+
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ OUString mediaType(mediaType_);
+ if ( mediaType.isEmpty() || mediaType == "application/vnd.sun.star.uno-component" || mediaType == "application/vnd.sun.star.uno-typelibrary" )
+ {
+ // detect exact media-type:
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, url, xCmdEnv )) {
+ const OUString title( StrTitle::getTitle( ucbContent ) );
+ if (title.endsWithIgnoreAsciiCase(SAL_DLLEXTENSION))
+ {
+ mediaType = "application/vnd.sun.star.uno-component;type=native;platform=" +
+ getPlatformString();
+ }
+ else if (title.endsWithIgnoreAsciiCase(".jar"))
+ {
+ if (jarManifestHeaderPresent(
+ url, u"RegistrationClassName", xCmdEnv ))
+ mediaType = "application/vnd.sun.star.uno-component;type=Java";
+ if (mediaType.isEmpty())
+ mediaType = "application/vnd.sun.star.uno-typelibrary;type=Java";
+ }
+ else if (title.endsWithIgnoreAsciiCase(".py"))
+ mediaType = "application/vnd.sun.star.uno-component;type=Python";
+ else if (title.endsWithIgnoreAsciiCase(".rdb"))
+ mediaType = "application/vnd.sun.star.uno-typelibrary;type=RDB";
+ }
+ if (mediaType.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ OUString name;
+ if (!bRemoved)
+ {
+ ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext );
+ name = StrTitle::getTitle( ucbContent );
+ }
+
+ if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-component"))
+ {
+ // xxx todo: probe and evaluate component xml description
+
+ auto const iter = params.find("platform"_ostr);
+ bool bPlatformFits(iter == params.end());
+ OUString aPlatform;
+ if (!bPlatformFits) // platform is specified, we have to check
+ {
+ aPlatform = iter->second.m_sValue;
+ bPlatformFits = platform_fits(aPlatform);
+ }
+ // If the package is being removed, do not care whether
+ // platform fits. We won't be using it anyway.
+ if (bPlatformFits || bRemoved) {
+ auto const iterType = params.find("type"_ostr);
+ if (iterType != params.end())
+ {
+ OUString const & value = iterType->second.m_sValue;
+ if (value.equalsIgnoreAsciiCase("native")) {
+ if (bPlatformFits)
+ return new BackendImpl::ComponentPackageImpl(
+ this, url, name, m_xDynComponentTypeInfo,
+ "com.sun.star.loader.SharedLibrary",
+ bRemoved, identifier);
+ else
+ return new BackendImpl::OtherPlatformPackageImpl(
+ this, url, name, m_xDynComponentTypeInfo,
+ bRemoved, identifier, aPlatform);
+ }
+ if (value.equalsIgnoreAsciiCase("Java")) {
+ return new BackendImpl::ComponentPackageImpl(
+ this, url, name, m_xJavaComponentTypeInfo,
+ "com.sun.star.loader.Java2",
+ bRemoved, identifier);
+ }
+ if (value.equalsIgnoreAsciiCase("Python")) {
+ return new BackendImpl::ComponentPackageImpl(
+ this, url, name, m_xPythonComponentTypeInfo,
+ "com.sun.star.loader.Python",
+ bRemoved, identifier);
+ }
+ }
+ }
+ }
+ else if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-components"))
+ {
+ auto const iter = params.find("platform"_ostr);
+ if (iter == params.end() || platform_fits(iter->second.m_sValue)) {
+ return new BackendImpl::ComponentsPackageImpl(
+ this, url, name, m_xComponentsTypeInfo, bRemoved,
+ identifier);
+ }
+ }
+ else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-typelibrary"))
+ {
+ auto const iter = params.find("type"_ostr);
+ if (iter != params.end()) {
+ OUString const & value = iter->second.m_sValue;
+ if (value.equalsIgnoreAsciiCase("RDB"))
+ {
+ return new BackendImpl::TypelibraryPackageImpl(
+ this, url, name, m_xRDBTypelibTypeInfo,
+ false /* rdb */, bRemoved, identifier);
+ }
+ if (value.equalsIgnoreAsciiCase("Java")) {
+ return new BackendImpl::TypelibraryPackageImpl(
+ this, url, name, m_xJavaTypelibTypeInfo,
+ true /* jar */, bRemoved, identifier);
+ }
+ }
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+
+void BackendImpl::unorc_verify_init(
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (transientMode())
+ return;
+ const ::osl::MutexGuard guard( m_aMutex );
+ if ( m_unorc_inited)
+ return;
+
+ // common rc:
+ ::ucbhelper::Content ucb_content;
+ if (create_ucb_content(
+ &ucb_content,
+ makeURL( getCachePath(), "unorc" ),
+ xCmdEnv, false /* no throw */ ))
+ {
+ OUString line;
+ if (readLine( &line, u"UNO_JAVA_CLASSPATH=", ucb_content,
+ RTL_TEXTENCODING_UTF8 ))
+ {
+ sal_Int32 index = sizeof ("UNO_JAVA_CLASSPATH=") - 1;
+ do {
+ OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) );
+ if (!token.isEmpty())
+ {
+ if (create_ucb_content(
+ nullptr, expandUnoRcTerm(token), xCmdEnv,
+ false /* no throw */ ))
+ {
+ //The jar file may not exist anymore if a shared or bundled
+ //extension was removed, but it can still be in the unorc
+ //After running XExtensionManager::synchronize, the unorc is
+ //cleaned up
+ m_jar_typelibs.push_back( token );
+ }
+ }
+ }
+ while (index >= 0);
+ }
+ if (readLine( &line, u"UNO_TYPES=", ucb_content,
+ RTL_TEXTENCODING_UTF8 )) {
+ sal_Int32 index = sizeof ("UNO_TYPES=") - 1;
+ do {
+ OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) );
+ if (!token.isEmpty())
+ {
+ if (token[ 0 ] == '?')
+ token = token.copy( 1 );
+ if (create_ucb_content(
+ nullptr, expandUnoRcTerm(token), xCmdEnv,
+ false /* no throw */ ))
+ {
+ //The RDB file may not exist anymore if a shared or bundled
+ //extension was removed, but it can still be in the unorc.
+ //After running XExtensionManager::synchronize, the unorc is
+ //cleaned up
+ m_rdb_typelibs.push_back( token );
+ }
+ }
+ }
+ while (index >= 0);
+ }
+ if (readLine( &line, u"UNO_SERVICES=", ucb_content,
+ RTL_TEXTENCODING_UTF8 ))
+ {
+ // The UNO_SERVICES line always has the BNF form
+ // "UNO_SERVICES="
+ // ("?$ORIGIN/" <common-rdb>)? -- first
+ // "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}"? -- second
+ // ("?" ("BUNDLED_EXTENSIONS" | -- third
+ // "UNO_SHARED_PACKAGES_CACHE" | "UNO_USER_PACKAGES_CACHE")
+ // ...)*
+ // so can unambiguously be split into its three parts:
+ int state = 1;
+ for (sal_Int32 i = RTL_CONSTASCII_LENGTH("UNO_SERVICES=");
+ i >= 0;)
+ {
+ OUString token(line.getToken(0, ' ', i));
+ if (!token.isEmpty())
+ {
+ if (state == 1 && token.match("?$ORIGIN/"))
+ {
+ m_commonRDB_orig = token.copy(
+ RTL_CONSTASCII_LENGTH("?$ORIGIN/"));
+ state = 2;
+ }
+ else if ( state <= 2 && token == "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" )
+ {
+ state = 3;
+ }
+ else
+ {
+ if (token[0] == '?')
+ {
+ token = token.copy(1);
+ }
+ m_components.push_back(token);
+ state = 3;
+ }
+ }
+ }
+ }
+
+ // native rc:
+ if (create_ucb_content(
+ &ucb_content,
+ makeURL( getCachePath(), getPlatformString() + "rc"),
+ xCmdEnv, false /* no throw */ )) {
+ if (readLine( &line, u"UNO_SERVICES=", ucb_content,
+ RTL_TEXTENCODING_UTF8 )) {
+ m_nativeRDB_orig = line.copy(
+ sizeof ("UNO_SERVICES=?$ORIGIN/") - 1 );
+ }
+ }
+ }
+ m_unorc_modified = false;
+ m_unorc_inited = true;
+}
+
+
+void BackendImpl::unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (transientMode())
+ return;
+ if (!m_unorc_inited || !m_unorc_modified)
+ return;
+
+ OUString sOrigin = dp_misc::makeRcTerm(m_cachePath);
+ OString osOrigin = OUStringToOString(sOrigin, RTL_TEXTENCODING_UTF8);
+ OStringBuffer buf("ORIGIN=" + osOrigin + OStringChar(LF));
+
+ if (! m_jar_typelibs.empty())
+ {
+ auto iPos( m_jar_typelibs.cbegin() );
+ auto const iEnd( m_jar_typelibs.cend() );
+ buf.append( "UNO_JAVA_CLASSPATH=" );
+ while (iPos != iEnd) {
+ // encoded ASCII file-urls:
+ const OString item(
+ OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) );
+ buf.append( item );
+ ++iPos;
+ if (iPos != iEnd)
+ buf.append( ' ' );
+ }
+ buf.append(LF);
+ }
+ if (! m_rdb_typelibs.empty())
+ {
+ auto iPos( m_rdb_typelibs.cbegin() );
+ auto const iEnd( m_rdb_typelibs.cend() );
+ buf.append( "UNO_TYPES=" );
+ while (iPos != iEnd) {
+ buf.append( '?' );
+ // encoded ASCII file-urls:
+ const OString item(
+ OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) );
+ buf.append( item );
+ ++iPos;
+ if (iPos != iEnd)
+ buf.append( ' ' );
+ }
+ buf.append(LF);
+ }
+
+ // If we duplicated the common or native rdb then we must use those urls
+ //otherwise we use those of the original files. That is, m_commonRDB_orig
+ //and m_nativeRDB_orig;
+ OUString sCommonRDB(m_commonRDB.isEmpty() ? m_commonRDB_orig : m_commonRDB );
+ OUString sNativeRDB(m_nativeRDB.isEmpty() ? m_nativeRDB_orig : m_nativeRDB );
+
+ if (!sCommonRDB.isEmpty() || !sNativeRDB.isEmpty() ||
+ !m_components.empty())
+ {
+ buf.append( "UNO_SERVICES=" );
+ bool space = false;
+ if (!sCommonRDB.isEmpty())
+ {
+ buf.append( "?$ORIGIN/"
+ + OUStringToOString( sCommonRDB, RTL_TEXTENCODING_ASCII_US ) );
+ space = true;
+ }
+ if (!sNativeRDB.isEmpty())
+ {
+ if (space)
+ {
+ buf.append(' ');
+ }
+ buf.append( "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" );
+ space = true;
+
+ // write native rc:
+ OString buf2 =
+ "ORIGIN=" +
+ osOrigin +
+ OStringChar(LF) +
+ "UNO_SERVICES=?$ORIGIN/" +
+ OUStringToOString( sNativeRDB, RTL_TEXTENCODING_ASCII_US ) +
+ OStringChar(LF);
+
+ const Reference<io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(buf2.getStr()),
+ buf2.getLength() ) );
+ ::ucbhelper::Content ucb_content(
+ makeURL( getCachePath(), getPlatformString() + "rc" ),
+ xCmdEnv, m_xComponentContext );
+ ucb_content.writeStream( xData, true /* replace existing */ );
+ }
+ for (auto const& component : m_components)
+ {
+ if (space)
+ {
+ buf.append(' ');
+ }
+ buf.append("?" + OUStringToOString(component, RTL_TEXTENCODING_UTF8));
+ space = true;
+ }
+ buf.append(LF);
+ }
+
+ // write unorc:
+ const Reference<io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(buf.getStr()),
+ buf.getLength() ) );
+ ::ucbhelper::Content ucb_content(
+ makeURL( getCachePath(), "unorc" ), xCmdEnv, m_xComponentContext );
+ ucb_content.writeStream( xData, true /* replace existing */ );
+
+ m_unorc_modified = false;
+}
+
+
+void BackendImpl::addToUnoRc( RcItem kind, OUString const & url_,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ const OUString rcterm( dp_misc::makeRcTerm(url_) );
+ const ::osl::MutexGuard guard( m_aMutex );
+ unorc_verify_init( xCmdEnv );
+ std::deque<OUString> & rSet = getRcItemList(kind);
+ if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) {
+ rSet.push_front( rcterm ); // prepend to list, thus overriding
+ // write immediately:
+ m_unorc_modified = true;
+ unorc_flush( xCmdEnv );
+ }
+}
+
+
+void BackendImpl::removeFromUnoRc(
+ RcItem kind, OUString const & url_,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ const OUString rcterm( dp_misc::makeRcTerm(url_) );
+ const ::osl::MutexGuard guard( m_aMutex );
+ unorc_verify_init( xCmdEnv );
+ std::deque<OUString> & aRcItemList = getRcItemList(kind);
+ std::erase(aRcItemList, rcterm);
+ // write immediately:
+ m_unorc_modified = true;
+ unorc_flush( xCmdEnv );
+}
+
+
+bool BackendImpl::hasInUnoRc(
+ RcItem kind, OUString const & url_ )
+{
+ const OUString rcterm( dp_misc::makeRcTerm(url_) );
+ const ::osl::MutexGuard guard( m_aMutex );
+ std::deque<OUString> const & rSet = getRcItemList(kind);
+ return std::find( rSet.begin(), rSet.end(), rcterm ) != rSet.end();
+}
+
+css::uno::Reference< css::uno::XComponentContext > BackendImpl::getRootContext()
+ const
+{
+ css::uno::Reference< css::uno::XComponentContext > rootContext(
+ getComponentContext()->getValueByName("_root"),
+ css::uno::UNO_QUERY);
+ return rootContext.is() ? rootContext : getComponentContext();
+}
+
+
+void BackendImpl::releaseObject( OUString const & id )
+{
+ const ::osl::MutexGuard guard( m_aMutex );
+ m_backendObjects.erase( id );
+}
+
+
+Reference<XInterface> BackendImpl::getObject( OUString const & id )
+{
+ const ::osl::MutexGuard guard( m_aMutex );
+ const t_string2object::const_iterator iFind( m_backendObjects.find( id ) );
+ if (iFind == m_backendObjects.end())
+ return Reference<XInterface>();
+ else
+ return iFind->second;
+}
+
+
+Reference<XInterface> BackendImpl::insertObject(
+ OUString const & id, Reference<XInterface> const & xObject )
+{
+ const ::osl::MutexGuard guard( m_aMutex );
+ const std::pair<t_string2object::iterator, bool> insertion(
+ m_backendObjects.emplace( id, xObject ) );
+ return insertion.first->second;
+}
+
+
+Reference<XComponentContext> raise_uno_process(
+ Reference<XComponentContext> const & xContext,
+ ::rtl::Reference<AbortChannel> const & abortChannel )
+{
+ OSL_ASSERT( xContext.is() );
+
+ OUString url( util::theMacroExpander::get(xContext)->expandMacros( "$URE_BIN_DIR/uno" ) );
+
+ const OUString connectStr = "uno:pipe,name=" + generateRandomPipeId() + ";urp;uno.ComponentContext";
+
+ // raise core UNO process to register/run a component,
+ // javavm service uses unorc next to executable to retrieve deployed
+ // jar typelibs
+
+ std::vector<OUString> args{
+#if OSL_DEBUG_LEVEL == 0
+ "--quiet",
+#endif
+ "--singleaccept",
+ "-u",
+ connectStr,
+ // don't inherit from unorc:
+ "-env:INIFILENAME=" };
+
+ //now add the bootstrap variables which were supplied on the command line
+ std::vector<OUString> bootvars = getCmdBootstrapVariables();
+ args.insert(args.end(), bootvars.begin(), bootvars.end());
+
+ oslProcess hProcess;
+ try {
+ hProcess = raiseProcess(
+ url, comphelper::containerToSequence(args) );
+ }
+ catch (...) {
+ OUStringBuffer sMsg = "error starting process: " + url;
+ for(const auto& arg : args)
+ sMsg.append(" " + arg);
+ throw uno::RuntimeException(sMsg.makeStringAndClear());
+ }
+ try {
+ return Reference<XComponentContext>(
+ resolveUnoURL( connectStr, xContext, abortChannel.get() ),
+ UNO_QUERY_THROW );
+ }
+ catch (...) {
+ // try to terminate process:
+ if ( osl_terminateProcess( hProcess ) != osl_Process_E_None )
+ {
+ OSL_ASSERT( false );
+ }
+ throw;
+ }
+}
+
+void extractComponentData(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ css::uno::Reference< css::registry::XRegistryKey > const & registry,
+ ComponentBackendDb::Data * data,
+ std::vector< css::uno::Reference< css::uno::XInterface > > * factories,
+ css::uno::Reference< css::loader::XImplementationLoader > const &
+ componentLoader,
+ OUString const & componentUrl)
+{
+ OSL_ASSERT(
+ context.is() && registry.is() && data != nullptr && componentLoader.is());
+ OUString registryName(registry->getKeyName());
+ sal_Int32 prefix = registryName.getLength();
+ if (!registryName.endsWith("/")) {
+ prefix += RTL_CONSTASCII_LENGTH("/");
+ }
+ const css::uno::Sequence< css::uno::Reference< css::registry::XRegistryKey > >
+ keys(registry->openKeys());
+ css::uno::Reference< css::lang::XMultiComponentFactory > smgr(
+ context->getServiceManager(), css::uno::UNO_SET_THROW);
+ for (css::uno::Reference< css::registry::XRegistryKey > const & key : keys) {
+ OUString name(key->getKeyName().copy(prefix));
+ data->implementationNames.push_back(name);
+ css::uno::Reference< css::registry::XRegistryKey > singletons(
+ key->openKey("UNO/SINGLETONS"));
+ if (singletons.is()) {
+ sal_Int32 prefix2 = key->getKeyName().getLength() +
+ RTL_CONSTASCII_LENGTH("/UNO/SINGLETONS/");
+ const css::uno::Sequence<
+ css::uno::Reference< css::registry::XRegistryKey > >
+ singletonKeys(singletons->openKeys());
+ for (css::uno::Reference< css::registry::XRegistryKey > const & singletonKey : singletonKeys) {
+ data->singletons.emplace_back(
+ singletonKey->getKeyName().copy(prefix2), name);
+ }
+ }
+ if (factories != nullptr) {
+ factories->push_back(
+ componentLoader->activate(
+ name, OUString(), componentUrl, key));
+ }
+ }
+}
+
+void BackendImpl::ComponentPackageImpl::getComponentInfo(
+ ComponentBackendDb::Data * data,
+ std::vector< css::uno::Reference< css::uno::XInterface > > * factories,
+ Reference<XComponentContext> const & xContext )
+{
+ const Reference<loader::XImplementationLoader> xLoader(
+ xContext->getServiceManager()->createInstanceWithContext(
+ m_loader, xContext ), UNO_QUERY );
+ if (! xLoader.is())
+ {
+ throw css::deployment::DeploymentException(
+ "cannot instantiate loader " + m_loader,
+ static_cast< OWeakObject * >(this), Any());
+ }
+
+ // HACK: highly dependent on stoc/source/servicemanager
+ // and stoc/source/implreg implementation which rely on the same
+ // services.rdb format!
+ // .../UNO/LOCATION and .../UNO/ACTIVATOR appear not to be written by
+ // writeRegistryInfo, however, but are known, fixed values here, so
+ // can be passed into extractComponentData
+ OUString url(getURL());
+ const Reference<registry::XSimpleRegistry> xMemReg(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.registry.SimpleRegistry", xContext ),
+ UNO_QUERY_THROW );
+ xMemReg->open( OUString() /* in mem */, false, true );
+ xLoader->writeRegistryInfo( xMemReg->getRootKey(), OUString(), url );
+ extractComponentData(
+ xContext, xMemReg->getRootKey(), data, factories, xLoader, url);
+}
+
+void BackendImpl::ComponentPackageImpl::componentLiveInsertion(
+ ComponentBackendDb::Data const & data,
+ std::vector< css::uno::Reference< css::uno::XInterface > > const &
+ factories)
+{
+ css::uno::Reference< css::uno::XComponentContext > rootContext(
+ getMyBackend()->getRootContext());
+ css::uno::Reference< css::container::XSet > set(
+ rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW);
+ std::vector< css::uno::Reference< css::uno::XInterface > >::const_iterator
+ factory(factories.begin());
+ for (auto const& implementationName : data.implementationNames)
+ {
+ try {
+ set->insert(css::uno::Any(*factory++));
+ } catch (const container::ElementExistException &) {
+ SAL_WARN("desktop.deployment", "implementation already registered " << implementationName);
+ }
+ }
+ if (data.singletons.empty()) return;
+
+ css::uno::Reference< css::container::XNameContainer > cont(
+ rootContext, css::uno::UNO_QUERY_THROW);
+ for (auto const& singleton : data.singletons)
+ {
+ OUString name("/singletons/" + singleton.first);
+ //TODO: Update should be atomic:
+ try {
+ cont->removeByName( name + "/arguments");
+ } catch (const container::NoSuchElementException &) {}
+ try {
+ cont->insertByName( name + "/service", css::uno::Any(singleton.second));
+ } catch (const container::ElementExistException &) {
+ cont->replaceByName( name + "/service", css::uno::Any(singleton.second));
+ }
+ try {
+ cont->insertByName(name, css::uno::Any());
+ } catch (const container::ElementExistException &) {
+ SAL_WARN("desktop.deployment", "singleton already registered " << singleton.first);
+ cont->replaceByName(name, css::uno::Any());
+ }
+ }
+}
+
+void BackendImpl::ComponentPackageImpl::componentLiveRemoval(
+ ComponentBackendDb::Data const & data)
+{
+ css::uno::Reference< css::uno::XComponentContext > rootContext(
+ getMyBackend()->getRootContext());
+ css::uno::Reference< css::container::XSet > set(
+ rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW);
+ for (auto const& implementationName : data.implementationNames)
+ {
+ try {
+ set->remove(css::uno::Any(implementationName));
+ } catch (const css::container::NoSuchElementException &) {
+ // ignore if factory has not been live deployed
+ }
+ }
+ if (data.singletons.empty())
+ return;
+
+ css::uno::Reference< css::container::XNameContainer > cont(
+ rootContext, css::uno::UNO_QUERY_THROW);
+ for (auto const& singleton : data.singletons)
+ {
+ OUString name("/singletons/" + singleton.first);
+ //TODO: Removal should be atomic:
+ try {
+ cont->removeByName(name);
+ } catch (const container::NoSuchElementException &) {}
+ try {
+ cont->removeByName( name + "/service" );
+ } catch (const container::NoSuchElementException &) {}
+ try {
+ cont->removeByName( name + "/arguments" );
+ } catch (const container::NoSuchElementException &) {}
+ }
+}
+
+// Package
+
+//We could use here BackendImpl::hasActiveEntry. However, this check is just as well.
+//And it also shows the problem if another extension has overwritten an implementation
+//entry, because it contains the same service implementation
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::ComponentPackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & )
+{
+ if (m_registered == Reg::Uninit)
+ {
+ m_registered = Reg::NotRegistered;
+ const Reference<registry::XSimpleRegistry> xRDB( getRDB() );
+ if (xRDB.is())
+ {
+ bool bAmbiguousComponentName = false;
+ // lookup rdb for location URL:
+ const Reference<registry::XRegistryKey> xRootKey(
+ xRDB->getRootKey() );
+ const Reference<registry::XRegistryKey> xImplKey(
+ xRootKey->openKey( "IMPLEMENTATIONS" ) );
+ Sequence<OUString> implNames;
+ if (xImplKey.is() && xImplKey->isValid())
+ implNames = xImplKey->getKeyNames();
+ OUString const * pImplNames = implNames.getConstArray();
+ sal_Int32 pos = implNames.getLength();
+ for ( ; pos--; )
+ {
+ checkAborted( abortChannel );
+ const OUString key(
+ pImplNames[ pos ] + "/UNO/LOCATION" );
+ const Reference<registry::XRegistryKey> xKey(
+ xRootKey->openKey(key) );
+ if (xKey.is() && xKey->isValid())
+ {
+ const OUString location( xKey->getAsciiValue() );
+ if (location.equalsIgnoreAsciiCase( getURL() ))
+ {
+ break;
+ }
+ else
+ {
+ //try to match only the file name
+ OUString thisUrl(getURL());
+ std::u16string_view thisFileName(thisUrl.subView(thisUrl.lastIndexOf('/')));
+
+ std::u16string_view locationFileName(location.subView(location.lastIndexOf('/')));
+ if (o3tl::equalsIgnoreAsciiCase(locationFileName, thisFileName))
+ bAmbiguousComponentName = true;
+ }
+ }
+ }
+ if (pos >= 0)
+ m_registered = Reg::Registered;
+ else if (bAmbiguousComponentName)
+ m_registered = Reg::MaybeRegistered;
+ }
+ }
+
+ //Different extensions can use the same service implementations. Then the extensions
+ //which was installed last will overwrite the one from the other extension. That is
+ //the registry will contain the path (the location) of the library or jar of the
+ //second extension. In this case isRegistered called for the lib of the first extension
+ //would return "not registered". That would mean that during uninstallation
+ //XPackage::registerPackage is not called, because it just was not registered. This is,
+ //however, necessary for jar files. Registering and unregistering update
+ //uno_packages/cache/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc
+ //Therefore, we will return always "is ambiguous" if the path of this component cannot
+ //be found in the registry and if there is another path and both have the same file name (but
+ //the rest of the path is different).
+ //If the caller cannot precisely determine that this package was registered, then it must
+ //call registerPackage.
+ bool bAmbiguous = m_registered == Reg::Void // Reg::Void == we are in the progress of unregistration
+ || m_registered == Reg::MaybeRegistered;
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true /* IsPresent */,
+ beans::Ambiguous<sal_Bool>(
+ m_registered == Reg::Registered, bAmbiguous) );
+}
+
+
+void BackendImpl::ComponentPackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ BackendImpl * that = getMyBackend();
+ OUString url(getURL());
+ if (doRegisterPackage) {
+ ComponentBackendDb::Data data;
+ css::uno::Reference< css::uno::XComponentContext > context;
+ if (startup) {
+ context = that->getComponentContext();
+ } else {
+ context.set(that->getObject(url), css::uno::UNO_QUERY);
+ if (!context.is()) {
+ context.set(
+ that->insertObject(
+ url,
+ raise_uno_process(
+ that->getComponentContext(), abortChannel)),
+ css::uno::UNO_QUERY_THROW);
+ }
+ }
+ css::uno::Reference< css::registry::XImplementationRegistration> impreg(
+ context->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.registry.ImplementationRegistration",
+ context),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::registry::XSimpleRegistry > rdb(getRDB());
+ impreg->registerImplementation(m_loader, url, rdb);
+ // Only write to unorc after successful registration; it may fail if
+ // there is no suitable java
+ if (m_loader == "com.sun.star.loader.Java2" && !jarManifestHeaderPresent(url, u"UNO-Type-Path", xCmdEnv))
+ {
+ that->addToUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv);
+ data.javaTypeLibrary = true;
+ }
+ std::vector< css::uno::Reference< css::uno::XInterface > > factories;
+ getComponentInfo(&data, startup ? nullptr : &factories, context);
+ if (!startup) {
+ try {
+ componentLiveInsertion(data, factories);
+ } catch (css::uno::Exception &) {
+ TOOLS_INFO_EXCEPTION("desktop.deployment", "caught");
+ try {
+ impreg->revokeImplementation(url, rdb);
+ } catch (css::uno::RuntimeException &) {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "ignored");
+ }
+ throw;
+ }
+ }
+ m_registered = Reg::Registered;
+ that->addDataToDb(url, data);
+ } else { // revoke
+ m_registered = Reg::Void;
+ ComponentBackendDb::Data data(that->readDataFromDb(url));
+ css::uno::Reference< css::uno::XComponentContext > context(
+ that->getObject(url), css::uno::UNO_QUERY);
+ bool remoteContext = context.is();
+ if (!remoteContext) {
+ context = that->getComponentContext();
+ }
+ if (!startup) {
+ componentLiveRemoval(data);
+ }
+ css::uno::Reference< css::registry::XImplementationRegistration >(
+ context->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.registry.ImplementationRegistration",
+ context),
+ css::uno::UNO_QUERY_THROW)->revokeImplementation(url, getRDB());
+ if (data.javaTypeLibrary) {
+ that->removeFromUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv);
+ }
+ if (remoteContext) {
+ that->releaseObject(url);
+ }
+ m_registered = Reg::NotRegistered;
+ getMyBackend()->revokeEntryFromDb(url);
+ }
+}
+
+BackendImpl::TypelibraryPackageImpl::TypelibraryPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool jarFile, bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier),
+ m_jarFile( jarFile )
+{
+}
+
+// Package
+BackendImpl * BackendImpl::TypelibraryPackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException( "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<TypelibraryPackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::TypelibraryPackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ BackendImpl * that = getMyBackend();
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true /* IsPresent */,
+ beans::Ambiguous<sal_Bool>(
+ that->hasInUnoRc(
+ m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, getURL() ),
+ false /* IsAmbiguous */ ) );
+}
+
+
+void BackendImpl::TypelibraryPackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool /*startup*/,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ BackendImpl * that = getMyBackend();
+ const OUString url( getURL() );
+
+ if (doRegisterPackage)
+ {
+ // live insertion:
+ if (m_jarFile) {
+ // xxx todo add to classpath at runtime: ???
+ //SB: It is probably not worth it to add the live inserted type
+ // library JAR to the UnoClassLoader in the soffice process. Any
+ // live inserted component JAR that might reference this type
+ // library JAR runs in its own uno process, so there is probably no
+ // Java code in the soffice process that would see any UNO types
+ // introduced by this type library JAR.
+ }
+ else // RDB:
+ {
+ css::uno::Reference< css::container::XSet >(
+ that->getComponentContext()->getValueByName(
+ "/singletons"
+ "/com.sun.star.reflection.theTypeDescriptionManager"),
+ css::uno::UNO_QUERY_THROW)->insert(
+ css::uno::Any(expandUnoRcUrl(url)));
+ }
+
+ that->addToUnoRc( m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB,
+ url, xCmdEnv );
+ }
+ else // revokePackage()
+ {
+ that->removeFromUnoRc(
+ m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, url, xCmdEnv );
+
+ // revoking types at runtime, possible, sensible?
+ if (!m_jarFile) {
+ css::uno::Reference< css::container::XSet >(
+ that->getComponentContext()->getValueByName(
+ "/singletons"
+ "/com.sun.star.reflection.theTypeDescriptionManager"),
+ css::uno::UNO_QUERY_THROW)->remove(
+ css::uno::Any(expandUnoRcUrl(url)));
+ }
+ }
+}
+
+BackendImpl::OtherPlatformPackageImpl::OtherPlatformPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier, OUString platform)
+ : Package(myBackend, url, name, name, xPackageType, bRemoved, identifier)
+ , m_aPlatform(std::move(platform))
+{
+ OSL_PRECOND(bRemoved, "this class can only be used for removing packages!");
+}
+
+BackendImpl *
+BackendImpl::OtherPlatformPackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //Throws a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException("Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<OtherPlatformPackageImpl*>(this)));
+ }
+ return pBackend;
+}
+
+Reference<registry::XSimpleRegistry>
+BackendImpl::OtherPlatformPackageImpl::impl_openRDB() const
+{
+ OUString const aRDB(m_aPlatform + ".rdb");
+ OUString const aRDBPath(makeURL(getMyBackend()->getCachePath(), aRDB));
+
+ Reference<registry::XSimpleRegistry> xRegistry;
+
+ try
+ {
+ xRegistry.set(
+ impl_createInstance("com.sun.star.registry.SimpleRegistry"),
+ UNO_QUERY)
+ ;
+ if (xRegistry.is())
+ xRegistry->open(expandUnoRcUrl(aRDBPath), false, false);
+ }
+ catch (registry::InvalidRegistryException const&)
+ {
+ // If the registry does not exist, we do not need to bother at all
+ xRegistry.clear();
+ }
+
+ SAL_WARN_IF( !xRegistry.is(), "desktop.deployment", "could not create registry for the package's platform");
+ return xRegistry;
+}
+
+Reference<XInterface>
+BackendImpl::OtherPlatformPackageImpl::impl_createInstance(OUString const& rService)
+const
+{
+ Reference<XComponentContext> const xContext(getMyBackend()->getComponentContext());
+ OSL_ASSERT(xContext.is());
+ Reference<XInterface> xService;
+ if (xContext.is())
+ xService.set(xContext->getServiceManager()->createInstanceWithContext(rService, xContext));
+ return xService;
+}
+
+beans::Optional<beans::Ambiguous<sal_Bool> >
+BackendImpl::OtherPlatformPackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard& /* guard */,
+ ::rtl::Reference<AbortChannel> const& /* abortChannel */,
+ Reference<XCommandEnvironment> const& /* xCmdEnv */ )
+{
+ return beans::Optional<beans::Ambiguous<sal_Bool> >(true,
+ beans::Ambiguous<sal_Bool>(true, false));
+}
+
+void
+BackendImpl::OtherPlatformPackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard& /* guard */,
+ bool bRegisterPackage,
+ bool /* bStartup */,
+ ::rtl::Reference<AbortChannel> const& /* abortChannel */,
+ Reference<XCommandEnvironment> const& /* xCmdEnv */)
+{
+ OSL_PRECOND(!bRegisterPackage, "this class can only be used for removing packages!");
+
+ OUString const aURL(getURL());
+
+ Reference<registry::XSimpleRegistry> const xServicesRDB(impl_openRDB());
+ Reference<registry::XImplementationRegistration> const xImplReg(
+ impl_createInstance("com.sun.star.registry.ImplementationRegistration"),
+ UNO_QUERY)
+ ;
+ if (xImplReg.is() && xServicesRDB.is())
+ xImplReg->revokeImplementation(aURL, xServicesRDB);
+ if (xServicesRDB.is())
+ xServicesRDB->close();
+
+ getMyBackend()->revokeEntryFromDb(aURL);
+}
+
+BackendImpl * BackendImpl::ComponentsPackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //Throws a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException("Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<ComponentsPackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::ComponentsPackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true,
+ beans::Ambiguous<sal_Bool>(
+ getMyBackend()->hasInUnoRc(RCITEM_COMPONENTS, getURL()), false));
+}
+
+void BackendImpl::ComponentsPackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ BackendImpl * that = getMyBackend();
+ OUString url(getURL());
+ if (doRegisterPackage) {
+ if (!startup) {
+ css::uno::Reference< css::uno::XComponentContext > context(
+ that->getObject(url), css::uno::UNO_QUERY);
+ if (!context.is()) {
+ context.set(
+ that->insertObject(
+ url,
+ raise_uno_process(
+ that->getComponentContext(), abortChannel)),
+ css::uno::UNO_QUERY_THROW);
+ }
+ // This relies on the root component context's service manager
+ // supporting the extended XSet semantics:
+ css::uno::Sequence< css::beans::NamedValue > args
+ {
+ { "uri", css::uno::Any(expandUnoRcUrl(url)) },
+ { "component-context", css::uno::Any(context) }
+ };
+ css::uno::Reference< css::container::XSet > smgr(
+ that->getRootContext()->getServiceManager(),
+ css::uno::UNO_QUERY_THROW);
+ smgr->insert(css::uno::Any(args));
+ }
+ that->addToUnoRc(RCITEM_COMPONENTS, url, xCmdEnv);
+ } else { // revoke
+ that->removeFromUnoRc(RCITEM_COMPONENTS, url, xCmdEnv);
+ if (!startup) {
+ // This relies on the root component context's service manager
+ // supporting the extended XSet semantics:
+ css::uno::Sequence< css::beans::NamedValue > args { { "uri", css::uno::Any(expandUnoRcUrl(url)) } };
+ css::uno::Reference< css::container::XSet > smgr(
+ that->getRootContext()->getServiceManager(),
+ css::uno::UNO_QUERY_THROW);
+ smgr->remove(css::uno::Any(args));
+ }
+ that->releaseObject(url);
+ that->revokeEntryFromDb(url); // in case it got added with old code
+ }
+}
+
+BackendImpl::ComponentsPackageImpl::ComponentsPackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier)
+{}
+
+} // anon namespace
+
+} // namespace dp_registry
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::component::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/configuration/dp_configuration.cxx b/desktop/source/deployment/registry/configuration/dp_configuration.cxx
new file mode 100644
index 0000000000..6228142486
--- /dev/null
+++ b/desktop/source/deployment/registry/configuration/dp_configuration.cxx
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+//TODO: Large parts of this file were copied from dp_component.cxx; those parts
+// should be consolidated.
+
+#include <config_extensions.h>
+
+#include <dp_backend.h>
+#if HAVE_FEATURE_EXTENSIONS
+#include <dp_persmap.h>
+#endif
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <rtl/string.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <ucbhelper/content.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <xmlscript/xml_helper.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/xmlencode.hxx>
+#include <svl/inettype.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/configuration/Update.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <deque>
+#include <memory>
+#include <string_view>
+#include <utility>
+
+#include "dp_configurationbackenddb.hxx"
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend::configuration {
+namespace {
+
+class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
+{
+ class PackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const ;
+
+ const bool m_isSchema;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ public:
+ PackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool isSchema, bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier),
+ m_isSchema( isSchema )
+ {}
+ };
+ friend class PackageImpl;
+
+ std::deque<OUString> m_xcs_files;
+ std::deque<OUString> m_xcu_files;
+ std::deque<OUString> & getFiles( bool xcs ) {
+ return xcs ? m_xcs_files : m_xcu_files;
+ }
+
+ bool m_configmgrini_inited;
+ bool m_configmgrini_modified;
+ std::unique_ptr<ConfigurationBackendDb> m_backendDb;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType, bool bRemoved,
+ OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+#if HAVE_FEATURE_EXTENSIONS
+ // for backwards compatibility - nil if no (compatible) back-compat db present
+ std::unique_ptr<PersistentMap> m_registeredPackages;
+#endif
+
+ virtual void SAL_CALL disposing() override;
+
+ const Reference<deployment::XPackageTypeInfo> m_xConfDataTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xConfSchemaTypeInfo;
+ Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
+
+ void configmgrini_verify_init(
+ Reference<XCommandEnvironment> const & xCmdEnv );
+ void configmgrini_flush( Reference<XCommandEnvironment> const & xCmdEnv );
+
+ /* The parameter isURL is false in the case of adding the conf:ini-entry
+ value from the backend db. This entry already contains the path as it
+ is used in the configmgr.ini.
+ */
+ void addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv );
+#if HAVE_FEATURE_EXTENSIONS
+ bool removeFromConfigmgrIni( bool isSchema, OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv );
+#endif
+ void addDataToDb(OUString const & url, ConfigurationBackendDb::Data const & data);
+ ::std::optional<ConfigurationBackendDb::Data> readDataFromDb(std::u16string_view url);
+ void revokeEntryFromDb(std::u16string_view url);
+ bool hasActiveEntry(std::u16string_view url);
+ bool activateEntry(std::u16string_view url);
+
+public:
+ BackendImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+ using PackageRegistryBackend::disposing;
+};
+
+
+void BackendImpl::disposing()
+{
+ try {
+ configmgrini_flush( Reference<XCommandEnvironment>() );
+
+ PackageRegistryBackend::disposing();
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException(
+ "caught unexpected exception while disposing...",
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : PackageRegistryBackend( args, xComponentContext ),
+ m_configmgrini_inited( false ),
+ m_configmgrini_modified( false ),
+ m_xConfDataTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.configuration-data",
+ "*.xcu",
+ DpResId(RID_STR_CONF_DATA)
+ ) ),
+ m_xConfSchemaTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.configuration-schema",
+ "*.xcs",
+ DpResId(RID_STR_CONF_SCHEMA)
+ ) ),
+ m_typeInfos{ m_xConfDataTypeInfo, m_xConfSchemaTypeInfo }
+{
+ const Reference<XCommandEnvironment> xCmdEnv;
+
+ if (transientMode())
+ {
+ // TODO
+ }
+ else
+ {
+ OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
+ m_backendDb.reset(
+ new ConfigurationBackendDb(getComponentContext(), dbFile));
+ //clean up data folders which are no longer used.
+ //This must not be done in the same process where the help files
+ //are still registers. Only after revoking and restarting OOo the folders
+ //can be removed. This works now, because the extension manager is a singleton
+ //and the backends are only create once per process.
+ std::vector<OUString> folders = m_backendDb->getAllDataUrls();
+ deleteUnusedFolders(folders);
+
+ configmgrini_verify_init( xCmdEnv );
+
+#if HAVE_FEATURE_EXTENSIONS
+ std::unique_ptr<PersistentMap> pMap;
+ OUString aCompatURL( makeURL( getCachePath(), "registered_packages.pmap" ) );
+
+ // Don't create it if it doesn't exist already
+ if ( ::utl::UCBContentHelper::Exists( expandUnoRcUrl( aCompatURL ) ) )
+ {
+ try
+ {
+ pMap.reset( new PersistentMap( aCompatURL ) );
+ }
+ catch (const Exception &e)
+ {
+ OUString aStr = "Exception loading legacy package database: '" +
+ e.Message +
+ "' - ignoring file, please remove it.\n";
+ dp_misc::writeConsole( aStr );
+ }
+ }
+ m_registeredPackages = std::move(pMap);
+#endif
+ }
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.configuration.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+void BackendImpl::addDataToDb(
+ OUString const & url, ConfigurationBackendDb::Data const & data)
+{
+ if (m_backendDb)
+ m_backendDb->addEntry(url, data);
+}
+
+::std::optional<ConfigurationBackendDb::Data> BackendImpl::readDataFromDb(
+ std::u16string_view url)
+{
+ ::std::optional<ConfigurationBackendDb::Data> data;
+ if (m_backendDb)
+ data = m_backendDb->getEntry(url);
+ return data;
+}
+
+void BackendImpl::revokeEntryFromDb(std::u16string_view url)
+{
+ if (m_backendDb)
+ m_backendDb->revokeEntry(url);
+}
+
+bool BackendImpl::hasActiveEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->hasActiveEntry(url);
+ return false;
+}
+
+bool BackendImpl::activateEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->activateEntry(url);
+ return false;
+}
+
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return m_typeInfos;
+}
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ OUString mediaType( mediaType_ );
+ if (mediaType.isEmpty())
+ {
+ // detect media-type:
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, url, xCmdEnv ))
+ {
+ const OUString title( StrTitle::getTitle( ucbContent ) );
+ if (title.endsWithIgnoreAsciiCase( ".xcu" )) {
+ mediaType = "application/vnd.sun.star.configuration-data";
+ }
+ if (title.endsWithIgnoreAsciiCase( ".xcs" )) {
+ mediaType = "application/vnd.sun.star.configuration-schema";
+ }
+ }
+ if (mediaType.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ OUString name;
+ if (!bRemoved)
+ {
+ ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext );
+ name = StrTitle::getTitle( ucbContent );
+ }
+
+ if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data"))
+ {
+ return new PackageImpl(
+ this, url, name, m_xConfDataTypeInfo, false /* data file */,
+ bRemoved, identifier);
+ }
+ else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-schema")) {
+ return new PackageImpl(
+ this, url, name, m_xConfSchemaTypeInfo, true /* schema file */,
+ bRemoved, identifier);
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+
+void BackendImpl::configmgrini_verify_init(
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (transientMode())
+ return;
+ const ::osl::MutexGuard guard( m_aMutex );
+ if ( m_configmgrini_inited)
+ return;
+
+ // common rc:
+ ::ucbhelper::Content ucb_content;
+ if (create_ucb_content(
+ &ucb_content,
+ makeURL( getCachePath(), "configmgr.ini" ),
+ xCmdEnv, false /* no throw */ ))
+ {
+ OUString line;
+ if (readLine( &line, u"SCHEMA=", ucb_content,
+ RTL_TEXTENCODING_UTF8 ))
+ {
+ sal_Int32 index = RTL_CONSTASCII_LENGTH("SCHEMA=");
+ do {
+ OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) );
+ if (!token.isEmpty()) {
+ //The file may not exist anymore if a shared or bundled
+ //extension was removed, but it can still be in the configmgrini.
+ //After running XExtensionManager::synchronize, the configmgrini is
+ //cleaned up
+ m_xcs_files.push_back( token );
+ }
+ }
+ while (index >= 0);
+ }
+ if (readLine( &line, u"DATA=", ucb_content,
+ RTL_TEXTENCODING_UTF8 )) {
+ sal_Int32 index = RTL_CONSTASCII_LENGTH("DATA=");
+ do {
+ std::u16string_view token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) );
+ if (!token.empty())
+ {
+ if (token[ 0 ] == '?')
+ token = token.substr( 1 );
+ //The file may not exist anymore if a shared or bundled
+ //extension was removed, but it can still be in the configmgrini.
+ //After running XExtensionManager::synchronize, the configmgrini is
+ //cleaned up
+ m_xcu_files.push_back( OUString(token) );
+ }
+ }
+ while (index >= 0);
+ }
+ }
+ m_configmgrini_modified = false;
+ m_configmgrini_inited = true;
+}
+
+
+void BackendImpl::configmgrini_flush(
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (transientMode())
+ return;
+ if (!m_configmgrini_inited || !m_configmgrini_modified)
+ return;
+
+ OStringBuffer buf;
+ if (! m_xcs_files.empty())
+ {
+ auto iPos( m_xcs_files.cbegin() );
+ auto const iEnd( m_xcs_files.cend() );
+ buf.append( "SCHEMA=" );
+ while (iPos != iEnd) {
+ // encoded ASCII file-urls:
+ const OString item(
+ OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) );
+ buf.append( item );
+ ++iPos;
+ if (iPos != iEnd)
+ buf.append( ' ' );
+ }
+ buf.append(LF);
+ }
+ if (! m_xcu_files.empty())
+ {
+ auto iPos( m_xcu_files.cbegin() );
+ auto const iEnd( m_xcu_files.cend() );
+ buf.append( "DATA=" );
+ while (iPos != iEnd) {
+ // encoded ASCII file-urls:
+ const OString item(
+ OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) );
+ buf.append( item );
+ ++iPos;
+ if (iPos != iEnd)
+ buf.append( ' ' );
+ }
+ buf.append(LF);
+ }
+
+ // write configmgr.ini:
+ const Reference<io::XInputStream> xData(
+ ::xmlscript::createInputStream(
+ reinterpret_cast<sal_Int8 const *>(buf.getStr()),
+ buf.getLength() ) );
+ ::ucbhelper::Content ucb_content(
+ makeURL( getCachePath(), "configmgr.ini" ), xCmdEnv, m_xComponentContext );
+ ucb_content.writeStream( xData, true /* replace existing */ );
+
+ m_configmgrini_modified = false;
+}
+
+
+void BackendImpl::addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url_,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ const OUString rcterm( isURL ? dp_misc::makeRcTerm(url_) : url_ );
+ const ::osl::MutexGuard guard( m_aMutex );
+ configmgrini_verify_init( xCmdEnv );
+ std::deque<OUString> & rSet = getFiles(isSchema);
+ if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) {
+ rSet.push_front( rcterm ); // prepend to list, thus overriding
+ // write immediately:
+ m_configmgrini_modified = true;
+ configmgrini_flush( xCmdEnv );
+ }
+}
+
+#if HAVE_FEATURE_EXTENSIONS
+bool BackendImpl::removeFromConfigmgrIni(
+ bool isSchema, OUString const & url_,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ const OUString rcterm( dp_misc::makeRcTerm(url_) );
+ const ::osl::MutexGuard guard( m_aMutex );
+ configmgrini_verify_init( xCmdEnv );
+ std::deque<OUString> & rSet = getFiles(isSchema);
+ auto i(std::find(rSet.begin(), rSet.end(), rcterm));
+ if (i == rSet.end() && !isSchema)
+ {
+ //in case the xcu contained %origin% then the configmr.ini contains the
+ //url to the file in the user installation (e.g. $BUNDLED_EXTENSIONS_USER)
+ //However, m_url (getURL()) contains the URL for the file in the actual
+ //extension installation.
+ ::std::optional<ConfigurationBackendDb::Data> data = readDataFromDb(url_);
+ if (data)
+ i = std::find(rSet.begin(), rSet.end(), data->iniEntry);
+ }
+ if (i == rSet.end()) {
+ return false;
+ }
+ rSet.erase(i);
+ // write immediately:
+ m_configmgrini_modified = true;
+ configmgrini_flush( xCmdEnv );
+ return true;
+}
+#endif
+
+// Package
+
+
+BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException(
+ "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::PackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ BackendImpl * that = getMyBackend();
+
+ bool bReg = false;
+ if (that->hasActiveEntry(getURL()))
+ bReg = true;
+
+#if HAVE_FEATURE_EXTENSIONS
+ const OUString url(getURL());
+ if (!bReg && that->m_registeredPackages)
+ {
+ // fallback for user extension registered in berkeley DB
+ bReg = that->m_registeredPackages->has(
+ OUStringToOString( url, RTL_TEXTENCODING_UTF8 ));
+ }
+#endif
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true, beans::Ambiguous<sal_Bool>( bReg, false ) );
+}
+
+
+OUString replaceOrigin(
+ OUString const & url, std::u16string_view destFolder, Reference< XCommandEnvironment > const & xCmdEnv, Reference< XComponentContext > const & xContext, bool & out_replaced)
+{
+ // looking for %origin%:
+ ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext );
+ std::vector<sal_Int8> bytes( readFile( ucb_content ) );
+ std::vector<sal_Int8> filtered( bytes.size() * 2 );
+ bool use_filtered = false;
+ OString origin;
+ char const * pBytes = reinterpret_cast<char const *>(
+ bytes.data());
+ std::size_t nBytes = bytes.size();
+ size_t write_pos = 0;
+ while (nBytes > 0)
+ {
+ sal_Int32 index = rtl_str_indexOfChar_WithLength( pBytes, nBytes, '%' );
+ if (index < 0) {
+ if (! use_filtered) // opt
+ break;
+ index = nBytes;
+ }
+
+ if ((write_pos + index) > filtered.size())
+ filtered.resize( (filtered.size() + index) * 2 );
+ memcpy( filtered.data() + write_pos, pBytes, index );
+ write_pos += index;
+ pBytes += index;
+ nBytes -= index;
+ if (nBytes == 0)
+ break;
+
+ // consume %:
+ ++pBytes;
+ --nBytes;
+ char const * pAdd = "%";
+ sal_Int32 nAdd = 1;
+ if (nBytes > 1 && pBytes[ 0 ] == '%')
+ {
+ // %% => %
+ ++pBytes;
+ --nBytes;
+ use_filtered = true;
+ }
+ else if (rtl_str_shortenedCompare_WithLength(
+ pBytes, nBytes,
+ "origin%",
+ RTL_CONSTASCII_LENGTH("origin%"),
+ RTL_CONSTASCII_LENGTH("origin%")) == 0)
+ {
+ if (origin.isEmpty()) {
+ // encode only once
+ origin = OUStringToOString(
+ comphelper::string::encodeForXml( url.subView( 0, url.lastIndexOf( '/' ) ) ),
+ // xxx todo: encode always for UTF-8? => lookup doc-header?
+ RTL_TEXTENCODING_UTF8 );
+ }
+ pAdd = origin.getStr();
+ nAdd = origin.getLength();
+ pBytes += RTL_CONSTASCII_LENGTH("origin%");
+ nBytes -= RTL_CONSTASCII_LENGTH("origin%");
+ use_filtered = true;
+ }
+ if ((write_pos + nAdd) > filtered.size())
+ filtered.resize( (filtered.size() + nAdd) * 2 );
+ memcpy( filtered.data() + write_pos, pAdd, nAdd );
+ write_pos += nAdd;
+ }
+ if (!use_filtered)
+ return url;
+ if (write_pos < filtered.size())
+ filtered.resize( write_pos );
+ OUString newUrl(url);
+ if (!destFolder.empty())
+ {
+ //get the file name of the xcu and add it to the url of the temporary folder
+ sal_Int32 i = url.lastIndexOf('/');
+ newUrl = OUString::Concat(destFolder) + url.subView(i);
+ }
+
+ ucbhelper::Content(newUrl, xCmdEnv, xContext).writeStream(
+ xmlscript::createInputStream(std::move(filtered)), true);
+ out_replaced = true;
+ return newUrl;
+}
+
+
+void BackendImpl::PackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ BackendImpl * that = getMyBackend();
+ OUString url( getURL() );
+
+ if (doRegisterPackage)
+ {
+ if (getMyBackend()->activateEntry(getURL()))
+ {
+ ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url);
+ OSL_ASSERT(data);
+ that->addToConfigmgrIni( m_isSchema, false, data->iniEntry, xCmdEnv );
+ }
+ else
+ {
+ ConfigurationBackendDb::Data data;
+ if (!m_isSchema)
+ {
+ const OUString sModFolder = that->createFolder(xCmdEnv);
+ bool out_replaced = false;
+ url = replaceOrigin(url, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced);
+ if (out_replaced)
+ data.dataUrl = sModFolder;
+ else
+ deleteTempFolder(sModFolder);
+ }
+ //No need for live-deployment for bundled extension, because OOo
+ //restarts after installation
+ if ((that->m_eContext != Context::Bundled && !startup)
+ || comphelper::LibreOfficeKit::isActive())
+ {
+ if (m_isSchema)
+ {
+ css::configuration::Update::get(
+ that->m_xComponentContext)->insertExtensionXcsFile(
+ that->m_eContext == Context::Shared, expandUnoRcUrl(url));
+ }
+ else
+ {
+ css::configuration::Update::get(
+ that->m_xComponentContext)->insertExtensionXcuFile(
+ that->m_eContext == Context::Shared, expandUnoRcUrl(url));
+ }
+ }
+ that->addToConfigmgrIni( m_isSchema, true, url, xCmdEnv );
+ data.iniEntry = dp_misc::makeRcTerm(url);
+ that->addDataToDb(getURL(), data);
+ }
+ }
+ else // revoke
+ {
+#if HAVE_FEATURE_EXTENSIONS
+ if (!that->removeFromConfigmgrIni(m_isSchema, url, xCmdEnv) &&
+ that->m_registeredPackages) {
+ // Obsolete package database handling - should be removed for LibreOffice 4.0
+ t_string2string_map entries(
+ that->m_registeredPackages->getEntries());
+ for (auto const& entry : entries)
+ {
+ //If the xcu file was installed before the configmgr was changed
+ //to use the configmgr.ini, one needed to rebuild to whole directory
+ //structure containing the xcu, xcs files from all extensions. Now,
+ //we just add all other xcu/xcs files to the configmgr.ini instead of
+ //rebuilding the directory structure.
+ OUString url2(
+ OStringToOUString(entry.first, RTL_TEXTENCODING_UTF8));
+ if (url2 != url) {
+ bool schema = entry.second.equalsIgnoreAsciiCase(
+ "vnd.sun.star.configuration-schema");
+ OUString url_replaced(url2);
+ ConfigurationBackendDb::Data data;
+ if (!schema)
+ {
+ const OUString sModFolder = that->createFolder(xCmdEnv);
+ bool out_replaced = false;
+ url_replaced = replaceOrigin(
+ url2, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced);
+ if (out_replaced)
+ data.dataUrl = sModFolder;
+ else
+ deleteTempFolder(sModFolder);
+ }
+ that->addToConfigmgrIni(schema, true, url_replaced, xCmdEnv);
+ data.iniEntry = dp_misc::makeRcTerm(url_replaced);
+ that->addDataToDb(url2, data);
+ }
+ that->m_registeredPackages->erase(entry.first);
+ }
+ try
+ {
+ ::ucbhelper::Content(
+ makeURL( that->getCachePath(), "registry" ),
+ xCmdEnv, that->getComponentContext() ).executeCommand(
+ "delete", Any( true /* delete physically */ ) );
+ }
+ catch(const Exception&)
+ {
+ OSL_ASSERT(false);
+ }
+ }
+#endif
+ ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url);
+ //If an xcu file was life deployed then always a data entry is written.
+ //If the xcu file was already in the configmr.ini then there is also
+ //a data entry
+ if (!m_isSchema && data)
+ {
+ css::configuration::Update::get(
+ that->m_xComponentContext)->removeExtensionXcuFile(expandUnoRcTerm(data->iniEntry));
+ }
+ that->revokeEntryFromDb(url);
+ }
+}
+
+} // anon namespace
+
+} // namespace dp_registry
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::configuration::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx
new file mode 100644
index 0000000000..afdc8112f2
--- /dev/null
+++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/xml/xpath/XXPathAPI.hpp>
+
+#include "dp_configurationbackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/configuration-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"conf";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"configuration-backend-db";
+constexpr OUStringLiteral KEY_ELEMENT_NAME = u"configuration";
+
+namespace dp_registry::backend::configuration {
+
+ConfigurationBackendDb::ConfigurationBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):BackendDb(xContext, url)
+{
+
+}
+
+OUString ConfigurationBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString ConfigurationBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString ConfigurationBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString ConfigurationBackendDb::getKeyElementName()
+{
+ return KEY_ELEMENT_NAME;
+}
+
+
+void ConfigurationBackendDb::addEntry(OUString const & url, Data const & data)
+{
+ try{
+ if (!activateEntry(url))
+ {
+ Reference<css::xml::dom::XNode> helpNode
+ = writeKeyElement(url);
+
+ writeSimpleElement(u"data-url", data.dataUrl, helpNode);
+ writeSimpleElement(u"ini-entry", data.iniEntry, helpNode);
+ save();
+ }
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in configuration backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+
+::std::optional<ConfigurationBackendDb::Data>
+ConfigurationBackendDb::getEntry(std::u16string_view url)
+{
+ try
+ {
+ ConfigurationBackendDb::Data retData;
+ Reference<css::xml::dom::XNode> aNode = getKeyElement(url);
+ if (aNode.is())
+ {
+ retData.dataUrl = readSimpleElement(u"data-url", aNode);
+ retData.iniEntry = readSimpleElement(u"ini-entry", aNode);
+ }
+ else
+ {
+ return ::std::optional<Data>();
+ }
+ return ::std::optional<Data>(retData);
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in configuration backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+std::vector<OUString> ConfigurationBackendDb::getAllDataUrls()
+{
+ try
+ {
+ std::vector<OUString> listRet;
+ Reference<css::xml::dom::XDocument> doc = getDocument();
+ Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+
+ Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ const OUString sPrefix = getNSPrefix();
+ OUString sExpression(
+ sPrefix + ":configuration/" + sPrefix + ":data-url/text()");
+ Reference<css::xml::dom::XNodeList> nodes =
+ xpathApi->selectNodeList(root, sExpression);
+ if (nodes.is())
+ {
+ sal_Int32 length = nodes->getLength();
+ for (sal_Int32 i = 0; i < length; i++)
+ listRet.push_back(nodes->item(i)->getNodeValue());
+ }
+ return listRet;
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in configuration backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+} // namespace dp_registry::backend::configuration
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx
new file mode 100644
index 0000000000..bd48aab7b2
--- /dev/null
+++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <rtl/string.hxx>
+#include <vector>
+#include <optional>
+#include <string_view>
+
+#include <dp_backenddb.hxx>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+namespace dp_registry::backend::configuration
+{
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ */
+class ConfigurationBackendDb : public dp_registry::backend::BackendDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+
+ virtual OUString getNSPrefix() override;
+
+ virtual OUString getRootElementName() override;
+
+ virtual OUString getKeyElementName() override;
+
+public:
+ struct Data
+ {
+ /* the URL to the folder containing the xcu or xcs files which contained
+ %origin%
+ */
+ OUString dataUrl;
+ /* the URL of the xcu or xcs file which is written in to the configmgr.ini
+ */
+ OUString iniEntry;
+ };
+
+public:
+ ConfigurationBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext,
+ OUString const& url);
+
+ void addEntry(OUString const& url, Data const& data);
+
+ ::std::optional<Data> getEntry(std::u16string_view url);
+ std::vector<OUString> getAllDataUrls();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/dp_backend.cxx b/desktop/source/deployment/registry/dp_backend.cxx
new file mode 100644
index 0000000000..016ff66286
--- /dev/null
+++ b/desktop/source/deployment/registry/dp_backend.cxx
@@ -0,0 +1,769 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+
+#include <dp_backend.h>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <rtl/ustring.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <comphelper/unwrapargs.hxx>
+#include <ucbhelper/content.hxx>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
+#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/ucb/IOErrorCode.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/tempfile.hxx>
+#include <optional>
+#include <utility>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend {
+
+
+PackageRegistryBackend::~PackageRegistryBackend()
+{
+}
+
+
+void PackageRegistryBackend::disposing( lang::EventObject const & event )
+{
+ Reference<deployment::XPackage> xPackage(
+ event.Source, UNO_QUERY_THROW );
+ OUString url( xPackage->getURL() );
+ ::osl::MutexGuard guard( m_aMutex );
+ if ( m_bound.erase( url ) != 1 )
+ {
+ SAL_WARN("desktop.deployment", "erase(" << url << ") != 1");
+ }
+}
+
+
+PackageRegistryBackend::PackageRegistryBackend(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xContext )
+ : t_BackendBase( m_aMutex ),
+ m_xComponentContext( xContext ),
+ m_eContext( Context::Unknown )
+{
+ assert(xContext.is());
+ std::optional<OUString> cachePath;
+ std::optional<bool> readOnly;
+ comphelper::unwrapArgs( args, m_context, cachePath, readOnly );
+ if (cachePath)
+ m_cachePath = *cachePath;
+
+ if ( m_context == "user" )
+ m_eContext = Context::User;
+ else if ( m_context == "shared" )
+ m_eContext = Context::Shared;
+ else if ( m_context == "bundled" )
+ m_eContext = Context::Bundled;
+ else if ( m_context == "tmp" )
+ m_eContext = Context::Tmp;
+ else if (m_context.matchIgnoreAsciiCase("vnd.sun.star.tdoc:/"))
+ m_eContext = Context::Document;
+ else
+ m_eContext = Context::Unknown;
+}
+
+
+void PackageRegistryBackend::check()
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed) {
+ throw lang::DisposedException(
+ "PackageRegistryBackend instance has already been disposed!",
+ static_cast<OWeakObject *>(this) );
+ }
+}
+
+
+void PackageRegistryBackend::disposing()
+{
+ try {
+ for (auto const& elem : m_bound)
+ elem.second->removeEventListener(this);
+ m_bound.clear();
+ m_xComponentContext.clear();
+ WeakComponentImplHelperBase::disposing();
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException(
+ "caught unexpected exception while disposing!",
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+// XPackageRegistry
+
+Reference<deployment::XPackage> PackageRegistryBackend::bindPackage(
+ OUString const & url, OUString const & mediaType, sal_Bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ ::osl::ResettableMutexGuard guard( m_aMutex );
+ check();
+
+ t_string2ref::const_iterator const iFind( m_bound.find( url ) );
+ if (iFind != m_bound.end())
+ {
+ Reference<deployment::XPackage> xPackage( iFind->second );
+ if (xPackage.is())
+ {
+ if (!mediaType.isEmpty() &&
+ mediaType != xPackage->getPackageType()->getMediaType())
+ throw lang::IllegalArgumentException
+ ("XPackageRegistry::bindPackage: media type does not match",
+ static_cast<OWeakObject*>(this), 1);
+ if (xPackage->isRemoved() != bRemoved)
+ throw deployment::InvalidRemovedParameterException(
+ "XPackageRegistry::bindPackage: bRemoved parameter does not match",
+ static_cast<OWeakObject*>(this), xPackage->isRemoved(), xPackage);
+ return xPackage;
+ }
+ }
+
+ guard.clear();
+
+ Reference<deployment::XPackage> xNewPackage;
+ try {
+ xNewPackage = bindPackage_( url, mediaType, bRemoved,
+ identifier, xCmdEnv );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException &) {
+ throw;
+ }
+ catch (const deployment::DeploymentException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw deployment::DeploymentException(
+ "Error binding package: " + url,
+ static_cast<OWeakObject *>(this), exc );
+ }
+
+ guard.reset();
+
+ std::pair< t_string2ref::iterator, bool > insertion(
+ m_bound.emplace( url, xNewPackage ) );
+ if (insertion.second)
+ { // first insertion
+ SAL_WARN_IF(
+ Reference<XInterface>(insertion.first->second) != xNewPackage,
+ "desktop.deployment", "mismatch");
+ }
+ else
+ { // found existing entry
+ Reference<deployment::XPackage> xPackage( insertion.first->second );
+ if (xPackage.is())
+ return xPackage;
+ insertion.first->second = xNewPackage;
+ }
+
+ guard.clear();
+ xNewPackage->addEventListener( this ); // listen for disposing events
+ return xNewPackage;
+}
+
+OUString PackageRegistryBackend::createFolder(
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ const OUString sDataFolder = makeURL(getCachePath(), OUString());
+ //make sure the folder exist
+ ucbhelper::Content dataContent;
+ ::dp_misc::create_folder(&dataContent, sDataFolder, xCmdEnv);
+
+ const OUString url = ::utl::CreateTempURL(&sDataFolder, true);
+ return sDataFolder + url.subView(url.lastIndexOf('/'));
+}
+
+//folderURL can have the extension .tmp or .tmp_
+//Before OOo 3.4 the created a tmp file with osl_createTempFile and
+//then created a Folder with a same name and a trailing '_'
+//If the folderURL has no '_' then there is no corresponding tmp file.
+void PackageRegistryBackend::deleteTempFolder(
+ OUString const & folderUrl)
+{
+ if (!folderUrl.isEmpty())
+ {
+ erase_path( folderUrl, Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+
+ if (folderUrl.endsWith("_"))
+ {
+ const OUString tempFile = folderUrl.copy(0, folderUrl.getLength() - 1);
+ erase_path( tempFile, Reference<XCommandEnvironment>(),
+ false /* no throw: ignore errors */ );
+ }
+ }
+}
+
+//usedFolders can contain folder names which have the extension .tmp or .tmp_
+//Before OOo 3.4 we created a tmp file with osl_createTempFile and
+//then created a Folder with a same name and a trailing '_'
+//If the folderURL has no '_' then there is no corresponding tmp file.
+void PackageRegistryBackend::deleteUnusedFolders(
+ std::vector< OUString> const & usedFolders)
+{
+ try
+ {
+ const OUString sDataFolder = makeURL(getCachePath(), OUString());
+ ::ucbhelper::Content tempFolder(
+ sDataFolder, Reference<ucb::XCommandEnvironment>(), m_xComponentContext);
+
+ Reference<sdbc::XResultSet> xResultSet(
+ StrTitle::createCursor( tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) );
+
+ // get all temp directories:
+ std::vector<OUString> tempEntries;
+
+ while (xResultSet->next())
+ {
+ OUString title(
+ Reference<sdbc::XRow>(
+ xResultSet, UNO_QUERY_THROW )->getString(
+ 1 /* Title */ ) );
+
+ if (title.endsWith(".tmp"))
+ tempEntries.push_back(
+ makeURLAppendSysPathSegment(sDataFolder, title));
+ }
+
+ for (const OUString & tempEntrie : tempEntries)
+ {
+ if (std::find( usedFolders.begin(), usedFolders.end(), tempEntrie ) ==
+ usedFolders.end())
+ {
+ deleteTempFolder(tempEntrie);
+ }
+ }
+ }
+ catch (const ucb::InteractiveAugmentedIOException& e)
+ {
+ //In case the folder containing all the data folder does not
+ //exist yet, we ignore the exception
+ if (e.Code != ucb::IOErrorCode_NOT_EXISTING)
+ throw;
+ }
+
+}
+
+
+Package::~Package()
+{
+}
+
+
+Package::Package( ::rtl::Reference<PackageRegistryBackend> myBackend,
+ OUString url,
+ OUString aName,
+ OUString displayName,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved,
+ OUString identifier)
+ : t_PackageBase( m_aMutex ),
+ m_myBackend(std::move( myBackend )),
+ m_url(std::move( url )),
+ m_name(std::move( aName )),
+ m_displayName(std::move( displayName )),
+ m_xPackageType( xPackageType ),
+ m_bRemoved(bRemoved),
+ m_identifier(std::move(identifier))
+{
+ if (m_bRemoved)
+ {
+ //We use the last segment of the URL
+ SAL_WARN_IF(
+ !m_name.isEmpty(), "desktop.deployment", "non-empty m_name");
+ OUString name = m_url;
+ rtl::Bootstrap::expandMacros(name);
+ sal_Int32 index = name.lastIndexOf('/');
+ if (index != -1 && index < name.getLength())
+ m_name = name.copy(index + 1);
+ }
+}
+
+
+void Package::disposing()
+{
+ m_myBackend.clear();
+ WeakComponentImplHelperBase::disposing();
+}
+
+
+void Package::check() const
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed) {
+ throw lang::DisposedException(
+ "Package instance has already been disposed!",
+ static_cast<OWeakObject *>(const_cast<Package *>(this)));
+ }
+}
+
+// XComponent
+
+void Package::dispose()
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::dispose();
+}
+
+
+void Package::addEventListener(
+ Reference<lang::XEventListener> const & xListener )
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::addEventListener( xListener );
+}
+
+
+void Package::removeEventListener(
+ Reference<lang::XEventListener> const & xListener )
+{
+ //Do not call check here. We must not throw an exception here if the object
+ //is being disposed or is already disposed. See com.sun.star.lang.XComponent
+ WeakComponentImplHelperBase::removeEventListener( xListener );
+}
+
+// XModifyBroadcaster
+
+void Package::addModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+
+void Package::removeModifyListener(
+ Reference<util::XModifyListener> const & xListener )
+{
+ check();
+ rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener );
+}
+
+
+void Package::checkAborted(
+ ::rtl::Reference<AbortChannel> const & abortChannel )
+{
+ if (abortChannel.is() && abortChannel->isAborted()) {
+ throw CommandAbortedException(
+ "abort!", static_cast<OWeakObject *>(this) );
+ }
+}
+
+// XPackage
+
+Reference<task::XAbortChannel> Package::createAbortChannel()
+{
+ check();
+ return new AbortChannel;
+}
+
+
+sal_Bool Package::isBundle()
+{
+ return false; // default
+}
+
+
+::sal_Int32 Package::checkPrerequisites(
+ const css::uno::Reference< css::task::XAbortChannel >&,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >&,
+ sal_Bool)
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return 0;
+}
+
+
+sal_Bool Package::checkDependencies(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return true;
+}
+
+
+Sequence< Reference<deployment::XPackage> > Package::getBundle(
+ Reference<task::XAbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ return Sequence< Reference<deployment::XPackage> >();
+}
+
+
+OUString Package::getName()
+{
+ return m_name;
+}
+
+beans::Optional<OUString> Package::getIdentifier()
+{
+ if (m_bRemoved)
+ return beans::Optional<OUString>(true, m_identifier);
+
+ return beans::Optional<OUString>();
+}
+
+
+OUString Package::getVersion()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return OUString();
+}
+
+
+OUString Package::getURL()
+{
+ return m_url;
+}
+
+
+OUString Package::getDisplayName()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return m_displayName;
+}
+
+
+OUString Package::getDescription()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return OUString();
+}
+
+
+OUString Package::getLicenseText()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return OUString();
+}
+
+
+Sequence<OUString> Package::getUpdateInformationURLs()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return Sequence<OUString>();
+}
+
+
+css::beans::StringPair Package::getPublisherInfo()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ css::beans::StringPair aEmptyPair;
+ return aEmptyPair;
+}
+
+
+uno::Reference< css::graphic::XGraphic > Package::getIcon( sal_Bool /*bHighContrast*/ )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ uno::Reference< css::graphic::XGraphic > aEmpty;
+ return aEmpty;
+}
+
+
+Reference<deployment::XPackageTypeInfo> Package::getPackageType()
+{
+ return m_xPackageType;
+}
+
+void Package::exportTo(
+ OUString const & destFolderURL, OUString const & newTitle,
+ sal_Int32 nameClashAction, Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ ::ucbhelper::Content destFolder( destFolderURL, xCmdEnv, getMyBackend()->getComponentContext() );
+ ::ucbhelper::Content sourceContent( getURL(), xCmdEnv, getMyBackend()->getComponentContext() );
+ bool bOk=true;
+ try
+ {
+ destFolder.transferContent(
+ sourceContent, ::ucbhelper::InsertOperation::Copy,
+ newTitle, nameClashAction);
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ bOk = false;
+ }
+
+ if (!bOk)
+ throw RuntimeException( "UCB transferContent() failed!", nullptr );
+}
+
+void Package::fireModified()
+{
+ ::cppu::OInterfaceContainerHelper * container = rBHelper.getContainer(
+ cppu::UnoType<util::XModifyListener>::get() );
+ if (container == nullptr)
+ return;
+
+ const Sequence< Reference<XInterface> > elements(
+ container->getElements() );
+ lang::EventObject evt( static_cast<OWeakObject *>(this) );
+ for ( const Reference<XInterface>& x : elements )
+ {
+ Reference<util::XModifyListener> xListener( x, UNO_QUERY );
+ if (xListener.is())
+ xListener->modified( evt );
+ }
+}
+
+// XPackage
+
+beans::Optional< beans::Ambiguous<sal_Bool> > Package::isRegistered(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ try {
+ ::osl::ResettableMutexGuard guard( m_aMutex );
+ return isRegistered_( guard,
+ AbortChannel::get(xAbortChannel),
+ xCmdEnv );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const CommandFailedException &) {
+ throw;
+ }
+ catch (const CommandAbortedException &) {
+ throw;
+ }
+ catch (const deployment::DeploymentException &) {
+ throw;
+ }
+ catch (const Exception & e) {
+ Any exc( ::cppu::getCaughtException() );
+ throw deployment::DeploymentException(
+ "unexpected " + exc.getValueTypeName() + ": " + e.Message,
+ static_cast<OWeakObject *>(this), exc );
+ }
+}
+
+
+void Package::processPackage_impl(
+ bool doRegisterPackage,
+ bool startup,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ check();
+ bool action = false;
+
+ try {
+ try {
+ ::osl::ResettableMutexGuard guard( m_aMutex );
+ beans::Optional< beans::Ambiguous<sal_Bool> > option(
+ isRegistered_( guard, AbortChannel::get(xAbortChannel),
+ xCmdEnv ) );
+ action = (option.IsPresent &&
+ (option.Value.IsAmbiguous ||
+ (doRegisterPackage ? !option.Value.Value
+ : option.Value.Value)));
+ if (action) {
+
+ OUString displayName = isRemoved() ? getName() : getDisplayName();
+ ProgressLevel progress(
+ xCmdEnv,
+ (doRegisterPackage
+ ? PackageRegistryBackend::StrRegisteringPackage()
+ : PackageRegistryBackend::StrRevokingPackage())
+ + displayName );
+ processPackage_( guard,
+ doRegisterPackage,
+ startup,
+ AbortChannel::get(xAbortChannel),
+ xCmdEnv );
+ }
+ }
+ catch (lang::IllegalArgumentException &) {
+ Any e(cppu::getCaughtException());
+ throw deployment::DeploymentException(
+ ((doRegisterPackage
+ ? DpResId(RID_STR_ERROR_WHILE_REGISTERING)
+ : DpResId(RID_STR_ERROR_WHILE_REVOKING))
+ + getDisplayName()),
+ static_cast< OWeakObject * >(this), e);
+ }
+ catch (const RuntimeException &) {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "unexpected");
+ throw;
+ }
+ catch (const CommandFailedException &) {
+ throw;
+ }
+ catch (const CommandAbortedException &) {
+ throw;
+ }
+ catch (const deployment::DeploymentException &) {
+ throw;
+ }
+ catch (const Exception & e) {
+ Any exc( ::cppu::getCaughtException() );
+ throw deployment::DeploymentException(
+ (doRegisterPackage
+ ? DpResId(RID_STR_ERROR_WHILE_REGISTERING)
+ : DpResId(RID_STR_ERROR_WHILE_REVOKING))
+ + getDisplayName() + ": " + exc.getValueType().getTypeName() + " \"" + e.Message
+ + "\"",
+ static_cast<OWeakObject *>(this), exc );
+ }
+ }
+ catch (...) {
+ if (action)
+ fireModified();
+ throw;
+ }
+ if (action)
+ fireModified();
+}
+
+
+void Package::registerPackage(
+ sal_Bool startup,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ processPackage_impl( true /* register */, startup, xAbortChannel, xCmdEnv );
+}
+
+
+void Package::revokePackage(
+ sal_Bool startup,
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ processPackage_impl( false /* revoke */, startup, xAbortChannel, xCmdEnv );
+
+}
+
+PackageRegistryBackend * Package::getMyBackend() const
+{
+ PackageRegistryBackend * pBackend = m_myBackend.get();
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException(
+ "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<Package *>(this)));
+ }
+ return pBackend;
+}
+
+OUString Package::getRepositoryName()
+{
+ PackageRegistryBackend * backEnd = getMyBackend();
+ return backEnd->getContext();
+}
+
+beans::Optional< OUString > Package::getRegistrationDataURL()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return beans::Optional<OUString>();
+}
+
+sal_Bool Package::isRemoved()
+{
+ return m_bRemoved;
+}
+
+Package::TypeInfo::~TypeInfo()
+{
+}
+
+// XPackageTypeInfo
+
+OUString Package::TypeInfo::getMediaType()
+{
+ return m_mediaType;
+}
+
+
+OUString Package::TypeInfo::getDescription()
+{
+ return getShortDescription();
+}
+
+
+OUString Package::TypeInfo::getShortDescription()
+{
+ return m_shortDescr;
+}
+
+OUString Package::TypeInfo::getFileFilter()
+{
+ return m_fileFilter;
+}
+
+Any Package::TypeInfo::getIcon( sal_Bool /*highContrast*/, sal_Bool /*smallIcon*/ )
+{
+ return Any();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/dp_backenddb.cxx b/desktop/source/deployment/registry/dp_backenddb.cxx
new file mode 100644
index 0000000000..28effd95c8
--- /dev/null
+++ b/desktop/source/deployment/registry/dp_backenddb.cxx
@@ -0,0 +1,655 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <osl/file.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/xpath/XPathAPI.hpp>
+#include <com/sun/star/io/XActiveDataSource.hpp>
+#include <com/sun/star/io/XActiveDataControl.hpp>
+#include <dp_misc.h>
+#include <ucbhelper/content.hxx>
+#include <xmlscript/xml_helper.hxx>
+#include <dp_backenddb.hxx>
+
+
+using namespace ::com::sun::star::uno;
+
+
+namespace dp_registry::backend {
+
+BackendDb::BackendDb(
+ Reference<css::uno::XComponentContext> const & xContext,
+ OUString const & url):
+ m_xContext(xContext)
+{
+ m_urlDb = dp_misc::expandUnoRcUrl(url);
+}
+
+void BackendDb::save()
+{
+ const Reference<css::io::XActiveDataSource> xDataSource(m_doc,css::uno::UNO_QUERY_THROW);
+ std::vector<sal_Int8> bytes;
+ xDataSource->setOutputStream(::xmlscript::createOutputStream(&bytes));
+ const Reference<css::io::XActiveDataControl> xDataControl(m_doc,css::uno::UNO_QUERY_THROW);
+ xDataControl->start();
+
+ const Reference<css::io::XInputStream> xData(
+ ::xmlscript::createInputStream(std::move(bytes)));
+ ::ucbhelper::Content ucbDb(m_urlDb, nullptr, m_xContext);
+ ucbDb.writeStream(xData, true /*replace existing*/);
+}
+
+css::uno::Reference<css::xml::dom::XDocument> const & BackendDb::getDocument()
+{
+ if (!m_doc.is())
+ {
+ const Reference<css::xml::dom::XDocumentBuilder> xDocBuilder(
+ css::xml::dom::DocumentBuilder::create(m_xContext) );
+
+ ::osl::DirectoryItem item;
+ ::osl::File::RC err = ::osl::DirectoryItem::get(m_urlDb, item);
+ if (err == ::osl::File::E_None)
+ {
+ ::ucbhelper::Content descContent(
+ m_urlDb, css::uno::Reference<css::ucb::XCommandEnvironment>(),
+ m_xContext);
+ Reference<css::io::XInputStream> xIn = descContent.openStream();
+ m_doc = xDocBuilder->parse(xIn);
+ }
+ else if (err == ::osl::File::E_NOENT)
+ {
+ //Create a new document and insert some basic stuff
+ m_doc = xDocBuilder->newDocument();
+ const Reference<css::xml::dom::XElement> rootNode =
+ m_doc->createElementNS(getDbNSName(), getNSPrefix() +
+ ":" + getRootElementName());
+
+ m_doc->appendChild(Reference<css::xml::dom::XNode>(
+ rootNode, UNO_QUERY_THROW));
+ save();
+ }
+ else
+ throw css::uno::RuntimeException(
+ "Extension manager could not access database file:"
+ + m_urlDb, nullptr);
+
+ if (!m_doc.is())
+ throw css::uno::RuntimeException(
+ "Extension manager could not get root node of data base file: "
+ + m_urlDb, nullptr);
+ }
+
+ return m_doc;
+}
+
+Reference<css::xml::xpath::XXPathAPI> const & BackendDb::getXPathAPI()
+{
+ if (!m_xpathApi.is())
+ {
+ m_xpathApi = css::xml::xpath::XPathAPI::create( m_xContext );
+
+ m_xpathApi->registerNS( getNSPrefix(), getDbNSName() );
+ }
+
+ return m_xpathApi;
+}
+
+void BackendDb::removeElement(OUString const & sXPathExpression)
+{
+ try
+ {
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+ const Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+ const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ //find the extension element that is to be removed
+ const Reference<css::xml::dom::XNode> aNode =
+ xpathApi->selectSingleNode(root, sXPathExpression);
+
+ if (aNode.is())
+ {
+ root->removeChild(aNode);
+ save();
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ //There must not be any other entry with the same url
+ const Reference<css::xml::dom::XNode> nextNode =
+ xpathApi->selectSingleNode(root, sXPathExpression);
+ OSL_ASSERT(! nextNode.is());
+#endif
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+void BackendDb::removeEntry(std::u16string_view url)
+{
+ const OUString sKeyElement = getKeyElementName();
+ const OUString sPrefix = getNSPrefix();
+ OUString sExpression =
+ sPrefix +
+ ":" +
+ sKeyElement +
+ "[@url = \"" +
+ url +
+ "\"]";
+
+ removeElement(sExpression);
+}
+
+void BackendDb::revokeEntry(std::u16string_view url)
+{
+ try
+ {
+ Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY);
+ if (entry.is())
+ {
+ entry->setAttribute("revoked", "true");
+ save();
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to revoke data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+bool BackendDb::activateEntry(std::u16string_view url)
+{
+ try
+ {
+ bool ret = false;
+ Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY);
+ if (entry.is())
+ {
+ //no attribute "active" means it is active, that is, registered.
+ entry->removeAttribute("revoked");
+ save();
+ ret = true;
+ }
+ return ret;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to revoke data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+bool BackendDb::hasActiveEntry(std::u16string_view url)
+{
+ try
+ {
+ bool ret = false;
+ Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY);
+ if (entry.is())
+ {
+ OUString sActive = entry->getAttribute("revoked");
+ if (!(sActive == "true"))
+ ret = true;
+ }
+ return ret;
+
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to determine an active entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+Reference<css::xml::dom::XNode> BackendDb::getKeyElement(
+ std::u16string_view url)
+{
+ try
+ {
+ const OUString sPrefix = getNSPrefix();
+ const OUString sKeyElement = getKeyElementName();
+ OUString sExpression =
+ sPrefix +
+ ":" +
+ sKeyElement +
+ "[@url = \"" +
+ url +
+ "\"]";
+
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+ const Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+ const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ return xpathApi->selectSingleNode(root, sExpression);
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read key element in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+//Only writes the data if there is at least one entry
+void BackendDb::writeVectorOfPair(
+ std::vector< std::pair< OUString, OUString > > const & vecPairs,
+ std::u16string_view sVectorTagName,
+ std::u16string_view sPairTagName,
+ std::u16string_view sFirstTagName,
+ std::u16string_view sSecondTagName,
+ css::uno::Reference<css::xml::dom::XNode> const & xParent)
+{
+ try{
+ if (vecPairs.empty())
+ return;
+ const OUString sNameSpace = getDbNSName();
+ OSL_ASSERT(!sNameSpace.isEmpty());
+ const OUString sPrefix(getNSPrefix() + ":");
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+
+ const Reference<css::xml::dom::XElement> vectorNode(
+ doc->createElementNS(sNameSpace, sPrefix + sVectorTagName));
+
+ xParent->appendChild(
+ Reference<css::xml::dom::XNode>(
+ vectorNode, css::uno::UNO_QUERY_THROW));
+ for (auto const& vecPair : vecPairs)
+ {
+ const Reference<css::xml::dom::XElement> pairNode(
+ doc->createElementNS(sNameSpace, sPrefix + sPairTagName));
+
+ vectorNode->appendChild(
+ Reference<css::xml::dom::XNode>(
+ pairNode, css::uno::UNO_QUERY_THROW));
+
+ const Reference<css::xml::dom::XElement> firstNode(
+ doc->createElementNS(sNameSpace, sPrefix + sFirstTagName));
+
+ pairNode->appendChild(
+ Reference<css::xml::dom::XNode>(
+ firstNode, css::uno::UNO_QUERY_THROW));
+
+ const Reference<css::xml::dom::XText> firstTextNode(
+ doc->createTextNode( vecPair.first));
+
+ firstNode->appendChild(
+ Reference<css::xml::dom::XNode>(
+ firstTextNode, css::uno::UNO_QUERY_THROW));
+
+ const Reference<css::xml::dom::XElement> secondNode(
+ doc->createElementNS(sNameSpace, sPrefix + sSecondTagName));
+
+ pairNode->appendChild(
+ Reference<css::xml::dom::XNode>(
+ secondNode, css::uno::UNO_QUERY_THROW));
+
+ const Reference<css::xml::dom::XText> secondTextNode(
+ doc->createTextNode( vecPair.second));
+
+ secondNode->appendChild(
+ Reference<css::xml::dom::XNode>(
+ secondTextNode, css::uno::UNO_QUERY_THROW));
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+std::vector< std::pair< OUString, OUString > >
+BackendDb::readVectorOfPair(
+ Reference<css::xml::dom::XNode> const & parent,
+ std::u16string_view sListTagName,
+ std::u16string_view sPairTagName,
+ std::u16string_view sFirstTagName,
+ std::u16string_view sSecondTagName)
+{
+ try
+ {
+ OSL_ASSERT(parent.is());
+ const OUString sPrefix(getNSPrefix() + ":");
+ const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ const OUString sExprPairs(
+ sPrefix + sListTagName + "/" + sPrefix + sPairTagName);
+ const Reference<css::xml::dom::XNodeList> listPairs =
+ xpathApi->selectNodeList(parent, sExprPairs);
+
+ std::vector< std::pair< OUString, OUString > > retVector;
+ sal_Int32 length = listPairs->getLength();
+ for (sal_Int32 i = 0; i < length; i++)
+ {
+ const Reference<css::xml::dom::XNode> aPair = listPairs->item(i);
+ const OUString sExprFirst(sPrefix + sFirstTagName + "/text()");
+ const Reference<css::xml::dom::XNode> first =
+ xpathApi->selectSingleNode(aPair, sExprFirst);
+
+ const OUString sExprSecond(sPrefix + sSecondTagName + "/text()");
+ const Reference<css::xml::dom::XNode> second =
+ xpathApi->selectSingleNode(aPair, sExprSecond);
+ OSL_ASSERT(first.is() && second.is());
+
+ retVector.emplace_back(
+ first->getNodeValue(), second->getNodeValue());
+ }
+ return retVector;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+//Only writes the data if there is at least one entry
+void BackendDb::writeSimpleList(
+ std::deque< OUString> const & list,
+ std::u16string_view sListTagName,
+ std::u16string_view sMemberTagName,
+ Reference<css::xml::dom::XNode> const & xParent)
+{
+ try
+ {
+ if (list.empty())
+ return;
+ const OUString sNameSpace = getDbNSName();
+ const OUString sPrefix(getNSPrefix() + ":");
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+
+ const Reference<css::xml::dom::XElement> listNode(
+ doc->createElementNS(sNameSpace, sPrefix + sListTagName));
+
+ xParent->appendChild(
+ Reference<css::xml::dom::XNode>(
+ listNode, css::uno::UNO_QUERY_THROW));
+
+ for (auto const& elem : list)
+ {
+ const Reference<css::xml::dom::XNode> memberNode(
+ doc->createElementNS(sNameSpace, sPrefix + sMemberTagName), css::uno::UNO_QUERY_THROW);
+
+ listNode->appendChild(memberNode);
+
+ const Reference<css::xml::dom::XNode> textNode(
+ doc->createTextNode(elem), css::uno::UNO_QUERY_THROW);
+
+ memberNode->appendChild(textNode);
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+//Writes only the element if is has a value.
+//The prefix is automatically added to the element name
+void BackendDb::writeSimpleElement(
+ std::u16string_view sElementName, OUString const & value,
+ Reference<css::xml::dom::XNode> const & xParent)
+{
+ try
+ {
+ if (value.isEmpty())
+ return;
+ const OUString sPrefix = getNSPrefix();
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+ const OUString sNameSpace = getDbNSName();
+ const Reference<css::xml::dom::XNode> dataNode(
+ doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName),
+ UNO_QUERY_THROW);
+ xParent->appendChild(dataNode);
+
+ const Reference<css::xml::dom::XNode> dataValue(
+ doc->createTextNode(value), UNO_QUERY_THROW);
+ dataNode->appendChild(dataValue);
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry(writeSimpleElement) in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+
+}
+
+/// The key elements have a url attribute and are always children of the root element.
+Reference<css::xml::dom::XNode> BackendDb::writeKeyElement(
+ OUString const & url)
+{
+ try
+ {
+ const OUString sNameSpace = getDbNSName();
+ const OUString sPrefix = getNSPrefix();
+ const OUString sElementName = getKeyElementName();
+ const Reference<css::xml::dom::XDocument> doc = getDocument();
+ const Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+
+ //Check if there are an entry with the same url. This can be the case if the
+ //status of an XPackage is ambiguous. In this case a call to activateExtension
+ //(dp_extensionmanager.cxx), will register the package again. See also
+ //Package::processPackage_impl in dp_backend.cxx.
+ //A package can become
+ //invalid after its successful registration, for example if a second extension with
+ //the same service is installed.
+ const OUString sExpression(
+ sPrefix + ":" + sElementName + "[@url = \"" + url + "\"]");
+ const Reference<css::xml::dom::XNode> existingNode =
+ getXPathAPI()->selectSingleNode(root, sExpression);
+ if (existingNode.is())
+ {
+ OSL_ASSERT(false);
+ //replace the existing entry.
+ removeEntry(url);
+ }
+
+ const Reference<css::xml::dom::XElement> keyElement(
+ doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName));
+
+ keyElement->setAttribute("url", url);
+
+ const Reference<css::xml::dom::XNode> keyNode(
+ keyElement, UNO_QUERY_THROW);
+ root->appendChild(keyNode);
+ return keyNode;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write key element in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+OUString BackendDb::readSimpleElement(
+ std::u16string_view sElementName, Reference<css::xml::dom::XNode> const & xParent)
+{
+ try
+ {
+ const OUString sPrefix = getNSPrefix();
+ const OUString sExpr(sPrefix + ":" + sElementName + "/text()");
+ const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ const Reference<css::xml::dom::XNode> val =
+ xpathApi->selectSingleNode(xParent, sExpr);
+ if (val.is())
+ return val->getNodeValue();
+ return OUString();
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data (readSimpleElement) in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+
+std::deque< OUString> BackendDb::readList(
+ Reference<css::xml::dom::XNode> const & parent,
+ std::u16string_view sListTagName,
+ std::u16string_view sMemberTagName)
+{
+ try
+ {
+ OSL_ASSERT(parent.is());
+ const OUString sPrefix(getNSPrefix() + ":");
+ const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ const OUString sExprList(
+ sPrefix + sListTagName + "/" + sPrefix + sMemberTagName + "/text()");
+ const Reference<css::xml::dom::XNodeList> list =
+ xpathApi->selectNodeList(parent, sExprList);
+
+ std::deque<OUString > retList;
+ sal_Int32 length = list->getLength();
+ for (sal_Int32 i = 0; i < length; i++)
+ {
+ const Reference<css::xml::dom::XNode> member = list->item(i);
+ retList.push_back(member->getNodeValue());
+ }
+ return retList;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+std::vector<OUString> BackendDb::getOneChildFromAllEntries(
+ std::u16string_view name)
+{
+ try
+ {
+ std::vector<OUString> listRet;
+ Reference<css::xml::dom::XDocument> doc = getDocument();
+ Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+
+ Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI();
+ const OUString sPrefix = getNSPrefix();
+ const OUString sKeyElement = getKeyElementName();
+ OUString sNodeSelectExpr =
+ sPrefix +
+ ":" +
+ sKeyElement +
+ "/" +
+ sPrefix +
+ ":" +
+ name +
+ "/text()";
+
+ Reference<css::xml::dom::XNodeList> nodes =
+ xpathApi->selectNodeList(root, sNodeSelectExpr);
+ if (nodes.is())
+ {
+ sal_Int32 length = nodes->getLength();
+ for (sal_Int32 i = 0; i < length; i++)
+ listRet.push_back(nodes->item(i)->getNodeValue());
+ }
+ return listRet;
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+
+RegisteredDb::RegisteredDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):BackendDb(xContext, url)
+{
+}
+
+void RegisteredDb::addEntry(OUString const & url)
+{
+ try{
+ if (!activateEntry(url))
+ {
+ const OUString sNameSpace = getDbNSName();
+ const OUString sPrefix = getNSPrefix();
+ const OUString sEntry = getKeyElementName();
+
+ Reference<css::xml::dom::XDocument> doc = getDocument();
+ Reference<css::xml::dom::XNode> root = doc->getFirstChild();
+
+#if OSL_DEBUG_LEVEL > 0
+ //There must not be yet an entry with the same url
+ OUString sExpression(
+ sPrefix + ":" + sEntry + "[@url = \"" + url + "\"]");
+ Reference<css::xml::dom::XNode> _extensionNode =
+ getXPathAPI()->selectSingleNode(root, sExpression);
+ OSL_ASSERT(! _extensionNode.is());
+#endif
+ Reference<css::xml::dom::XElement> helpElement(
+ doc->createElementNS(sNameSpace, sPrefix + ":" + sEntry));
+
+ helpElement->setAttribute("url", url);
+
+ Reference<css::xml::dom::XNode> helpNode(
+ helpElement, UNO_QUERY_THROW);
+ root->appendChild(helpNode);
+
+ save();
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+} // namespace dp_registry
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/dp_registry.cxx b/desktop/source/deployment/registry/dp_registry.cxx
new file mode 100644
index 0000000000..ac19bcd8e9
--- /dev/null
+++ b/desktop/source/deployment/registry/dp_registry.cxx
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <dp_shared.hxx>
+#include <dp_package.hxx>
+#include <strings.hrc>
+#include <dp_registry.hxx>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/uri.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <comphelper/sequence.hxx>
+#include <ucbhelper/content.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/uno/DeploymentException.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XSingleComponentFactory.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/util/XUpdatable.hpp>
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/deployment/XPackageTypeInfo.hpp>
+#include <com/sun/star/deployment/XPackageRegistry.hpp>
+#include <set>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+
+namespace dp_registry {
+
+namespace {
+
+typedef ::cppu::WeakComponentImplHelper<
+ deployment::XPackageRegistry, util::XUpdatable > t_helper;
+
+
+class PackageRegistryImpl : private cppu::BaseMutex, public t_helper
+{
+ struct ci_string_hash {
+ std::size_t operator () ( OUString const & str ) const {
+ return str.toAsciiLowerCase().hashCode();
+ }
+ };
+ struct ci_string_equals {
+ bool operator () ( std::u16string_view str1, std::u16string_view str2 ) const{
+ return o3tl::equalsIgnoreAsciiCase( str1, str2 );
+ }
+ };
+ typedef std::unordered_map<
+ OUString, Reference<deployment::XPackageRegistry>,
+ ci_string_hash, ci_string_equals > t_string2registry;
+ typedef std::unordered_map<
+ OUString, OUString,
+ ci_string_hash, ci_string_equals > t_string2string;
+ typedef std::set<
+ Reference<deployment::XPackageRegistry> > t_registryset;
+
+ t_string2registry m_mediaType2backend;
+ t_string2string m_filter2mediaType;
+ t_registryset m_ambiguousBackends;
+ t_registryset m_allBackends;
+ std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos;
+
+ void insertBackend(
+ Reference<deployment::XPackageRegistry> const & xBackend );
+
+protected:
+ void check();
+ virtual void SAL_CALL disposing() override;
+
+ virtual ~PackageRegistryImpl() override;
+ PackageRegistryImpl() : t_helper( m_aMutex ) {}
+
+
+public:
+ static Reference<deployment::XPackageRegistry> create(
+ OUString const & context,
+ OUString const & cachePath,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XUpdatable
+ virtual void SAL_CALL update() override;
+
+ // XPackageRegistry
+ virtual Reference<deployment::XPackage> SAL_CALL bindPackage(
+ OUString const & url, OUString const & mediaType, sal_Bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+};
+
+
+void PackageRegistryImpl::check()
+{
+ ::osl::MutexGuard guard( m_aMutex );
+ if (rBHelper.bInDispose || rBHelper.bDisposed) {
+ throw lang::DisposedException(
+ "PackageRegistry instance has already been disposed!",
+ static_cast<OWeakObject *>(this) );
+ }
+}
+
+
+void PackageRegistryImpl::disposing()
+{
+ // dispose all backends:
+ for (auto const& backend : m_allBackends)
+ {
+ try_dispose(backend);
+ }
+ m_mediaType2backend = t_string2registry();
+ m_ambiguousBackends = t_registryset();
+ m_allBackends = t_registryset();
+
+ t_helper::disposing();
+}
+
+
+PackageRegistryImpl::~PackageRegistryImpl()
+{
+}
+
+
+OUString normalizeMediaType( std::u16string_view mediaType )
+{
+ OUStringBuffer buf;
+ sal_Int32 index = 0;
+ for (;;) {
+ buf.append( o3tl::trim(o3tl::getToken(mediaType, 0, '/', index )) );
+ if (index < 0)
+ break;
+ buf.append( '/' );
+ }
+ return buf.makeStringAndClear();
+}
+
+
+void PackageRegistryImpl::packageRemoved(
+ OUString const & url, OUString const & mediaType)
+{
+ const t_string2registry::const_iterator i =
+ m_mediaType2backend.find(mediaType);
+
+ if (i != m_mediaType2backend.end())
+ {
+ i->second->packageRemoved(url, mediaType);
+ }
+}
+
+void PackageRegistryImpl::insertBackend(
+ Reference<deployment::XPackageRegistry> const & xBackend )
+{
+ m_allBackends.insert( xBackend );
+ std::unordered_set<OUString> ambiguousFilters;
+
+ const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes(
+ xBackend->getSupportedPackageTypes() );
+ for ( Reference<deployment::XPackageTypeInfo> const & xPackageType : packageTypes )
+ {
+ m_typesInfos.push_back( xPackageType );
+
+ const OUString mediaType( normalizeMediaType(
+ xPackageType->getMediaType() ) );
+ std::pair<t_string2registry::iterator, bool> a_insertion(
+ m_mediaType2backend.emplace( mediaType, xBackend ) );
+ if (a_insertion.second)
+ {
+ // add parameterless media-type, too:
+ sal_Int32 semi = mediaType.indexOf( ';' );
+ if (semi >= 0) {
+ m_mediaType2backend.emplace( mediaType.copy( 0, semi ), xBackend );
+ }
+ const OUString fileFilter( xPackageType->getFileFilter() );
+ //The package backend shall also be called to determine the mediatype
+ //(XPackageRegistry.bindPackage) when the URL points to a directory.
+ const bool bExtension = (mediaType == "application/vnd.sun.star.package-bundle");
+ if (fileFilter.isEmpty() || fileFilter == "*.*" || fileFilter == "*" || bExtension)
+ {
+ m_ambiguousBackends.insert( xBackend );
+ }
+ else
+ {
+ sal_Int32 nIndex = 0;
+ do {
+ OUString token( fileFilter.getToken( 0, ';', nIndex ) );
+ if (token.match( "*." ))
+ token = token.copy( 1 );
+ if (token.isEmpty())
+ continue;
+ // mark any further wildcards ambig:
+ bool ambig = (token.indexOf('*') >= 0 ||
+ token.indexOf('?') >= 0);
+ if (! ambig) {
+ std::pair<t_string2string::iterator, bool> ins(
+ m_filter2mediaType.emplace(
+ token, mediaType ) );
+ ambig = !ins.second;
+ if (ambig) {
+ // filter has already been in: add previously
+ // added backend to ambig set
+ const t_string2registry::const_iterator iFind(
+ m_mediaType2backend.find(
+ /* media-type of pr. added backend */
+ ins.first->second ) );
+ OSL_ASSERT(
+ iFind != m_mediaType2backend.end() );
+ if (iFind != m_mediaType2backend.end())
+ m_ambiguousBackends.insert( iFind->second );
+ }
+ }
+ if (ambig) {
+ m_ambiguousBackends.insert( xBackend );
+ // mark filter to be removed later from filters map:
+ ambiguousFilters.insert( token );
+ }
+ }
+ while (nIndex >= 0);
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ SAL_WARN( "desktop", "more than one PackageRegistryBackend for media-type=\""
+ << mediaType
+ << "\" => "
+ << Reference<lang::XServiceInfo>(
+ xBackend, UNO_QUERY_THROW )->getImplementationName()
+ << "\"!" );
+ }
+#endif
+ }
+
+ // cut out ambiguous filters:
+ for (auto const& ambiguousFilter : ambiguousFilters)
+ {
+ m_filter2mediaType.erase(ambiguousFilter);
+ }
+}
+
+
+Reference<deployment::XPackageRegistry> PackageRegistryImpl::create(
+ OUString const & context,
+ OUString const & cachePath,
+ Reference<XComponentContext> const & xComponentContext )
+{
+ rtl::Reference<PackageRegistryImpl> that = new PackageRegistryImpl;
+
+ // auto-detect all registered package registries:
+ Reference<container::XEnumeration> xEnum(
+ Reference<container::XContentEnumerationAccess>(
+ xComponentContext->getServiceManager(),
+ UNO_QUERY_THROW )->createContentEnumeration(
+ "com.sun.star.deployment.PackageRegistryBackend" ) );
+ if (xEnum.is())
+ {
+ while (xEnum->hasMoreElements())
+ {
+ Any element( xEnum->nextElement() );
+ Sequence<Any> registryArgs(cachePath.isEmpty() ? 1 : 3 );
+ auto pregistryArgs = registryArgs.getArray();
+ pregistryArgs[ 0 ] <<= context;
+ if (!cachePath.isEmpty())
+ {
+ Reference<lang::XServiceInfo> xServiceInfo(
+ element, UNO_QUERY_THROW );
+ OUString registryCachePath(
+ makeURL( cachePath,
+ ::rtl::Uri::encode(
+ xServiceInfo->getImplementationName(),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) ) );
+ pregistryArgs[ 1 ] <<= registryCachePath;
+ pregistryArgs[ 2 ] <<= false; // readOnly;
+ create_folder( nullptr, registryCachePath,
+ Reference<XCommandEnvironment>() );
+ }
+
+ Reference<deployment::XPackageRegistry> xBackend;
+ Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY );
+ if (xFac.is()) {
+ xBackend.set(
+ xFac->createInstanceWithArgumentsAndContext(
+ registryArgs, xComponentContext ), UNO_QUERY );
+ }
+ else {
+ Reference<lang::XSingleServiceFactory> xSingleServiceFac(
+ element, UNO_QUERY_THROW );
+ xBackend.set(
+ xSingleServiceFac->createInstanceWithArguments(
+ registryArgs ), UNO_QUERY );
+ }
+ if (! xBackend.is()) {
+ throw DeploymentException(
+ "cannot instantiate PackageRegistryBackend service: "
+ + Reference<lang::XServiceInfo>(
+ element, UNO_QUERY_THROW )->getImplementationName(),
+ static_cast<OWeakObject *>(that.get()) );
+ }
+
+ that->insertBackend( xBackend );
+ }
+ }
+
+ // Insert bundle back-end.
+ // Always register as last, because we want to add extensions also as folders
+ // and as a default we accept every folder, which was not recognized by the other
+ // backends.
+ Reference<deployment::XPackageRegistry> extensionBackend =
+ ::dp_registry::backend::bundle::create(
+ that, context, cachePath, xComponentContext);
+ that->insertBackend(extensionBackend);
+
+ Reference<lang::XServiceInfo> xServiceInfo(
+ extensionBackend, UNO_QUERY_THROW );
+
+ OSL_ASSERT(xServiceInfo.is());
+ OUString registryCachePath(
+ makeURL( cachePath,
+ ::rtl::Uri::encode(
+ xServiceInfo->getImplementationName(),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) ) );
+ create_folder( nullptr, registryCachePath, Reference<XCommandEnvironment>());
+
+
+#if OSL_DEBUG_LEVEL > 0
+ // dump tables:
+ {
+ t_registryset allBackends;
+ dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" );
+ for (auto const& elem : that->m_filter2mediaType)
+ {
+ const Reference<deployment::XPackageRegistry> xBackend(
+ that->m_mediaType2backend.find( elem.second )->second );
+ allBackends.insert( xBackend );
+ dp_misc::TRACE(
+ "extension \"" + elem.first + "\" maps to media-type \"" + elem.second
+ + "\" maps to backend "
+ + Reference<lang::XServiceInfo>(
+ xBackend, UNO_QUERY_THROW )
+ ->getImplementationName()
+ + "\n");
+ }
+ dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" );
+ for (auto const& ambiguousBackend : that->m_ambiguousBackends)
+ {
+ OUStringBuffer buf;
+ buf.append(
+ Reference<lang::XServiceInfo>(
+ ambiguousBackend, UNO_QUERY_THROW )->getImplementationName()
+ + ": " );
+ const Sequence< Reference<deployment::XPackageTypeInfo> > types(
+ ambiguousBackend->getSupportedPackageTypes() );
+ for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) {
+ Reference<deployment::XPackageTypeInfo> const & xInfo =
+ types[ pos ];
+ buf.append( xInfo->getMediaType() );
+ const OUString filter( xInfo->getFileFilter() );
+ if (!filter.isEmpty()) {
+ buf.append( " (" + filter + ")" );
+ }
+ if (pos < (types.getLength() - 1))
+ buf.append( ", " );
+ }
+ dp_misc::TRACE(buf + "\n\n");
+ }
+ allBackends.insert( that->m_ambiguousBackends.begin(),
+ that->m_ambiguousBackends.end() );
+ OSL_ASSERT( allBackends == that->m_allBackends );
+ }
+#endif
+
+ return that;
+}
+
+// XUpdatable: broadcast to backends
+
+void PackageRegistryImpl::update()
+{
+ check();
+ for (auto const& backend : m_allBackends)
+ {
+ const Reference<util::XUpdatable> xUpdatable(backend, UNO_QUERY);
+ if (xUpdatable.is())
+ xUpdatable->update();
+ }
+}
+
+// XPackageRegistry
+
+Reference<deployment::XPackage> PackageRegistryImpl::bindPackage(
+ OUString const & url, OUString const & mediaType_, sal_Bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ check();
+ OUString mediaType(mediaType_);
+ if (mediaType.isEmpty())
+ {
+ ::ucbhelper::Content ucbContent;
+ bool bOk=true;
+
+ try
+ {
+ bOk = create_ucb_content(
+ &ucbContent, url, xCmdEnv, false /* no throw */ )
+ && !ucbContent.isFolder();
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ bOk = false;
+ }
+
+ if (bOk)
+ {
+ OUString title( StrTitle::getTitle( ucbContent ) );
+ for (;;)
+ {
+ const t_string2string::const_iterator iFind(
+ m_filter2mediaType.find(title) );
+ if (iFind != m_filter2mediaType.end()) {
+ mediaType = iFind->second;
+ break;
+ }
+ sal_Int32 point = title.indexOf( '.', 1 /* consume . */ );
+ if (point < 0)
+ break;
+ title = title.copy(point);
+ }
+ }
+ }
+ if (mediaType.isEmpty())
+ {
+ // try ambiguous backends:
+ for (auto const& ambiguousBackend : m_ambiguousBackends)
+ {
+ try {
+ return ambiguousBackend->bindPackage( url, mediaType, bRemoved,
+ identifier, xCmdEnv );
+ }
+ catch (const lang::IllegalArgumentException &) {
+ }
+ }
+ throw lang::IllegalArgumentException(
+ DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+ else
+ {
+ // get backend by media-type:
+ t_string2registry::const_iterator iFind(
+ m_mediaType2backend.find( normalizeMediaType(mediaType) ) );
+ if (iFind == m_mediaType2backend.end()) {
+ // xxx todo: more sophisticated media-type argument parsing...
+ sal_Int32 q = mediaType.indexOf( ';' );
+ if (q >= 0) {
+ iFind = m_mediaType2backend.find(
+ normalizeMediaType(
+ // cut parameters:
+ mediaType.subView( 0, q ) ) );
+ }
+ }
+ if (iFind == m_mediaType2backend.end()) {
+ throw lang::IllegalArgumentException(
+ DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+ return iFind->second->bindPackage( url, mediaType, bRemoved,
+ identifier, xCmdEnv );
+ }
+}
+
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+PackageRegistryImpl::getSupportedPackageTypes()
+{
+ return comphelper::containerToSequence(m_typesInfos);
+}
+} // anon namespace
+
+
+Reference<deployment::XPackageRegistry> create(
+ OUString const & context,
+ OUString const & cachePath,
+ Reference<XComponentContext> const & xComponentContext )
+{
+ return PackageRegistryImpl::create(
+ context, cachePath, xComponentContext );
+}
+
+} // namespace dp_registry
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/executable/dp_executable.cxx b/desktop/source/deployment/registry/executable/dp_executable.cxx
new file mode 100644
index 0000000000..40b253587b
--- /dev/null
+++ b/desktop/source/deployment/registry/executable/dp_executable.cxx
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <memory>
+#include <string_view>
+
+#include <dp_misc.h>
+#include <dp_backend.h>
+#include <dp_ucb.h>
+#include <dp_interact.h>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <osl/file.hxx>
+#include <ucbhelper/content.hxx>
+#include <svl/inettype.hxx>
+#include "dp_executablebackenddb.hxx"
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace dp_misc;
+
+namespace dp_registry::backend::executable {
+namespace {
+
+class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
+{
+ class ExecutablePackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ bool getFileAttributes(sal_uInt64& out_Attributes);
+ bool isUrlTargetInExtension() const;
+
+ public:
+ ExecutablePackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier)
+ {}
+ };
+ friend class ExecutablePackageImpl;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType, bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ void addDataToDb(OUString const & url);
+ bool hasActiveEntry(std::u16string_view url);
+ void revokeEntryFromDb(std::u16string_view url);
+
+ Reference<deployment::XPackageTypeInfo> m_xExecutableTypeInfo;
+ std::unique_ptr<ExecutableBackendDb> m_backendDb;
+public:
+ BackendImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+};
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : PackageRegistryBackend( args, xComponentContext ),
+ m_xExecutableTypeInfo(new Package::TypeInfo(
+ "application/vnd.sun.star.executable",
+ "", "Executable" ) )
+{
+ if (!transientMode())
+ {
+ OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
+ m_backendDb.reset(
+ new ExecutableBackendDb(getComponentContext(), dbFile));
+ }
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.executable.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+void BackendImpl::addDataToDb(OUString const & url)
+{
+ if (m_backendDb)
+ m_backendDb->addEntry(url);
+}
+
+void BackendImpl::revokeEntryFromDb(std::u16string_view url)
+{
+ if (m_backendDb)
+ m_backendDb->revokeEntry(url);
+}
+
+bool BackendImpl::hasActiveEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->hasActiveEntry(url);
+ return false;
+}
+
+
+// XPackageRegistry
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return Sequence<Reference<deployment::XPackageTypeInfo> >(
+ & m_xExecutableTypeInfo, 1);
+}
+
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+// PackageRegistryBackend
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType, bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ if (mediaType.isEmpty())
+ {
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ OUString name;
+ if (!bRemoved)
+ {
+ ::ucbhelper::Content ucbContent(
+ url, xCmdEnv, getComponentContext() );
+ name = StrTitle::getTitle( ucbContent );
+ }
+ if (subType.equalsIgnoreAsciiCase("vnd.sun.star.executable"))
+ {
+ return new BackendImpl::ExecutablePackageImpl(
+ this, url, name, m_xExecutableTypeInfo, bRemoved,
+ identifier);
+ }
+ }
+ }
+ return Reference<deployment::XPackage>();
+}
+
+
+// Package
+BackendImpl * BackendImpl::ExecutablePackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException( "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<ExecutablePackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::ExecutablePackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<dp_misc::AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ bool registered = getMyBackend()->hasActiveEntry(getURL());
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true /* IsPresent */,
+ beans::Ambiguous<sal_Bool>(
+ registered, false /* IsAmbiguous */ ) );
+}
+
+void BackendImpl::ExecutablePackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool /*startup*/,
+ ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & /*xCmdEnv*/ )
+{
+ checkAborted(abortChannel);
+ if (doRegisterPackage)
+ {
+ if (!isUrlTargetInExtension())
+ {
+ OSL_ASSERT(false);
+ return;
+ }
+ sal_uInt64 attributes = 0;
+ //Setting the executable attribute does not affect executables on Windows
+ if (getFileAttributes(attributes))
+ {
+ if(getMyBackend()->m_context == "user")
+ attributes |= osl_File_Attribute_OwnExe;
+ else if (getMyBackend()->m_context == "shared")
+ attributes |= (osl_File_Attribute_OwnExe | osl_File_Attribute_GrpExe
+ | osl_File_Attribute_OthExe);
+ else if (getMyBackend()->m_context != "bundled")
+ //Bundled extensions are required to be in the properly
+ //installed. That is an executable must have the right flags
+ OSL_ASSERT(false);
+
+ //This won't have effect on Windows
+ osl::File::setAttributes(
+ dp_misc::expandUnoRcUrl(m_url), attributes);
+ }
+ getMyBackend()->addDataToDb(getURL());
+ }
+ else
+ {
+ getMyBackend()->revokeEntryFromDb(getURL());
+ }
+}
+
+//We currently cannot check if this XPackage represents a content of a particular extension
+//But we can check if we are within $UNO_USER_PACKAGES_CACHE etc.
+//Done for security reasons. For example an extension manifest could contain a path to
+//an executable outside the extension.
+bool BackendImpl::ExecutablePackageImpl::isUrlTargetInExtension() const
+{
+ bool bSuccess = false;
+ OUString sExtensionDir;
+ if(getMyBackend()->m_context == "user")
+ sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_USER_PACKAGES_CACHE");
+ else if (getMyBackend()->m_context == "shared")
+ sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_SHARED_PACKAGES_CACHE");
+ else if (getMyBackend()->m_context == "bundled")
+ sExtensionDir = dp_misc::expandUnoRcTerm("$BUNDLED_EXTENSIONS");
+ else
+ OSL_ASSERT(false);
+ //remove file ellipses
+ if (osl::File::E_None == osl::File::getAbsoluteFileURL(OUString(), sExtensionDir, sExtensionDir))
+ {
+ OUString sFile;
+ if (osl::File::E_None == osl::File::getAbsoluteFileURL(
+ OUString(), dp_misc::expandUnoRcUrl(m_url), sFile))
+ {
+ if (sFile.match(sExtensionDir))
+ bSuccess = true;
+ }
+ }
+ return bSuccess;
+}
+
+bool BackendImpl::ExecutablePackageImpl::getFileAttributes(sal_uInt64& out_Attributes)
+{
+ bool bSuccess = false;
+ const OUString url(dp_misc::expandUnoRcUrl(m_url));
+ osl::DirectoryItem item;
+ if (osl::FileBase::E_None == osl::DirectoryItem::get(url, item))
+ {
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ if( osl::FileBase::E_None == item.getFileStatus(aStatus))
+ {
+ out_Attributes = aStatus.getAttributes();
+ bSuccess = true;
+ }
+ }
+ return bSuccess;
+}
+
+
+} // anon namespace
+
+
+} // namespace dp_registry::backend::executable
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::executable::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx
new file mode 100644
index 0000000000..29cbf85b33
--- /dev/null
+++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "dp_executablebackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/executable-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"exe";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"executable-backend-db";
+constexpr OUStringLiteral ENTRY_NAME = u"executable";
+
+namespace dp_registry::backend::executable {
+
+ExecutableBackendDb::ExecutableBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):RegisteredDb(xContext, url)
+{
+
+}
+
+OUString ExecutableBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString ExecutableBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString ExecutableBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString ExecutableBackendDb::getKeyElementName()
+{
+ return ENTRY_NAME;
+}
+
+
+} // namespace dp_registry::backend::executable
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx
new file mode 100644
index 0000000000..0561a8c546
--- /dev/null
+++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <dp_backenddb.hxx>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+namespace dp_registry::backend::executable
+{
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ The format looks like this:
+
+<?xml version="1.0"?>
+ */
+class ExecutableBackendDb : public dp_registry::backend::RegisteredDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+
+ virtual OUString getNSPrefix() override;
+
+ virtual OUString getRootElementName() override;
+
+ virtual OUString getKeyElementName() override;
+
+public:
+ ExecutableBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext,
+ OUString const& url);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/help/README.md b/desktop/source/deployment/registry/help/README.md
new file mode 100644
index 0000000000..24ea195181
--- /dev/null
+++ b/desktop/source/deployment/registry/help/README.md
@@ -0,0 +1 @@
+Support for help integrated in extensions. Also see /README.help.md.
diff --git a/desktop/source/deployment/registry/help/dp_help.cxx b/desktop/source/deployment/registry/help/dp_help.cxx
new file mode 100644
index 0000000000..a84bc28095
--- /dev/null
+++ b/desktop/source/deployment/registry/help/dp_help.cxx
@@ -0,0 +1,621 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <config_features.h>
+
+#include <strings.hrc>
+#include <dp_backend.h>
+#include <dp_misc.h>
+#include "dp_helpbackenddb.hxx"
+#include <dp_ucb.h>
+#include <rtl/uri.hxx>
+#include <osl/file.hxx>
+#include <ucbhelper/content.hxx>
+#include <svl/inettype.hxx>
+#include <unotools/pathoptions.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <o3tl/string_view.hxx>
+
+#if HAVE_FEATURE_XMLHELP
+#include <helpcompiler/compilehelp.hxx>
+#include <helpcompiler/HelpIndexer.hxx>
+#endif
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/util/XMacroExpander.hpp>
+#include <optional>
+#include <string_view>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend::help {
+namespace {
+
+
+class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
+{
+ class PackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+
+ public:
+ PackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier);
+
+ bool extensionContainsCompiledHelp();
+
+ //XPackage
+ virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override;
+ };
+ friend class PackageImpl;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ void implProcessHelp( PackageImpl * package, bool doRegisterPackage,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv);
+ void implCollectXhpFiles( const OUString& aDir,
+ std::vector< OUString >& o_rXhpFileVector );
+
+ ::std::optional<HelpBackendDb::Data> readDataFromDb(std::u16string_view url);
+ bool hasActiveEntry(std::u16string_view url);
+ bool activateEntry(std::u16string_view url);
+
+ Reference< ucb::XSimpleFileAccess3 > const & getFileAccess();
+ Reference< ucb::XSimpleFileAccess3 > m_xSFA;
+
+ const Reference<deployment::XPackageTypeInfo> m_xHelpTypeInfo;
+ Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
+ std::unique_ptr<HelpBackendDb> m_backendDb;
+
+public:
+ BackendImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+};
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : PackageRegistryBackend( args, xComponentContext ),
+ m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help",
+ OUString(),
+ DpResId(RID_STR_HELP)
+ ) ),
+ m_typeInfos{ m_xHelpTypeInfo }
+{
+ if (transientMode())
+ return;
+
+ OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
+ m_backendDb.reset(
+ new HelpBackendDb(getComponentContext(), dbFile));
+
+ //clean up data folders which are no longer used.
+ //This must not be done in the same process where the help files
+ //are still registers. Only after revoking and restarting OOo the folders
+ //can be removed. This works now, because the extension manager is a singleton
+ //and the backends are only create once per process.
+ std::vector<OUString> folders = m_backendDb->getAllDataUrls();
+ deleteUnusedFolders(folders);
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.help.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return m_typeInfos;
+}
+
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ // we don't support auto detection:
+ if (mediaType_.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType_, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ OUString name;
+ if (!bRemoved)
+ {
+ ::ucbhelper::Content ucbContent(
+ url, xCmdEnv, getComponentContext() );
+ name = StrTitle::getTitle( ucbContent );
+ }
+
+ if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
+ {
+ return new PackageImpl(
+ this, url, name, m_xHelpTypeInfo, bRemoved,
+ identifier);
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType_,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+::std::optional<HelpBackendDb::Data> BackendImpl::readDataFromDb(
+ std::u16string_view url)
+{
+ ::std::optional<HelpBackendDb::Data> data;
+ if (m_backendDb)
+ data = m_backendDb->getEntry(url);
+ return data;
+}
+
+bool BackendImpl::hasActiveEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->hasActiveEntry(url);
+ return false;
+}
+
+bool BackendImpl::activateEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->activateEntry(url);
+ return false;
+}
+
+
+BackendImpl::PackageImpl::PackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url, OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name, xPackageType, bRemoved,
+ identifier)
+{
+}
+
+// Package
+BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException("Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
+{
+ bool bCompiled = true;
+ OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl(getURL());
+
+ ::osl::Directory helpFolder(aExpandedHelpURL);
+ if ( helpFolder.open() == ::osl::File::E_None)
+ {
+ //iterate over the contents of the help folder
+ //We assume that all folders within the help folder contain language specific
+ //help files. If just one of them does not contain compiled help then this
+ //function returns false.
+ ::osl::DirectoryItem item;
+ ::osl::File::RC errorNext = ::osl::File::E_None;
+ while ((errorNext = helpFolder.getNextItem(item)) == ::osl::File::E_None)
+ {
+ //No find the language folders
+ ::osl::FileStatus stat(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |osl_FileStatus_Mask_FileURL);
+ if (item.getFileStatus(stat) == ::osl::File::E_None)
+ {
+ if (stat.getFileType() != ::osl::FileStatus::Directory)
+ continue;
+
+ //look if there is the folder help.idxl in the language folder
+ OUString compUrl(stat.getFileURL() + "/help.idxl");
+ ::osl::Directory compiledFolder(compUrl);
+ if (compiledFolder.open() != ::osl::File::E_None)
+ {
+ bCompiled = false;
+ break;
+ }
+ }
+ else
+ {
+ //Error
+ OSL_ASSERT(false);
+ bCompiled = false;
+ break;
+ }
+ }
+ if (errorNext != ::osl::File::E_NOENT
+ && errorNext != ::osl::File::E_None)
+ {
+ //Error
+ OSL_ASSERT(false);
+ bCompiled = false;
+ }
+ }
+ return bCompiled;
+}
+
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::PackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ BackendImpl * that = getMyBackend();
+
+ bool bReg = false;
+ if (that->hasActiveEntry(getURL()))
+ bReg = true;
+
+ return beans::Optional< beans::Ambiguous<sal_Bool> >( true, beans::Ambiguous<sal_Bool>( bReg, false ) );
+}
+
+
+void BackendImpl::PackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool /* startup */,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ BackendImpl* that = getMyBackend();
+ that->implProcessHelp( this, doRegisterPackage, xCmdEnv);
+}
+
+beans::Optional< OUString > BackendImpl::PackageImpl::getRegistrationDataURL()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ ::std::optional<HelpBackendDb::Data> data =
+ getMyBackend()->readDataFromDb(getURL());
+
+ if (data && getMyBackend()->hasActiveEntry(getURL()))
+ return beans::Optional<OUString>(true, data->dataUrl);
+
+ return beans::Optional<OUString>(true, OUString());
+}
+
+void BackendImpl::implProcessHelp(
+ PackageImpl * package, bool doRegisterPackage,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ Reference< deployment::XPackage > xPackage(package);
+ OSL_ASSERT(xPackage.is());
+ if (doRegisterPackage)
+ {
+ //revive already processed help if possible
+ if ( !activateEntry(xPackage->getURL()))
+ {
+ HelpBackendDb::Data data;
+ data.dataUrl = xPackage->getURL();
+ if (!package->extensionContainsCompiledHelp())
+ {
+#if HAVE_FEATURE_XMLHELP
+ const OUString sHelpFolder = createFolder(xCmdEnv);
+ data.dataUrl = sHelpFolder;
+
+ Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess();
+ OUString aHelpURL = xPackage->getURL();
+ OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl( aHelpURL );
+ if( !xSFA->isFolder( aExpandedHelpURL ) )
+ {
+ OUString aErrStr = DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR ) +
+ "No help folder";
+ OWeakObject* oWeakThis = this;
+ throw deployment::DeploymentException( OUString(), oWeakThis,
+ Any( uno::Exception( aErrStr, oWeakThis ) ) );
+ }
+
+ // Scan languages
+ Sequence< OUString > aLanguageFolderSeq = xSFA->getFolderContents( aExpandedHelpURL, true );
+ sal_Int32 nLangCount = aLanguageFolderSeq.getLength();
+ const OUString* pSeq = aLanguageFolderSeq.getConstArray();
+ for( sal_Int32 iLang = 0 ; iLang < nLangCount ; ++iLang )
+ {
+ OUString aLangURL = pSeq[iLang];
+ if( xSFA->isFolder( aLangURL ) )
+ {
+ std::vector< OUString > aXhpFileVector;
+
+ // calculate jar file URL
+ sal_Int32 indexStartSegment = aLangURL.lastIndexOf('/');
+ // for example "/en"
+ OUString langFolderURLSegment(
+ aLangURL.copy(
+ indexStartSegment + 1, aLangURL.getLength() - indexStartSegment - 1));
+
+ //create the folder in the "temporary folder"
+ ::ucbhelper::Content langFolderContent;
+ const OUString langFolderDest = makeURL(sHelpFolder, langFolderURLSegment);
+ const OUString langFolderDestExpanded = ::dp_misc::expandUnoRcUrl(langFolderDest);
+ ::dp_misc::create_folder(
+ &langFolderContent,
+ langFolderDest, xCmdEnv);
+
+ static constexpr OUString aHelpStr(u"help"_ustr);
+
+ OUString aJarFile(
+ makeURL(sHelpFolder, langFolderURLSegment + "/" + aHelpStr + ".jar"));
+ aJarFile = ::dp_misc::expandUnoRcUrl(aJarFile);
+
+ OUString aEncodedJarFilePath = rtl::Uri::encode(
+ aJarFile, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ OUString aDestBasePath = "vnd.sun.star.zip://" +
+ aEncodedJarFilePath + "/" ;
+
+ sal_Int32 nLenLangFolderURL = aLangURL.getLength() + 1;
+
+ Sequence< OUString > aSubLangSeq = xSFA->getFolderContents( aLangURL, true );
+ sal_Int32 nSubLangCount = aSubLangSeq.getLength();
+ const OUString* pSubLangSeq = aSubLangSeq.getConstArray();
+ for( sal_Int32 iSubLang = 0 ; iSubLang < nSubLangCount ; ++iSubLang )
+ {
+ OUString aSubFolderURL = pSubLangSeq[iSubLang];
+ if( !xSFA->isFolder( aSubFolderURL ) )
+ continue;
+
+ implCollectXhpFiles( aSubFolderURL, aXhpFileVector );
+
+ // Copy to package (later: move?)
+ std::u16string_view aPureFolderName = aSubFolderURL.subView( nLenLangFolderURL );
+ OUString aDestPath = aDestBasePath + aPureFolderName;
+ xSFA->copy( aSubFolderURL, aDestPath );
+ }
+
+ // Call compiler
+ sal_Int32 nXhpFileCount = aXhpFileVector.size();
+ std::unique_ptr<OUString[]> pXhpFiles(new OUString[nXhpFileCount]);
+ for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
+ {
+ OUString aXhpFile = aXhpFileVector[iXhp];
+ OUString aXhpRelFile = aXhpFile.copy( nLenLangFolderURL );
+ pXhpFiles[iXhp] = aXhpRelFile;
+ }
+
+ OUString aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
+ OUString aOfficeHelpPathFileURL;
+ ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath, aOfficeHelpPathFileURL );
+
+ HelpProcessingErrorInfo aErrorInfo;
+ bool bSuccess = compileExtensionHelp(
+ aOfficeHelpPathFileURL, aHelpStr, aLangURL,
+ nXhpFileCount, pXhpFiles.get(),
+ langFolderDestExpanded, aErrorInfo );
+
+ pXhpFiles.reset();
+
+ if( bSuccess )
+ {
+ OUString aLang;
+ sal_Int32 nLastSlash = aLangURL.lastIndexOf( '/' );
+ if( nLastSlash != -1 )
+ aLang = aLangURL.copy( nLastSlash + 1 );
+ else
+ aLang = "en";
+
+ HelpIndexer aIndexer(aLang, "help", langFolderDestExpanded, langFolderDestExpanded);
+ aIndexer.indexDocuments();
+ }
+
+ if( !bSuccess )
+ {
+ TranslateId pErrStrId;
+ switch( aErrorInfo.m_eErrorClass )
+ {
+ case HelpProcessingErrorClass::General: pErrStrId = RID_STR_HELPPROCESSING_GENERAL_ERROR; break;
+ case HelpProcessingErrorClass::XmlParsing: pErrStrId = RID_STR_HELPPROCESSING_XMLPARSING_ERROR; break;
+ default: ;
+ };
+
+ OUString aErrStr;
+ if (pErrStrId)
+ {
+ aErrStr = DpResId(pErrStrId);
+
+ // Remove CR/LF
+ OUString aErrMsg( aErrorInfo.m_aErrorMsg );
+ sal_Unicode const nCR = 13, nLF = 10;
+ sal_Int32 nSearchCR = aErrMsg.indexOf( nCR );
+ sal_Int32 nSearchLF = aErrMsg.indexOf( nLF );
+ if( nSearchCR != -1 || nSearchLF != -1 )
+ {
+ sal_Int32 nCopy;
+ if( nSearchCR == -1 )
+ nCopy = nSearchLF;
+ else if( nSearchLF == -1 )
+ nCopy = nSearchCR;
+ else
+ nCopy = ( nSearchCR < nSearchLF ) ? nSearchCR : nSearchLF;
+
+ aErrMsg = aErrMsg.copy( 0, nCopy );
+ }
+ aErrStr += aErrMsg;
+ if (pErrStrId != RID_STR_HELPPROCESSING_XMLPARSING_ERROR && !aErrorInfo.m_aXMLParsingFile.isEmpty() )
+ {
+ aErrStr += " in ";
+
+ OUString aDecodedFile = rtl::Uri::decode( aErrorInfo.m_aXMLParsingFile,
+ rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ aErrStr += aDecodedFile;
+ if( aErrorInfo.m_nXMLParsingLine != -1 )
+ {
+ aErrStr += ", line " +
+ OUString::number( aErrorInfo.m_nXMLParsingLine );
+ }
+ }
+ }
+
+ OWeakObject* oWeakThis = this;
+ throw deployment::DeploymentException( OUString(), oWeakThis,
+ Any( uno::Exception( aErrStr, oWeakThis ) ) );
+ }
+ }
+ }
+#else
+ (void) xCmdEnv;
+#endif
+ }
+ // Writing the data entry replaces writing the flag file. If we got to this
+ // point the registration was successful.
+ if (m_backendDb)
+ m_backendDb->addEntry(xPackage->getURL(), data);
+ }
+ } //if (doRegisterPackage)
+ else
+ {
+ if (m_backendDb)
+ m_backendDb->revokeEntry(xPackage->getURL());
+ }
+}
+
+void BackendImpl::implCollectXhpFiles( const OUString& aDir,
+ std::vector< OUString >& o_rXhpFileVector )
+{
+ Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess();
+
+ // Scan xhp files recursively
+ Sequence< OUString > aSeq = xSFA->getFolderContents( aDir, true );
+ sal_Int32 nCount = aSeq.getLength();
+ const OUString* pSeq = aSeq.getConstArray();
+ for( sal_Int32 i = 0 ; i < nCount ; ++i )
+ {
+ OUString aURL = pSeq[i];
+ if( xSFA->isFolder( aURL ) )
+ {
+ implCollectXhpFiles( aURL, o_rXhpFileVector );
+ }
+ else
+ {
+ sal_Int32 nLastDot = aURL.lastIndexOf( '.' );
+ if( nLastDot != -1 )
+ {
+ std::u16string_view aExt = aURL.subView( nLastDot + 1 );
+ if( o3tl::equalsIgnoreAsciiCase( aExt, u"xhp" ) )
+ o_rXhpFileVector.push_back( aURL );
+ }
+ }
+ }
+}
+
+Reference< ucb::XSimpleFileAccess3 > const & BackendImpl::getFileAccess()
+{
+ if( !m_xSFA.is() )
+ {
+ Reference<XComponentContext> const & xContext = getComponentContext();
+ if( xContext.is() )
+ {
+ m_xSFA = ucb::SimpleFileAccess::create(xContext);
+ }
+ if( !m_xSFA.is() )
+ {
+ throw RuntimeException(
+ "dp_registry::backend::help::BackendImpl::getFileAccess(), "
+ "could not instantiate SimpleFileAccess." );
+ }
+ }
+ return m_xSFA;
+}
+
+} // anon namespace
+
+} // namespace dp_registry
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::help::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx
new file mode 100644
index 0000000000..19bb3c3ce5
--- /dev/null
+++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "dp_helpbackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/help-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"help";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"help-backend-db";
+constexpr OUStringLiteral KEY_ELEMENT_NAME = u"help";
+
+namespace dp_registry::backend::help {
+
+HelpBackendDb::HelpBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):BackendDb(xContext, url)
+{
+
+}
+
+OUString HelpBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString HelpBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString HelpBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString HelpBackendDb::getKeyElementName()
+{
+ return KEY_ELEMENT_NAME;
+}
+
+
+void HelpBackendDb::addEntry(OUString const & url, Data const & data)
+{
+ try{
+ if (!activateEntry(url))
+ {
+ Reference<css::xml::dom::XNode> helpNode
+ = writeKeyElement(url);
+
+ writeSimpleElement(u"data-url", data.dataUrl, helpNode);
+ save();
+ }
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in help backend db: " + m_urlDb, nullptr, exc);
+ }
+}
+
+
+::std::optional<HelpBackendDb::Data>
+HelpBackendDb::getEntry(std::u16string_view url)
+{
+ try
+ {
+ HelpBackendDb::Data retData;
+ Reference<css::xml::dom::XNode> aNode = getKeyElement(url);
+ if (aNode.is())
+ {
+ retData.dataUrl = readSimpleElement(u"data-url", aNode);
+ }
+ else
+ {
+ return ::std::optional<Data>();
+ }
+ return ::std::optional<Data>(retData);
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ throw;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in help backend db: " + m_urlDb, nullptr, exc);
+ }
+}
+
+std::vector<OUString> HelpBackendDb::getAllDataUrls()
+{
+ return getOneChildFromAllEntries(u"data-url");
+}
+
+} // namespace dp_registry::backend::help
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx
new file mode 100644
index 0000000000..a46bd8663c
--- /dev/null
+++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <optional>
+#include <string_view>
+
+#include <dp_backenddb.hxx>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+namespace dp_registry::backend::help
+{
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ */
+class HelpBackendDb : public dp_registry::backend::BackendDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+
+ virtual OUString getNSPrefix() override;
+
+ virtual OUString getRootElementName() override;
+
+ virtual OUString getKeyElementName() override;
+
+public:
+ struct Data
+ {
+ /* the URL to the folder containing the compiled help files, etc.
+ */
+ OUString dataUrl;
+ };
+
+public:
+ HelpBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext,
+ OUString const& url);
+
+ void addEntry(OUString const& url, Data const& data);
+
+ ::std::optional<Data> getEntry(std::u16string_view url);
+ //must also return the data urls for entries with @active="false". That is,
+ //those are currently revoked.
+ std::vector<OUString> getAllDataUrls();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/inc/dp_backend.h b/desktop/source/deployment/registry/inc/dp_backend.h
new file mode 100644
index 0000000000..a68420d6b0
--- /dev/null
+++ b/desktop/source/deployment/registry/inc/dp_backend.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <dp_shared.hxx>
+#include <dp_interact.h>
+#include <rtl/ref.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/deployment/XPackageRegistry.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <unordered_map>
+#include <strings.hrc>
+#include <utility>
+
+namespace dp_registry::backend
+{
+
+class PackageRegistryBackend;
+
+inline constexpr OUString BACKEND_SERVICE_NAME = u"com.sun.star.deployment.PackageRegistryBackend"_ustr;
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::deployment::XPackage > t_PackageBase;
+
+
+class Package : protected cppu::BaseMutex, public t_PackageBase
+{
+ PackageRegistryBackend * getMyBackend() const;
+ void processPackage_impl(
+ bool registerPackage,
+ bool startup,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv );
+
+protected:
+ ::rtl::Reference<PackageRegistryBackend> m_myBackend;
+ const OUString m_url;
+ OUString m_name;
+ OUString m_displayName;
+ const css::uno::Reference<css::deployment::XPackageTypeInfo> m_xPackageType;
+ const bool m_bRemoved;
+ //Only set if m_bRemoved = true;
+ const OUString m_identifier;
+
+ void check() const;
+ void fireModified();
+ virtual void SAL_CALL disposing() override;
+
+ void checkAborted(
+ ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel );
+
+ // @@@ to be implemented by specific backend:
+ virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> >
+ isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv )
+ = 0;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv )
+ = 0;
+
+ virtual ~Package() override;
+ Package( ::rtl::Reference<PackageRegistryBackend> myBackend,
+ OUString url,
+ OUString name,
+ OUString displayName,
+ css::uno::Reference<css::deployment::XPackageTypeInfo> const & xPackageType,
+ bool bRemoved,
+ OUString identifier);
+
+public:
+
+ class TypeInfo :
+ public ::cppu::WeakImplHelper<css::deployment::XPackageTypeInfo>
+ {
+ const OUString m_mediaType;
+ const OUString m_fileFilter;
+ const OUString m_shortDescr;
+ public:
+ virtual ~TypeInfo() override;
+ TypeInfo( OUString mediaType,
+ OUString fileFilter,
+ OUString shortDescr )
+ : m_mediaType(std::move(mediaType)), m_fileFilter(std::move(fileFilter)),
+ m_shortDescr(std::move(shortDescr))
+ {}
+ // XPackageTypeInfo
+ virtual OUString SAL_CALL getMediaType() override;
+ virtual OUString SAL_CALL getDescription() override;
+ virtual OUString SAL_CALL getShortDescription() override;
+ virtual OUString SAL_CALL getFileFilter() override;
+ virtual css::uno::Any SAL_CALL getIcon( sal_Bool highContrast,
+ sal_Bool smallIcon ) override;
+ };
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener(
+ css::uno::Reference<css::lang::XEventListener> const & xListener ) override;
+ virtual void SAL_CALL removeEventListener(
+ css::uno::Reference<css::lang::XEventListener> const & xListener ) override;
+
+ // XModifyBroadcaster
+ virtual void SAL_CALL addModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+ virtual void SAL_CALL removeModifyListener(
+ css::uno::Reference<css::util::XModifyListener> const & xListener ) override;
+
+ // XPackage
+ virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL
+ createAbortChannel() override;
+ virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> >
+ SAL_CALL isRegistered(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual ::sal_Int32 SAL_CALL checkPrerequisites(
+ const css::uno::Reference< css::task::XAbortChannel >& xAbortChannel,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv,
+ sal_Bool noLicenseChecking) override;
+
+ virtual ::sal_Bool SAL_CALL checkDependencies(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv ) override;
+
+ virtual void SAL_CALL registerPackage(
+ sal_Bool startup,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void SAL_CALL revokePackage(
+ sal_Bool startup,
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual sal_Bool SAL_CALL isBundle() override;
+ virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> >
+ SAL_CALL getBundle(
+ css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual OUString SAL_CALL getName() override;
+ virtual css::beans::Optional< OUString > SAL_CALL getIdentifier() override;
+ virtual OUString SAL_CALL getVersion() override;
+ virtual OUString SAL_CALL getURL() override;
+ virtual OUString SAL_CALL getDisplayName() override;
+ virtual OUString SAL_CALL getDescription() override;
+ virtual OUString SAL_CALL getLicenseText() override;
+ virtual css::uno::Sequence< OUString > SAL_CALL
+ getUpdateInformationURLs() override;
+ virtual css::beans::StringPair SAL_CALL getPublisherInfo() override;
+ virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL
+ getIcon( sal_Bool bHighContrast ) override;
+ virtual css::uno::Reference<css::deployment::XPackageTypeInfo> SAL_CALL
+ getPackageType() override;
+ virtual void SAL_CALL exportTo(
+ OUString const & destFolderURL,
+ OUString const & newTitle,
+ sal_Int32 nameClashAction,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual OUString SAL_CALL getRepositoryName() override;
+ virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override;
+ virtual sal_Bool SAL_CALL isRemoved() override;
+
+};
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::lang::XEventListener,
+ css::deployment::XPackageRegistry,
+ css::lang::XServiceInfo > t_BackendBase;
+
+
+class PackageRegistryBackend
+ : protected cppu::BaseMutex, public t_BackendBase
+{
+ //The map held originally WeakReferences. The map entries are removed in the disposing
+ //function, which is called when the XPackages are destructed or they are
+ //explicitly disposed. The latter happens, for example, when an extension is
+ //removed (see dp_manager.cxx). However, because of how the help systems work, now
+ // XPackageManager::getDeployedPackages is called often. This results in a lot
+ //of bindPackage calls which are costly. Therefore we keep hard references in
+ //the map now.
+ typedef std::unordered_map<
+ OUString, css::uno::Reference<css::deployment::XPackage> > t_string2ref;
+ t_string2ref m_bound;
+
+protected:
+ OUString m_cachePath;
+ css::uno::Reference<css::uno::XComponentContext> m_xComponentContext;
+
+ OUString m_context;
+ // currently only for library containers:
+ enum class Context {
+ Unknown, User, Shared, Bundled, Tmp, Document
+ } m_eContext;
+
+ static OUString StrCannotDetectMediaType() { return DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE); }
+ static OUString StrUnsupportedMediaType() { return DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE); }
+
+ // @@@ to be implemented by specific backend:
+ virtual css::uno::Reference<css::deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv )
+ = 0;
+
+ void check();
+ virtual void SAL_CALL disposing() override;
+
+ virtual ~PackageRegistryBackend() override;
+ PackageRegistryBackend(
+ css::uno::Sequence<css::uno::Any> const & args,
+ css::uno::Reference<css::uno::XComponentContext> const & xContext );
+
+ /* creates a folder with a unique name.
+ If url is empty then it is created in the backend folder, otherwise
+ at a location relative to that folder specified by url.
+ */
+ OUString createFolder(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv);
+ /* deletes folders and files.
+
+ All folder all files which end with ".tmp" or ".tmp_" and which are
+ not used are deleted.
+ */
+ void deleteUnusedFolders(
+ std::vector< OUString> const & usedFolders);
+ /* deletes one folder with a "temporary" name and the corresponding
+ tmp file, which was used to derive the folder name.
+ */
+ static void deleteTempFolder(
+ OUString const & folderUrl);
+
+public:
+ static OUString StrRegisteringPackage() { return DpResId(RID_STR_REGISTERING_PACKAGE); }
+ static OUString StrRevokingPackage() { return DpResId(RID_STR_REVOKING_PACKAGE); }
+
+ css::uno::Reference<css::uno::XComponentContext> const &
+ getComponentContext() const { return m_xComponentContext; }
+
+ OUString const & getCachePath() const { return m_cachePath; }
+ bool transientMode() const { return m_cachePath.isEmpty(); }
+
+ const OUString& getContext() const {return m_context; }
+
+ // XEventListener
+ virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override;
+
+ // XPackageRegistry
+ virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL bindPackage(
+ OUString const & url, OUString const & mediaType,
+ sal_Bool bRemoved, OUString const & identifier,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+// virtual void SAL_CALL packageRemoved(
+// OUString const & url, OUString const & mediaType)
+// throw (css::deployment::DeploymentException,
+// css::uno::RuntimeException);
+
+};
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/inc/dp_backenddb.hxx b/desktop/source/deployment/registry/inc/dp_backenddb.hxx
new file mode 100644
index 0000000000..7852014667
--- /dev/null
+++ b/desktop/source/deployment/registry/inc/dp_backenddb.hxx
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ustring.hxx>
+#include <deque>
+#include <string_view>
+#include <vector>
+
+namespace com::sun::star {
+ namespace uno {
+ class XComponentContext;
+ }
+ namespace xml::dom {
+ class XDocument;
+ class XNode;
+ }
+ namespace xml::xpath {
+ class XXPathAPI;
+ }
+}
+
+namespace dp_registry::backend {
+
+class BackendDb
+{
+private:
+
+ css::uno::Reference<css::xml::dom::XDocument> m_doc;
+ css::uno::Reference<css::xml::xpath::XXPathAPI> m_xpathApi;
+
+ BackendDb(BackendDb const &) = delete;
+ BackendDb & operator = (BackendDb const &) = delete;
+
+protected:
+ const css::uno::Reference<css::uno::XComponentContext> m_xContext;
+ OUString m_urlDb;
+
+protected:
+
+ /* caller must make sure that only one thread accesses the function
+ */
+ css::uno::Reference<css::xml::dom::XDocument> const & getDocument();
+
+ /* the namespace prefix is "reg" (without quotes)
+ */
+ css::uno::Reference<css::xml::xpath::XXPathAPI> const & getXPathAPI();
+ void save();
+ void removeElement(OUString const & sXPathExpression);
+
+ css::uno::Reference<css::xml::dom::XNode> getKeyElement(
+ std::u16string_view url);
+
+ void writeSimpleList(
+ std::deque< OUString> const & list,
+ std::u16string_view sListTagName,
+ std::u16string_view sMemberTagName,
+ css::uno::Reference<css::xml::dom::XNode> const & xParent);
+
+ void writeVectorOfPair(
+ std::vector< std::pair< OUString, OUString > > const & vecPairs,
+ std::u16string_view sVectorTagName,
+ std::u16string_view sPairTagName,
+ std::u16string_view sFirstTagName,
+ std::u16string_view sSecondTagName,
+ css::uno::Reference<css::xml::dom::XNode> const & xParent);
+
+ void writeSimpleElement(
+ std::u16string_view sElementName, OUString const & value,
+ css::uno::Reference<css::xml::dom::XNode> const & xParent);
+
+ css::uno::Reference<css::xml::dom::XNode> writeKeyElement(
+ OUString const & url);
+
+ OUString readSimpleElement(
+ std::u16string_view sElementName,
+ css::uno::Reference<css::xml::dom::XNode> const & xParent);
+
+ std::vector< std::pair< OUString, OUString > >
+ readVectorOfPair(
+ css::uno::Reference<css::xml::dom::XNode> const & parent,
+ std::u16string_view sListTagName,
+ std::u16string_view sPairTagName,
+ std::u16string_view sFirstTagName,
+ std::u16string_view sSecondTagName);
+
+ std::deque< OUString> readList(
+ css::uno::Reference<css::xml::dom::XNode> const & parent,
+ std::u16string_view sListTagName,
+ std::u16string_view sMemberTagName);
+
+ /* returns the values of one particularly child element of all key elements.
+ */
+ std::vector< OUString> getOneChildFromAllEntries(
+ std::u16string_view sElementName);
+
+
+ /* returns the namespace which is to be written as xmlns attribute
+ into the root element.
+ */
+ virtual OUString getDbNSName()=0;
+ /* return the namespace prefix which is to be registered with the XPath API.
+
+ The prefix can then be used in XPath expressions.
+ */
+ virtual OUString getNSPrefix()=0;
+ /* returns the name of the root element without any namespace prefix.
+ */
+ virtual OUString getRootElementName()=0;
+ /* returns the name of xml element for each entry
+ */
+ virtual OUString getKeyElementName()=0;
+
+public:
+ BackendDb(css::uno::Reference<css::uno::XComponentContext> const & xContext,
+ OUString const & url);
+ virtual ~BackendDb() {};
+
+ void removeEntry(std::u16string_view url);
+
+ /* This is called to write the "revoked" attribute to the entry.
+ This is done when XPackage::revokePackage is called.
+ */
+ void revokeEntry(std::u16string_view url);
+
+ /* returns false if the entry does not exist yet.
+ */
+ bool activateEntry(std::u16string_view url);
+
+ bool hasActiveEntry(std::u16string_view url);
+
+};
+
+class RegisteredDb: public BackendDb
+{
+
+public:
+ RegisteredDb( css::uno::Reference<css::uno::XComponentContext> const & xContext,
+ OUString const & url);
+
+
+ void addEntry(OUString const & url);
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.cxx b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx
new file mode 100644
index 0000000000..8e656c5d7d
--- /dev/null
+++ b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "dp_extbackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/extension-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"ext";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"extension-backend-db";
+constexpr OUStringLiteral KEY_ELEMENT_NAME = u"extension";
+
+namespace dp_registry::backend::bundle {
+
+ExtensionBackendDb::ExtensionBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):BackendDb(xContext, url)
+{
+
+}
+
+OUString ExtensionBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString ExtensionBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString ExtensionBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString ExtensionBackendDb::getKeyElementName()
+{
+ return KEY_ELEMENT_NAME;
+}
+
+void ExtensionBackendDb::addEntry(OUString const & url, Data const & data)
+{
+ try{
+ //reactive revoked entry if possible.
+ if (!activateEntry(url))
+ {
+ Reference<css::xml::dom::XNode> extensionNodeNode = writeKeyElement(url);
+ writeVectorOfPair( data.items, u"extension-items", u"item",
+ u"url", u"media-type", extensionNodeNode);
+ save();
+ }
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to write data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+ExtensionBackendDb::Data ExtensionBackendDb::getEntry(std::u16string_view url)
+{
+ try
+ {
+ ExtensionBackendDb::Data retData;
+ Reference<css::xml::dom::XNode> aNode = getKeyElement(url);
+
+ if (aNode.is())
+ {
+ retData.items =
+ readVectorOfPair( aNode, u"extension-items", u"item",
+ u"url", u"media-type");
+ }
+ return retData;
+ }
+ catch(const css::uno::Exception &)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Extension Manager: failed to read data entry in backend db: " +
+ m_urlDb, nullptr, exc);
+ }
+}
+
+} // namespace dp_registry::backend::bundle
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.hxx b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx
new file mode 100644
index 0000000000..fb736e6e26
--- /dev/null
+++ b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <rtl/ustring.hxx>
+
+#include <dp_backenddb.hxx>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+namespace dp_registry::backend::bundle
+{
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ */
+class ExtensionBackendDb : public dp_registry::backend::BackendDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+ virtual OUString getNSPrefix() override;
+ virtual OUString getRootElementName() override;
+ virtual OUString getKeyElementName() override;
+
+public:
+ struct Data
+ {
+ /* every element consists of a pair of the url to the item (jar,rdb, etc)
+ and the media type
+ */
+ std::vector<std::pair<OUString, OUString>> items;
+ };
+
+public:
+ ExtensionBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext,
+ OUString const& url);
+
+ void addEntry(OUString const& url, Data const& data);
+
+ Data getEntry(std::u16string_view url);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/package/dp_package.cxx b/desktop/source/deployment/registry/package/dp_package.cxx
new file mode 100644
index 0000000000..6af2fb5515
--- /dev/null
+++ b/desktop/source/deployment/registry/package/dp_package.cxx
@@ -0,0 +1,1594 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <strings.hrc>
+#include <dp_package.hxx>
+#include <dp_backend.h>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <dp_interact.h>
+#include <dp_dependencies.hxx>
+#include <dp_platform.hxx>
+#include <dp_descriptioninfoset.hxx>
+#include <dp_identifier.hxx>
+#include <dp_resource.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <ucbhelper/content.hxx>
+#include <svl/inettype.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <com/sun/star/lang/WrappedTargetException.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/io/Pipe.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/task/InteractionClassification.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/XInteractionReplaceExistingData.hpp>
+#include <com/sun/star/ucb/NameClashResolveRequest.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/packages/manifest/ManifestReader.hpp>
+#include <com/sun/star/packages/manifest/ManifestWriter.hpp>
+#include <com/sun/star/deployment/DependencyException.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/PlatformException.hpp>
+#include <com/sun/star/deployment/Prerequisites.hpp>
+#include <optional>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "dp_extbackenddb.hxx"
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+namespace dp_registry::backend::bundle {
+namespace {
+
+typedef cppu::ImplInheritanceHelper<PackageRegistryBackend> ImplBaseT;
+
+
+class BackendImpl : public ImplBaseT
+{
+ class PackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+ /** contains the old tooltip description for the Extension Manager GUI in OOo v.2.x
+ We keep it for backward compatibility.
+ */
+ OUString m_oldDescription;
+ OUString m_url_expanded;
+ const bool m_legacyBundle;
+ Sequence< Reference<deployment::XPackage> > m_bundle;
+ Sequence< Reference<deployment::XPackage> > * m_pBundle;
+
+ ExtensionBackendDb::Data m_dbData;
+
+ Reference<deployment::XPackage> bindBundleItem(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, //that is, using data base information
+ OUString const & identifier,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ bool notifyDetectionError = true );
+
+ typedef std::vector< Reference<deployment::XPackage> > t_packagevec;
+ void scanBundle(
+ t_packagevec & bundle,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv );
+ void scanLegacyBundle(
+ t_packagevec & bundle,
+ OUString const & url,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ bool skip_registration = false );
+ std::vector<Reference<deployment::XPackage> > getPackagesFromDb(
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv);
+ bool checkPlatform(
+ Reference<ucb::XCommandEnvironment > const & environment);
+
+ bool checkDependencies(
+ Reference<ucb::XCommandEnvironment > const &
+ environment,
+ DescriptionInfoset const & description);
+ // throws css::uno::RuntimeException,
+ // css::deployment::DeploymentException
+
+ /// @throws deployment::DeploymentException
+ /// @throws ucb::CommandFailedException
+ /// @throws ucb::CommandAbortedException
+ /// @throws RuntimeException
+ bool checkLicense(
+ Reference< ucb::XCommandEnvironment > const & xCmdEnv,
+ DescriptionInfoset const & description, bool bNoLicenseChecking);
+ // @throws DeploymentException
+ OUString getTextFromURL(
+ const Reference< ucb::XCommandEnvironment >& xCmdEnv,
+ const OUString& licenseUrl);
+
+ DescriptionInfoset getDescriptionInfoset() const;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL disposing() override;
+
+
+ public:
+ PackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url,
+ OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool legacyBundle,
+ bool bRemoved,
+ OUString const & identifier);
+
+ // XPackage
+ virtual sal_Bool SAL_CALL isBundle() override;
+
+ virtual Sequence< Reference<deployment::XPackage> > SAL_CALL getBundle(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override;
+ virtual OUString SAL_CALL getDescription() override;
+
+ virtual OUString SAL_CALL getLicenseText() override;
+
+ virtual void SAL_CALL exportTo(
+ OUString const & destFolderURL, OUString const & newTitle,
+ sal_Int32 nameClashAction,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual ::sal_Int32 SAL_CALL checkPrerequisites(
+ const Reference< task::XAbortChannel >& xAbortChannel,
+ const Reference< ucb::XCommandEnvironment >& xCmdEnv,
+ sal_Bool noLicenseChecking) override;
+
+ virtual sal_Bool SAL_CALL checkDependencies(
+ const Reference< ucb::XCommandEnvironment >& xCmdEnv ) override;
+
+ virtual beans::Optional<OUString> SAL_CALL getIdentifier() override;
+
+ virtual OUString SAL_CALL getVersion() override;
+
+ virtual Sequence<OUString> SAL_CALL getUpdateInformationURLs() override;
+
+ virtual beans::StringPair SAL_CALL getPublisherInfo() override;
+
+ virtual OUString SAL_CALL getDisplayName() override;
+
+ virtual Reference< graphic::XGraphic > SAL_CALL
+ getIcon( sal_Bool bHighContrast ) override;
+ };
+ friend class PackageImpl;
+
+ Reference<deployment::XPackageRegistry> m_xRootRegistry;
+ const Reference<deployment::XPackageTypeInfo> m_xBundleTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xLegacyBundleTypeInfo;
+ Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
+
+ std::unique_ptr<ExtensionBackendDb> m_backendDb;
+
+ void addDataToDb(OUString const & url, ExtensionBackendDb::Data const & data);
+ ExtensionBackendDb::Data readDataFromDb(std::u16string_view url);
+ void revokeEntryFromDb(std::u16string_view url);
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override;
+
+ virtual void SAL_CALL disposing() override;
+
+public:
+ BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext,
+ Reference<deployment::XPackageRegistry> const & xRootRegistry );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( OUString const& name ) override;
+ virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+ using ImplBaseT::disposing;
+};
+
+//Used to find a XPackage with a particular URL
+class XPackage_eq
+{
+ OUString m_URL;
+public:
+ explicit XPackage_eq(OUString s) : m_URL(std::move(s)) {}
+ bool operator() (const Reference<deployment::XPackage> & p) const
+ {
+ return m_URL == p->getURL();
+ }
+};
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext,
+ Reference<deployment::XPackageRegistry> const & xRootRegistry )
+ : ImplBaseT( args, xComponentContext ),
+ m_xRootRegistry( xRootRegistry ),
+ m_xBundleTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.package-bundle",
+ "*.oxt;*.uno.pkg",
+ DpResId(RID_STR_PACKAGE_BUNDLE)
+ ) ),
+ m_xLegacyBundleTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.legacy-package-bundle",
+ "*.zip",
+ m_xBundleTypeInfo->getShortDescription()
+ ) ),
+ m_typeInfos{ m_xBundleTypeInfo, m_xLegacyBundleTypeInfo }
+{
+ if (!transientMode())
+ {
+ OUString dbFile = makeURL(getCachePath(), getImplementationName());
+ dbFile = makeURL(dbFile, "backenddb.xml");
+ m_backendDb.reset(
+ new ExtensionBackendDb(getComponentContext(), dbFile));
+ }
+}
+
+
+void BackendImpl::disposing()
+{
+ m_xRootRegistry.clear();
+ PackageRegistryBackend::disposing();
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.bundle.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence<OUString> BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return m_typeInfos;
+}
+
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ //Notify the backend responsible for processing the different media
+ //types that this extension was removed.
+ ExtensionBackendDb::Data data = readDataFromDb(url);
+ for (auto const& item : data.items)
+ {
+ m_xRootRegistry->packageRemoved(item.first, item.second);
+ }
+
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_,
+ bool bRemoved, OUString const & identifier,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ OUString mediaType( mediaType_ );
+ if (mediaType.isEmpty())
+ {
+ // detect media-type:
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, url, xCmdEnv ))
+ {
+ if (ucbContent.isFolder())
+ {
+ //Every .oxt, uno.pkg file must contain a META-INF folder
+ ::ucbhelper::Content metaInfContent;
+ if (create_ucb_content(
+ &metaInfContent, makeURL( url, "META-INF" ),
+ xCmdEnv, false /* no throw */ ))
+ {
+ mediaType = "application/vnd.sun.star.package-bundle";
+ }
+ //No support of legacy bundles, because every folder could be one.
+ }
+ else
+ {
+ const OUString title( StrTitle::getTitle( ucbContent ) );
+ if (title.endsWithIgnoreAsciiCase(".oxt") ||
+ title.endsWithIgnoreAsciiCase(".uno.pkg"))
+ mediaType = "application/vnd.sun.star.package-bundle";
+ else if (title.endsWithIgnoreAsciiCase(".zip"))
+ mediaType = "application/vnd.sun.star.legacy-package-bundle";
+ }
+ }
+ if (mediaType.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+
+ //In case a XPackage is created for a removed extension, we cannot
+ //obtain the name
+ OUString name;
+ if (!bRemoved)
+ {
+ ::ucbhelper::Content ucbContent(
+ url, xCmdEnv, getComponentContext() );
+ name = StrTitle::getTitle( ucbContent );
+ }
+ if (subType.equalsIgnoreAsciiCase("vnd.sun.star.package-bundle"))
+ {
+ return new PackageImpl(
+ this, url, name, m_xBundleTypeInfo, false, bRemoved,
+ identifier);
+ }
+ else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.legacy-package-bundle"))
+ {
+ return new PackageImpl(
+ this, url, name, m_xLegacyBundleTypeInfo, true, bRemoved,
+ identifier);
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+void BackendImpl::addDataToDb(
+ OUString const & url, ExtensionBackendDb::Data const & data)
+{
+ if (m_backendDb)
+ m_backendDb->addEntry(url, data);
+}
+
+ExtensionBackendDb::Data BackendImpl::readDataFromDb(
+ std::u16string_view url)
+{
+ ExtensionBackendDb::Data data;
+ if (m_backendDb)
+ data = m_backendDb->getEntry(url);
+ return data;
+}
+
+void BackendImpl::revokeEntryFromDb(std::u16string_view url)
+{
+ if (m_backendDb)
+ m_backendDb->revokeEntry(url);
+}
+
+
+BackendImpl::PackageImpl::PackageImpl(
+ ::rtl::Reference<PackageRegistryBackend> const & myBackend,
+ OUString const & url,
+ OUString const & name,
+ Reference<deployment::XPackageTypeInfo> const & xPackageType,
+ bool legacyBundle, bool bRemoved, OUString const & identifier)
+ : Package( myBackend, url, name, name /* display-name */,
+ xPackageType, bRemoved, identifier),
+ m_url_expanded( expandUnoRcUrl( url ) ),
+ m_legacyBundle( legacyBundle ),
+ m_pBundle( nullptr )
+{
+ if (bRemoved)
+ m_dbData = getMyBackend()->readDataFromDb(url);
+}
+
+BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException("Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+void BackendImpl::PackageImpl::disposing()
+{
+ sal_Int32 len = m_bundle.getLength();
+ Reference<deployment::XPackage> const * p = m_bundle.getConstArray();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ try_dispose( p[ pos ] );
+ m_bundle.realloc( 0 );
+
+ Package::disposing();
+}
+
+// Package
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::PackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ //In case the object was created for a removed extension (m_bRemoved = true)
+ //but the extension is not registered, then bundle will be empty. Then
+ //the return value will be Optional<...>.IsPresent= false. Although this is
+ //not true, this does not matter. Then registerPackage or revokePackage
+ //would never be called for the items. But since the extension is removed
+ //and not registered anyway, this does not matter.
+ const Sequence< Reference<deployment::XPackage> > bundle(
+ getBundle( abortChannel, xCmdEnv ) );
+
+ bool reg = false;
+ bool present = false;
+ bool ambig = false;
+ for ( sal_Int32 pos = bundle.getLength(); pos--; )
+ {
+ Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
+ Reference<task::XAbortChannel> xSubAbortChannel(
+ xPackage->createAbortChannel() );
+ AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
+ beans::Optional< beans::Ambiguous<sal_Bool> > option(
+ xPackage->isRegistered( xSubAbortChannel, xCmdEnv ) );
+
+ //present = true if at least one bundle item has this value.
+ //reg = true if all bundle items have an option value (option.IsPresent == 1)
+ //and all have value of true (option.Value.Value == true)
+ //If not, then the bundle has the status of not registered and ambiguous.
+ if (option.IsPresent)
+ {
+ beans::Ambiguous<sal_Bool> const & status = option.Value;
+ if (present)
+ {
+ //we never come here in the first iteration
+ if (reg != bool(status.Value)) {
+
+ ambig = true;
+ reg = false;
+ break;
+ }
+ }
+ else
+ {
+ //we always come here in the first iteration
+ reg = status.Value;
+ present = true;
+ }
+ }
+ }
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ present, beans::Ambiguous<sal_Bool>(reg, ambig) );
+}
+
+OUString BackendImpl::PackageImpl::getTextFromURL(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv,
+ const OUString& licenseUrl)
+{
+ try
+ {
+ ::ucbhelper::Content descContent(
+ licenseUrl, xCmdEnv, getMyBackend()->getComponentContext());
+ std::vector<sal_Int8> seq = dp_misc::readFile(descContent);
+ return OUString( reinterpret_cast<char const *>(
+ seq.data()), seq.size(), RTL_TEXTENCODING_UTF8);
+ }
+ catch (const css::uno::Exception&)
+ {
+ Any exc( ::cppu::getCaughtException() );
+ throw css::deployment::DeploymentException(
+ "Could not read file " + licenseUrl, nullptr, exc);
+ }
+
+}
+
+DescriptionInfoset BackendImpl::PackageImpl::getDescriptionInfoset() const
+{
+ return dp_misc::getDescriptionInfoset(m_url_expanded);
+}
+
+bool BackendImpl::PackageImpl::checkPlatform(
+ css::uno::Reference< css::ucb::XCommandEnvironment > const & environment)
+{
+ bool ret = false;
+ DescriptionInfoset info(getDescriptionInfoset());
+ Sequence<OUString> platforms(info.getSupportedPlatforms());
+ if (hasValidPlatform(platforms))
+ {
+ ret = true;
+ }
+ else
+ {
+ ret = false;
+ OUString msg(
+ "unsupported platform");
+ Any e(
+ css::deployment::PlatformException(
+ msg, static_cast<OWeakObject *>(this), this));
+ if (!interactContinuation(
+ e, cppu::UnoType< css::task::XInteractionApprove >::get(),
+ environment, nullptr, nullptr))
+ {
+ throw css::deployment::DeploymentException(
+ msg, static_cast<OWeakObject *>(this), e);
+ }
+ }
+ return ret;
+}
+
+
+bool BackendImpl::PackageImpl::checkDependencies(
+ css::uno::Reference< css::ucb::XCommandEnvironment > const & environment,
+ DescriptionInfoset const & description)
+{
+ css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > >
+ unsatisfied(dp_misc::Dependencies::check(description));
+
+ if (!unsatisfied.hasElements()) {
+ return true;
+ } else {
+ OUString msg(
+ "unsatisfied dependencies");
+ Any e(
+ css::deployment::DependencyException(
+ msg, static_cast<OWeakObject *>(this), unsatisfied));
+ if (!interactContinuation(
+ e, cppu::UnoType< css::task::XInteractionApprove >::get(),
+ environment, nullptr, nullptr))
+ {
+ throw css::deployment::DeploymentException(
+ msg, static_cast<OWeakObject *>(this), e);
+ }
+ return false;
+ }
+}
+
+bool BackendImpl::PackageImpl::checkLicense(
+ css::uno::Reference< css::ucb::XCommandEnvironment > const & xCmdEnv,
+ DescriptionInfoset const & info, bool alreadyInstalled)
+{
+ try
+ {
+ ::std::optional<SimpleLicenseAttributes> simplLicAttr
+ = info.getSimpleLicenseAttributes();
+ if (! simplLicAttr)
+ return true;
+ OUString sLic = info.getLocalizedLicenseURL();
+ //If we do not get a localized licence then there is an error in the description.xml
+ //This should be handled by using a validating parser. Therefore we assume that no
+ //license is available.
+ if (sLic.isEmpty())
+ throw css::deployment::DeploymentException(
+ "Could not obtain path to license. Possible error in description.xml", nullptr, Any());
+ OUString sHref = m_url_expanded + "/" + sLic;
+ OUString sLicense = getTextFromURL(xCmdEnv, sHref);
+ ////determine who has to agree to the license
+ //check correct value for attribute
+ if ( simplLicAttr->acceptBy != "user" && simplLicAttr->acceptBy != "admin")
+ throw css::deployment::DeploymentException(
+ "Could not obtain attribute simple-license@accept-by or it has no valid value", nullptr, Any());
+
+
+ //Only use interaction if there is no version of this extension already installed
+ //and the suppress-on-update flag is not set for the new extension
+ // alreadyInstalled | bSuppressOnUpdate | show license
+
+ // 0 | 0 | 1
+ // 0 | 1 | 1
+ // 1 | 0 | 1
+ // 1 | 1 | 0
+
+ if ( !(alreadyInstalled && simplLicAttr->suppressOnUpdate))
+ {
+ css::deployment::LicenseException licExc(
+ OUString(), nullptr, getDisplayName(), sLicense,
+ simplLicAttr->acceptBy);
+ bool approve = false;
+ bool abort = false;
+ if (! interactContinuation(
+ Any(licExc), cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, &approve, &abort ))
+ throw css::deployment::DeploymentException(
+ "Could not interact with user.", nullptr, Any());
+
+ return approve;
+ }
+ return true;
+ } catch (const css::ucb::CommandFailedException&) {
+ throw;
+ } catch (const css::ucb::CommandAbortedException&) {
+ throw;
+ } catch (const css::deployment::DeploymentException&) {
+ throw;
+ } catch (const css::uno::RuntimeException&) {
+ throw;
+ } catch (const css::uno::Exception&) {
+ Any anyExc = cppu::getCaughtException();
+ throw css::deployment::DeploymentException("Unexpected exception", nullptr, anyExc);
+ }
+}
+
+::sal_Int32 BackendImpl::PackageImpl::checkPrerequisites(
+ const css::uno::Reference< css::task::XAbortChannel >&,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv,
+ sal_Bool alreadyInstalled)
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ DescriptionInfoset info = getDescriptionInfoset();
+ if (!info.hasDescription())
+ return 0;
+
+ //always return LICENSE as long as the user did not accept the license
+ //so that XExtensionManager::checkPrerequisitesAndEnable will again
+ //check the license
+ if (!checkPlatform(xCmdEnv))
+ return deployment::Prerequisites::PLATFORM |
+ deployment::Prerequisites::LICENSE;
+ else if(!checkDependencies(xCmdEnv, info))
+ return deployment::Prerequisites::DEPENDENCIES |
+ deployment::Prerequisites::LICENSE;
+ else if(!checkLicense(xCmdEnv, info, alreadyInstalled))
+ return deployment::Prerequisites::LICENSE;
+ else
+ return 0;
+}
+
+sal_Bool BackendImpl::PackageImpl::checkDependencies(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ DescriptionInfoset info = getDescriptionInfoset();
+ if (!info.hasDescription())
+ return true;
+
+ return checkDependencies(xCmdEnv, info);
+}
+
+beans::Optional<OUString> BackendImpl::PackageImpl::getIdentifier()
+{
+ OUString identifier;
+ if (m_bRemoved)
+ identifier = m_identifier;
+ else
+ identifier = dp_misc::generateIdentifier(
+ getDescriptionInfoset().getIdentifier(), m_name);
+
+ return beans::Optional<OUString>(
+ true, identifier);
+}
+
+OUString BackendImpl::PackageImpl::getVersion()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return getDescriptionInfoset().getVersion();
+}
+
+Sequence<OUString> BackendImpl::PackageImpl::getUpdateInformationURLs()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ return getDescriptionInfoset().getUpdateInformationUrls();
+}
+
+beans::StringPair BackendImpl::PackageImpl::getPublisherInfo()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+ std::pair< OUString, OUString > aInfo = getDescriptionInfoset().getLocalizedPublisherNameAndURL();
+ beans::StringPair aStrPair( aInfo.first, aInfo.second );
+ return aStrPair;
+}
+
+
+uno::Reference< graphic::XGraphic > BackendImpl::PackageImpl::getIcon( sal_Bool bHighContrast )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ uno::Reference< graphic::XGraphic > xGraphic;
+
+ OUString aIconURL = getDescriptionInfoset().getIconURL( bHighContrast );
+ if ( !aIconURL.isEmpty() )
+ {
+ OUString aFullIconURL = m_url_expanded + "/" + aIconURL;
+
+ uno::Reference< XComponentContext > xContext( getMyBackend()->getComponentContext() );
+ uno::Reference< graphic::XGraphicProvider > xGraphProvider( graphic::GraphicProvider::create(xContext) );
+
+ uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue(
+ "URL", aFullIconURL) };
+ xGraphic = xGraphProvider->queryGraphic( aMediaProps );
+ }
+
+ return xGraphic;
+}
+
+
+void BackendImpl::PackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ const Sequence< Reference<deployment::XPackage> > bundle(
+ getBundle( abortChannel, xCmdEnv ) );
+
+ if (doRegisterPackage)
+ {
+ ExtensionBackendDb::Data data;
+ const sal_Int32 len = bundle.getLength();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ checkAborted(abortChannel);
+ Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
+ Reference<task::XAbortChannel> xSubAbortChannel(
+ xPackage->createAbortChannel() );
+ AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
+ try {
+ xPackage->registerPackage( startup, xSubAbortChannel, xCmdEnv );
+ }
+ catch (const Exception &)
+ {
+ //We even try a rollback if the user cancelled the action (CommandAbortedException)
+ //in order to prevent invalid database entries.
+ Any exc( ::cppu::getCaughtException() );
+ // try to handle exception, notify:
+ bool approve = false, abort = false;
+ if (! interactContinuation(
+ Any( lang::WrappedTargetException(
+ "bundle item registration error!",
+ static_cast<OWeakObject *>(this), exc ) ),
+ cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv,
+ &approve, &abort )) {
+ OSL_ASSERT( !approve && !abort );
+ if (m_legacyBundle) // default for legacy packages: ignore
+ continue;
+ // no selection at all, so rethrow;
+ // no C++ rethrow after getCaughtException(),
+ // see cppuhelper/exc_hlp.hxx:
+ ::cppu::throwException(exc);
+ }
+ if (approve && !abort) // ignore error, just continue
+ continue;
+
+ {
+ ProgressLevel progress( xCmdEnv, "rollback..." );
+ // try rollback
+ for ( ; pos--; )
+ {
+ try {
+ bundle[ pos ]->revokePackage(
+ startup, xSubAbortChannel, xCmdEnv );
+ }
+ catch (const Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ // ignore any errors of rollback
+ }
+ }
+ progress.update( "rollback finished." );
+ }
+
+ deployment::DeploymentException dpExc;
+ if (exc >>= dpExc) {
+ throw ucb::CommandFailedException(
+ dpExc.Message, dpExc.Context, dpExc.Cause );
+ }
+ else {
+ // rethrow CommandFailedException
+ ::cppu::throwException(exc);
+ }
+ }
+ data.items.emplace_back(xPackage->getURL(),
+ xPackage->getPackageType()->getMediaType());
+ }
+ getMyBackend()->addDataToDb(getURL(), data);
+ }
+ else
+ {
+ // revoke in reverse order:
+ for ( sal_Int32 pos = bundle.getLength(); pos--; )
+ {
+ checkAborted(abortChannel);
+ Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
+ Reference<task::XAbortChannel> xSubAbortChannel(
+ xPackage->createAbortChannel() );
+ AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
+ try {
+ bundle[ pos ]->revokePackage(
+ startup, xSubAbortChannel, xCmdEnv );
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const ucb::CommandAbortedException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ // CommandFailedException, DeploymentException:
+ Any exc( ::cppu::getCaughtException() );
+ // try to handle exception, notify:
+ bool approve = false, abort = false;
+ if (! interactContinuation(
+ Any( lang::WrappedTargetException(
+ "bundle item revocation error!",
+ static_cast<OWeakObject *>(this), exc ) ),
+ cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv,
+ &approve, &abort )) {
+ OSL_ASSERT( !approve && !abort );
+ if (m_legacyBundle) // default for legacy packages: ignore
+ continue;
+ // no selection at all, so rethrow
+ // no C++ rethrow after getCaughtException(),
+ // see cppuhelper/exc_hlp.hxx:
+ ::cppu::throwException(exc);
+ }
+ // ignore errors when revoking, although abort may have been
+ // selected
+ }
+ }
+ getMyBackend()->revokeEntryFromDb(getURL());
+ }
+}
+
+
+OUString BackendImpl::PackageImpl::getDescription()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ const OUString sRelativeURL(getDescriptionInfoset().getLocalizedDescriptionURL());
+ OUString sDescription;
+ if (!sRelativeURL.isEmpty())
+ {
+ OUString sURL = m_url_expanded + "/" + sRelativeURL;
+
+ try
+ {
+ sDescription = getTextFromURL( css::uno::Reference< css::ucb::XCommandEnvironment >(), sURL );
+ }
+ catch ( const css::deployment::DeploymentException& )
+ {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ }
+
+ if (!sDescription.isEmpty())
+ return sDescription;
+ return m_oldDescription;
+}
+
+
+OUString BackendImpl::PackageImpl::getLicenseText()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ OUString sLicense;
+ DescriptionInfoset aInfo = getDescriptionInfoset();
+
+ ::std::optional< SimpleLicenseAttributes > aSimplLicAttr = aInfo.getSimpleLicenseAttributes();
+ if ( aSimplLicAttr )
+ {
+ OUString aLicenseURL = aInfo.getLocalizedLicenseURL();
+
+ if ( !aLicenseURL.isEmpty() )
+ {
+ OUString aFullURL = m_url_expanded + "/" + aLicenseURL;
+ sLicense = getTextFromURL( Reference< ucb::XCommandEnvironment >(), aFullURL);
+ }
+ }
+
+ return sLicense;
+}
+
+
+void BackendImpl::PackageImpl::exportTo(
+ OUString const & destFolderURL, OUString const & newTitle,
+ sal_Int32 nameClashAction, Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ ::ucbhelper::Content sourceContent(
+ m_url_expanded, xCmdEnv, getMyBackend()->getComponentContext() );
+ OUString title(newTitle);
+ if (title.isEmpty())
+ sourceContent.getPropertyValue( "Title" ) >>= title;
+ OUString destURL( makeURL( destFolderURL, ::rtl::Uri::encode(
+ title, rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) ) );
+
+ if (nameClashAction == ucb::NameClash::ASK)
+ {
+ if (create_ucb_content(
+ nullptr, destURL, xCmdEnv, false /* no throw */ )) {
+ bool replace = false, abort = false;
+ if (! interactContinuation(
+ Any( ucb::NameClashResolveRequest(
+ "file already exists: " + title,
+ static_cast<OWeakObject *>(this),
+ task::InteractionClassification_QUERY,
+ destFolderURL, title, OUString() ) ),
+ cppu::UnoType<ucb::XInteractionReplaceExistingData>::get(), xCmdEnv,
+ &replace, &abort ) || !replace) {
+ return;
+ }
+ }
+ }
+ else if (nameClashAction != ucb::NameClash::OVERWRITE) {
+ throw ucb::CommandFailedException("unsupported nameClashAction!",
+ static_cast<OWeakObject *>(this), Any() );
+ }
+ erase_path( destURL, xCmdEnv );
+
+ OUString destFolder =
+ "vnd.sun.star.zip://" +
+ ::rtl::Uri::encode( destURL,
+ rtl_UriCharClassRegName,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) +
+ "/";
+
+ ::ucbhelper::Content destFolderContent(
+ destFolder, xCmdEnv, getMyBackend()->getComponentContext() );
+ {
+ // transfer every item of folder into zip:
+ Reference<sdbc::XResultSet> xResultSet(
+ sourceContent.createCursor( Sequence<OUString>() ) );
+ ProgressLevel progress( xCmdEnv, OUString() );
+ while (xResultSet->next())
+ {
+ ::ucbhelper::Content subContent(
+ Reference<ucb::XContentAccess>(
+ xResultSet, UNO_QUERY_THROW )->queryContent(),
+ xCmdEnv, getMyBackend()->getComponentContext() );
+ destFolderContent.transferContent(
+ subContent, ::ucbhelper::InsertOperation::Copy,
+ OUString(), ucb::NameClash::OVERWRITE );
+ progress.update( Any() ); // animating progress bar
+ }
+ }
+
+ // assure META-INF folder:
+ ::ucbhelper::Content metainfFolderContent;
+ create_folder( &metainfFolderContent,
+ makeURL( destFolderContent.getURL(), "META-INF" ),
+ xCmdEnv );
+
+ if (m_legacyBundle)
+ {
+ // easy to migrate legacy bundles to new format:
+ // just export them once using a .oxt name!
+ // set detected media-types of any bundle item:
+
+ // collect all manifest entries:
+ Sequence< Reference<deployment::XPackage> > bundle;
+ try {
+ bundle = getBundle( Reference<task::XAbortChannel>(), xCmdEnv );
+ }
+ // xxx todo: think about exception specs:
+ catch (const deployment::DeploymentException &) {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+ catch (const lang::IllegalArgumentException &) {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+
+ std::vector< Sequence<beans::PropertyValue> > manifest;
+ manifest.reserve( bundle.getLength() );
+ sal_Int32 baseURLlen = m_url_expanded.getLength();
+ Reference<deployment::XPackage> const *pbundle = bundle.getConstArray();
+ static constexpr OUStringLiteral strMediaType( u"MediaType" );
+ static constexpr OUStringLiteral strFullPath( u"FullPath" );
+ static constexpr OUStringLiteral strIsFolder( u"IsFolder" );
+ for ( sal_Int32 pos = bundle.getLength(); pos--; )
+ {
+ Reference<deployment::XPackage> const & xPackage = pbundle[ pos ];
+ OUString url_( expandUnoRcUrl( xPackage->getURL() ) );
+ OSL_ASSERT( url_.getLength() >= baseURLlen );
+ OUString fullPath;
+ if (url_.getLength() > baseURLlen)
+ fullPath = url_.copy( baseURLlen + 1 );
+ ::ucbhelper::Content ucbContent(
+ url_, xCmdEnv, getMyBackend()->getComponentContext() );
+ if (ucbContent.getPropertyValue(strIsFolder).get<bool>())
+ fullPath += "/";
+ Sequence<beans::PropertyValue> attribs( 2 );
+ beans::PropertyValue * pattribs = attribs.getArray();
+ pattribs[ 0 ].Name = strFullPath;
+ pattribs[ 0 ].Value <<= fullPath;
+ pattribs[ 1 ].Name = strMediaType;
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OUString mediaType;
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is())
+ mediaType = xPackageType->getMediaType();
+ else
+ mediaType = "unknown";
+ pattribs[ 1 ].Value <<= mediaType;
+ manifest.push_back( attribs );
+ }
+
+ // write into pipe:
+ Reference<XComponentContext> xContext(
+ getMyBackend()->getComponentContext() );
+ Reference<packages::manifest::XManifestWriter> xManifestWriter =
+ packages::manifest::ManifestWriter::create( xContext );
+ Reference<io::XOutputStream> xPipe( io::Pipe::create(xContext), UNO_QUERY_THROW );
+ xManifestWriter->writeManifestSequence(
+ xPipe, comphelper::containerToSequence(manifest) );
+
+ // write buffered pipe data to content:
+ ::ucbhelper::Content manifestContent(
+ makeURL( metainfFolderContent.getURL(), "manifest.xml" ),
+ xCmdEnv, getMyBackend()->getComponentContext() );
+ manifestContent.writeStream(
+ Reference<io::XInputStream>( xPipe, UNO_QUERY_THROW ),
+ true /* replace existing */ );
+ }
+ else
+ {
+ bool bSuccess = false;
+ try
+ {
+ // overwrite manifest.xml:
+ ::ucbhelper::Content manifestContent;
+ if ( ! create_ucb_content(
+ &manifestContent,
+ makeURL( m_url_expanded, "META-INF/manifest.xml" ),
+ xCmdEnv, false ) )
+ {
+ OSL_FAIL( "### missing META-INF/manifest.xml file!" );
+ return;
+ }
+
+ metainfFolderContent.transferContent(
+ manifestContent, ::ucbhelper::InsertOperation::Copy,
+ OUString(), ucb::NameClash::OVERWRITE );
+ bSuccess = true;
+ }
+ catch (const css::ucb::ContentCreationException &)
+ {
+ TOOLS_WARN_EXCEPTION("desktop.deployment", "exception on overwriting manifest");
+ }
+
+ if (!bSuccess)
+ throw RuntimeException( "UCB transferContent() failed!",
+ static_cast<OWeakObject *>(this) );
+ }
+
+ // xxx todo: maybe obsolete in the future
+ try {
+ destFolderContent.executeCommand( "flush", Any() );
+ }
+ catch (const ucb::UnsupportedCommandException &) {
+ }
+}
+
+
+sal_Bool BackendImpl::PackageImpl::isBundle()
+{
+ return true;
+}
+
+
+Sequence< Reference<deployment::XPackage> > BackendImpl::PackageImpl::getBundle(
+ Reference<task::XAbortChannel> const & xAbortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ Sequence< Reference<deployment::XPackage> > * pBundle = m_pBundle;
+ if (pBundle == nullptr)
+ {
+ t_packagevec bundle;
+ if (m_bRemoved)
+ {
+ bundle = getPackagesFromDb(xCmdEnv);
+ }
+ else
+ {
+ try {
+ if (m_legacyBundle)
+ {
+ // .zip legacy packages allow script.xlb, dialog.xlb in bundle
+ // root folder:
+ OUString mediaType;
+ // probe for script.xlb:
+ if (create_ucb_content(
+ nullptr, makeURL( m_url_expanded, "script.xlb" ),
+ xCmdEnv, false /* no throw */ )) {
+ mediaType = "application/vnd.sun.star.basic-library";
+ }
+ // probe for dialog.xlb:
+ else if (create_ucb_content(
+ nullptr, makeURL( m_url_expanded, "dialog.xlb" ),
+ xCmdEnv, false /* no throw */ ))
+ mediaType = "application/vnd.sun.star.dialog-library";
+
+ if (!mediaType.isEmpty()) {
+ const Reference<deployment::XPackage> xPackage(
+ bindBundleItem( getURL(), mediaType, false, OUString(),
+ xCmdEnv ) );
+ if (xPackage.is())
+ bundle.push_back( xPackage );
+ // continue scanning:
+ }
+ scanLegacyBundle( bundle, getURL(),
+ AbortChannel::get(xAbortChannel), xCmdEnv );
+ }
+ else
+ {
+ // .oxt:
+ scanBundle( bundle, AbortChannel::get(xAbortChannel), xCmdEnv );
+ }
+
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const ucb::CommandFailedException &) {
+ throw;
+ }
+ catch (const ucb::CommandAbortedException &) {
+ throw;
+ }
+ catch (const deployment::DeploymentException &) {
+ throw;
+ }
+ catch (const Exception &) {
+ Any exc( ::cppu::getCaughtException() );
+ throw deployment::DeploymentException(
+ "error scanning bundle: " + getURL(),
+ static_cast<OWeakObject *>(this), exc );
+ }
+ }
+
+ // sort: schema before config data, typelibs before components:
+ Sequence< Reference<deployment::XPackage> > ret( bundle.size() );
+ Reference<deployment::XPackage> * pret = ret.getArray();
+ sal_Int32 lower_end = 0;
+ sal_Int32 upper_end = ret.getLength();
+ for (auto const& elem : bundle)
+ {
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ elem->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is())
+ {
+ const OUString mediaType( xPackageType->getMediaType() );
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ) &&
+ type.equalsIgnoreAsciiCase("application") &&
+ (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-component") ||
+ subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data")))
+ {
+ --upper_end;
+ pret[ upper_end ] = elem;
+ continue;
+ }
+ }
+ pret[ lower_end ] = elem;
+ ++lower_end;
+ }
+ OSL_ASSERT( lower_end == upper_end );
+
+ const ::osl::MutexGuard guard( m_aMutex );
+ pBundle = m_pBundle;
+ if (pBundle == nullptr) {
+ m_bundle = ret;
+ pBundle = &m_bundle;
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ m_pBundle = pBundle;
+ }
+ }
+ else {
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ }
+ return *pBundle;
+}
+
+bool isBundle_( std::u16string_view mediaType )
+{
+ // xxx todo: additional parsing?
+ return !mediaType.empty() &&
+ (o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.package-bundle") ||
+ o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.legacy-package-bundle"));
+}
+
+
+Reference<deployment::XPackage> BackendImpl::PackageImpl::bindBundleItem(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ bool notifyDetectionError )
+{
+ // ignore any nested bundles:
+ if (isBundle_(mediaType))
+ return Reference<deployment::XPackage>();
+
+ Reference<deployment::XPackage>xPackage;
+ try {
+ try {
+ xPackage.set( getMyBackend()->m_xRootRegistry->bindPackage(
+ url, mediaType, bRemoved, identifier, xCmdEnv ) );
+ OSL_ASSERT( xPackage.is() );
+ } catch (css::lang::IllegalArgumentException & e) {
+ css::uno::Any exc(cppu::getCaughtException());
+ throw css::lang::WrappedTargetException(
+ "wrapped: " + e.Message, e.Context, exc);
+ }
+ }
+ catch (const RuntimeException &) {
+ throw;
+ }
+ catch (const ucb::CommandFailedException &) {
+ // ignore already handled error
+ }
+ catch (const Exception &) {
+ const Any exc( ::cppu::getCaughtException() );
+ if (notifyDetectionError ||
+ !exc.isExtractableTo( cppu::UnoType<lang::IllegalArgumentException>::get()) )
+ {
+ (void)interactContinuation(
+ Any( lang::WrappedTargetException("bundle item error!",
+ static_cast<OWeakObject *>(this), exc ) ),
+ cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, nullptr, nullptr );
+ }
+ }
+
+ if (xPackage.is()) {
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ // ignore any nested bundles:
+ if (xPackageType.is() && isBundle_( xPackageType->getMediaType() ))
+ xPackage.clear();
+ }
+ return xPackage;
+}
+
+
+void BackendImpl::PackageImpl::scanBundle(
+ t_packagevec & bundle,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv )
+{
+ OSL_ASSERT( !m_legacyBundle );
+
+ OUString mfUrl( makeURL( m_url_expanded, "META-INF/manifest.xml" ) );
+ ::ucbhelper::Content manifestContent;
+ if (! create_ucb_content(
+ &manifestContent, mfUrl, xCmdEnv, false /* no throw */ ))
+ {
+ SAL_WARN(
+ "desktop.deployment",
+ "cannot create UCB Content for <" << mfUrl << ">" );
+ return;
+ }
+
+
+ const LanguageTag& officeLocale = getOfficeLanguageTag();
+ const std::vector< OUString > officeFallbacks( officeLocale.getFallbackStrings( true));
+ const size_t nPenaltyMax = std::numeric_limits<size_t>::max();
+ size_t descrPenalty = nPenaltyMax;
+ OUString descrFile;
+
+ const Reference<XComponentContext> xContext(
+ getMyBackend()->getComponentContext() );
+ Reference<packages::manifest::XManifestReader> xManifestReader =
+ packages::manifest::ManifestReader::create( xContext );
+ const Sequence< Sequence<beans::PropertyValue> > manifestSeq(
+ xManifestReader->readManifestSequence( manifestContent.openStream() ) );
+ const OUString packageRootURL( getURL() );
+ for ( sal_Int32 pos = manifestSeq.getLength(); pos--; )
+ {
+ OUString fullPath, mediaType;
+ Sequence<beans::PropertyValue> const & attribs = manifestSeq[ pos ];
+ for ( sal_Int32 i = attribs.getLength(); i--; )
+ {
+ if (!(fullPath.isEmpty() || mediaType.isEmpty()))
+ break;
+ if ( attribs[i].Name == "FullPath" )
+ attribs[i].Value >>= fullPath;
+ else if ( attribs[i].Name == "MediaType" )
+ attribs[i].Value >>= mediaType;
+ }
+
+ if ( fullPath.isEmpty() || mediaType.isEmpty() || mediaType == "text/xml" )// opt: exclude common text/xml
+ continue;
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (! INetContentTypes::parse( mediaType, type, subType, &params ))
+ continue;
+
+ {
+ auto const iter = params.find("platform"_ostr);
+ if (iter != params.end() && !platform_fits(iter->second.m_sValue))
+ continue;
+ }
+ const OUString url( makeURL( packageRootURL, fullPath ) );
+
+ // check for bundle description:
+ if (type.equalsIgnoreAsciiCase("application") &&
+ subType.equalsIgnoreAsciiCase( "vnd.sun.star.package-bundle-description"))
+ {
+ // check locale:
+ auto const iter = params.find("locale"_ostr);
+ if (iter == params.end())
+ {
+ if (descrFile.isEmpty())
+ descrFile = url;
+ }
+ else {
+ // match best locale:
+ LanguageTag descrTag(iter->second.m_sValue);
+ if (officeLocale.getLanguage() == descrTag.getLanguage())
+ {
+ size_t nPenalty = nPenaltyMax;
+ const std::vector< OUString > descrFallbacks( descrTag.getFallbackStrings( true));
+ for (size_t o=0; o < officeFallbacks.size() && nPenalty == nPenaltyMax; ++o)
+ {
+ for (size_t d=0; d < descrFallbacks.size() && nPenalty == nPenaltyMax; ++d)
+ {
+ if (officeFallbacks[o] == descrFallbacks[d])
+ {
+ // The last fallbacks are always language-only
+ // fallbacks, so we _will_ have _some_ match if
+ // we ever entered the overall if() condition.
+ nPenalty = o * 1000 + d;
+ if (descrPenalty > nPenalty)
+ {
+ descrPenalty = nPenalty;
+ descrFile = url;
+ }
+ }
+ }
+ }
+ }
+ // TODO: we could break here if descrPenalty==0 for an exact
+ // match of officeLocale, but the previous code didn't; are
+ // there side effects?
+ }
+ continue;
+ }
+
+ checkAborted( abortChannel );
+
+ //We make sure that we only create one XPackage for a particular URL.
+ //Sometime programmers insert the same URL several times in the manifest
+ //which may lead to DisposedExceptions.
+ if (std::none_of(bundle.begin(), bundle.end(), XPackage_eq(url)))
+ {
+ const Reference<deployment::XPackage> xPackage(
+ bindBundleItem( url, mediaType, false, OUString(), xCmdEnv ) );
+ if (xPackage.is())
+ bundle.push_back( xPackage );
+ }
+ else
+ {
+ SAL_WARN("desktop.deployment", "manifest.xml contains a duplicate entry (from " << url << ")");
+ }
+ }
+
+ if (descrFile.isEmpty())
+ return;
+
+ ::ucbhelper::Content descrFileContent;
+ if (!create_ucb_content( &descrFileContent, descrFile,
+ xCmdEnv, false /* no throw */ ))
+ return;
+
+ // patch description:
+ std::vector<sal_Int8> bytes( readFile( descrFileContent ) );
+ OUStringBuffer buf;
+ if ( !bytes.empty() )
+ {
+ buf.append( OUString( reinterpret_cast<char const *>(
+ bytes.data() ),
+ bytes.size(), RTL_TEXTENCODING_UTF8 ) );
+ }
+ else
+ {
+ buf.append( Package::getDescription() );
+ }
+ m_oldDescription = buf.makeStringAndClear();
+}
+
+
+void BackendImpl::PackageImpl::scanLegacyBundle(
+ t_packagevec & bundle,
+ OUString const & url,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv,
+ bool skip_registration )
+{
+ ::ucbhelper::Content ucbContent(
+ url, xCmdEnv, getMyBackend()->getComponentContext() );
+
+ // check for platform paths:
+ const OUString title( StrTitle::getTitle( ucbContent ) );
+ if (title.endsWithIgnoreAsciiCase( ".plt" ) &&
+ !platform_fits( title.subView( 0, title.getLength() - 4 ) )) {
+ return;
+ }
+ if (title.endsWithIgnoreAsciiCase("skip_registration") )
+ skip_registration = true;
+
+ Sequence<OUString> ar { OUString("Title"), OUString("IsFolder") };
+ Reference<sdbc::XResultSet> xResultSet( ucbContent.createCursor( ar ) );
+ while (xResultSet->next())
+ {
+ checkAborted( abortChannel );
+
+ const Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW );
+ const OUString title_enc( ::rtl::Uri::encode(
+ xRow->getString( 1 /* Title */ ),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 ) );
+ const OUString path( makeURL( url, title_enc ) );
+
+ OUString mediaType;
+ const Reference<deployment::XPackage> xPackage(
+ bindBundleItem( path, OUString() /* detect */, false, OUString(),
+ xCmdEnv, false /* ignore detection errors */ ) );
+ if (xPackage.is()) {
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is())
+ mediaType = xPackageType->getMediaType();
+
+ if (skip_registration &&
+ // xxx todo: additional parsing?
+ mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.uno-component"))
+ continue;
+
+ bundle.push_back( xPackage );
+ }
+
+ if (mediaType.isEmpty() ||
+ // script.xlb, dialog.xlb can be met everywhere:
+ mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.basic-library") ||
+ mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.dialog-library"))
+ {
+ if (xRow->getBoolean( 2 /* IsFolder */ )) { // recurse into folder:
+ scanLegacyBundle(
+ bundle, path, abortChannel, xCmdEnv, skip_registration );
+ }
+ }
+ }
+}
+
+OUString BackendImpl::PackageImpl::getDisplayName()
+{
+ if (m_bRemoved)
+ throw deployment::ExtensionRemovedException();
+
+ OUString sName = getDescriptionInfoset().getLocalizedDisplayName();
+ if (sName.isEmpty())
+ return m_displayName;
+ else
+ return sName;
+}
+
+std::vector<Reference<deployment::XPackage> >
+BackendImpl::PackageImpl::getPackagesFromDb(
+ Reference<ucb::XCommandEnvironment> const & xCmdEnv)
+{
+ std::vector<Reference<deployment::XPackage> > retVector;
+
+ for (auto const& item : m_dbData.items)
+ {
+ Reference<deployment::XPackage> xExtension =
+ bindBundleItem(item.first, item.second, true, m_identifier, xCmdEnv);
+ OSL_ASSERT(xExtension.is());
+ if (xExtension.is())
+ retVector.push_back(xExtension);
+ }
+
+ return retVector;
+}
+
+} // anon namespace
+
+
+Reference<deployment::XPackageRegistry> create(
+ Reference<deployment::XPackageRegistry> const & xRootRegistry,
+ OUString const & context, OUString const & cachePath,
+ Reference<XComponentContext> const & xComponentContext )
+{
+ Sequence<Any> args(cachePath.isEmpty() ? 1 : 3 );
+ auto pArgs = args.getArray();
+ pArgs[ 0 ] <<= context;
+ if (!cachePath.isEmpty()) {
+ pArgs[ 1 ] <<= cachePath;
+ pArgs[ 2 ] <<= false; // readOnly
+ }
+ return new BackendImpl( args, xComponentContext, xRootRegistry );
+}
+
+} // namespace dp_registry
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/script/dp_lib_container.cxx b/desktop/source/deployment/registry/script/dp_lib_container.cxx
new file mode 100644
index 0000000000..3a6f30253f
--- /dev/null
+++ b/desktop/source/deployment/registry/script/dp_lib_container.cxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+#include <strings.hrc>
+#include <dp_resource.h>
+#include <dp_shared.hxx>
+#include <dp_xml.h>
+#include "dp_lib_container.h"
+
+#include <rtl/ustring.hxx>
+#include <ucbhelper/content.hxx>
+#include <xmlscript/xmllib_imexp.hxx>
+
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend::script {
+
+namespace {
+ OUString StrCannotDetermineLibName() { return DpResId(RID_STR_CANNOT_DETERMINE_LIBNAME); }
+}
+
+OUString LibraryContainer::get_libname(
+ OUString const & url,
+ Reference<XCommandEnvironment> const & xCmdEnv,
+ Reference<XComponentContext> const & xContext )
+{
+ ::xmlscript::LibDescriptor import;
+ ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext );
+ xml_parse( ::xmlscript::importLibrary( import ), ucb_content, xContext );
+
+ if (import.aName.isEmpty()) {
+ throw Exception( StrCannotDetermineLibName(),
+ Reference<XInterface>() );
+ }
+ return import.aName;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/script/dp_lib_container.h b/desktop/source/deployment/registry/script/dp_lib_container.h
new file mode 100644
index 0000000000..fbbedf8667
--- /dev/null
+++ b/desktop/source/deployment/registry/script/dp_lib_container.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star {
+ namespace uno {
+ class XComponentContext;
+ }
+ namespace ucb {
+ class XCommandEnvironment;
+ }
+}
+
+
+namespace dp_registry::backend::script {
+
+
+class LibraryContainer
+{
+public:
+ static OUString get_libname(
+ OUString const & url,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ css::uno::Reference<css::uno::XComponentContext> const & xContext );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/script/dp_script.cxx b/desktop/source/deployment/registry/script/dp_script.cxx
new file mode 100644
index 0000000000..47e41a364e
--- /dev/null
+++ b/desktop/source/deployment/registry/script/dp_script.cxx
@@ -0,0 +1,480 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <strings.hrc>
+#include "dp_lib_container.h"
+#include <dp_backend.h>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include <ucbhelper/content.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <svl/inettype.hxx>
+#include <com/sun/star/util/XUpdatable.hpp>
+#include <com/sun/star/script/XLibraryContainer3.hpp>
+#include <memory>
+#include <string_view>
+
+#include "dp_scriptbackenddb.hxx"
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace dp_registry::backend::script {
+namespace {
+
+typedef ::cppu::ImplInheritanceHelper<
+ ::dp_registry::backend::PackageRegistryBackend, util::XUpdatable > t_helper;
+
+class BackendImpl : public t_helper
+{
+ class PackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ const OUString m_scriptURL;
+ const OUString m_dialogURL;
+ OUString m_dialogName;
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ public:
+ PackageImpl(
+ ::rtl::Reference<BackendImpl> const & myBackend,
+ OUString const & url,
+ Reference<XCommandEnvironment> const &xCmdEnv,
+ OUString const & scriptURL, OUString const & dialogURL,
+ bool bRemoved, OUString const & identifier);
+ };
+ friend class PackageImpl;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ void addDataToDb(OUString const & url);
+ bool hasActiveEntry(std::u16string_view url);
+ void revokeEntryFromDb(std::u16string_view url);
+
+ const Reference<deployment::XPackageTypeInfo> m_xBasicLibTypeInfo;
+ const Reference<deployment::XPackageTypeInfo> m_xDialogLibTypeInfo;
+ Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
+ std::unique_ptr<ScriptBackendDb> m_backendDb;
+public:
+ BackendImpl( Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XUpdatable
+ virtual void SAL_CALL update() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+
+};
+
+
+BackendImpl::PackageImpl::PackageImpl(
+ ::rtl::Reference<BackendImpl> const & myBackend,
+ OUString const & url,
+ Reference<XCommandEnvironment> const &xCmdEnv,
+ OUString const & scriptURL, OUString const & dialogURL, bool bRemoved,
+ OUString const & identifier)
+ : Package( myBackend, url,
+ OUString(), OUString(), // will be late-initialized
+ !scriptURL.isEmpty() ? myBackend->m_xBasicLibTypeInfo
+ : myBackend->m_xDialogLibTypeInfo, bRemoved, identifier),
+ m_scriptURL( scriptURL ),
+ m_dialogURL( dialogURL )
+{
+ // name, displayName:
+ if (!dialogURL.isEmpty()) {
+ m_dialogName = LibraryContainer::get_libname(
+ dialogURL, xCmdEnv, myBackend->getComponentContext() );
+ }
+ if (!scriptURL.isEmpty()) {
+ assert(m_name.pData);
+ m_name = LibraryContainer::get_libname(
+ scriptURL, xCmdEnv, myBackend->getComponentContext() );
+ }
+ else
+ m_name = m_dialogName;
+ m_displayName = m_name;
+}
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : t_helper( args, xComponentContext ),
+ m_xBasicLibTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.basic-library",
+ OUString() /* no file filter */,
+ DpResId(RID_STR_BASIC_LIB)
+ ) ),
+ m_xDialogLibTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.dialog-library",
+ OUString() /* no file filter */,
+ DpResId(RID_STR_DIALOG_LIB)
+ ) ),
+ m_typeInfos{ m_xBasicLibTypeInfo, m_xDialogLibTypeInfo }
+{
+ OSL_ASSERT( ! transientMode() );
+
+ if (!transientMode())
+ {
+ OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
+ m_backendDb.reset(
+ new ScriptBackendDb(getComponentContext(), dbFile));
+ }
+
+}
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.script.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+void BackendImpl::addDataToDb(OUString const & url)
+{
+ if (m_backendDb)
+ m_backendDb->addEntry(url);
+}
+
+bool BackendImpl::hasActiveEntry(std::u16string_view url)
+{
+ if (m_backendDb)
+ return m_backendDb->hasActiveEntry(url);
+ return false;
+}
+
+// XUpdatable
+
+void BackendImpl::update()
+{
+ // Nothing to do here after fixing i70283!?
+}
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return m_typeInfos;
+}
+void BackendImpl::revokeEntryFromDb(std::u16string_view url)
+{
+ if (m_backendDb)
+ m_backendDb->revokeEntry(url);
+}
+
+void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
+{
+ if (m_backendDb)
+ m_backendDb->removeEntry(url);
+}
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ OUString mediaType( mediaType_ );
+ if (mediaType.isEmpty())
+ {
+ // detect media-type:
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, url, xCmdEnv ) &&
+ ucbContent.isFolder())
+ {
+ // probe for script.xlb:
+ if (create_ucb_content(
+ nullptr, makeURL( url, "script.xlb" ),
+ xCmdEnv, false /* no throw */ ))
+ mediaType = "application/vnd.sun.star.basic-library";
+ // probe for dialog.xlb:
+ else if (create_ucb_content(
+ nullptr, makeURL( url, "dialog.xlb" ),
+ xCmdEnv, false /* no throw */ ))
+ mediaType = "application/vnd.sun.star.dialog-library";
+ }
+ if (mediaType.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ OUString dialogURL( makeURL( url, "dialog.xlb" ) );
+ if (! create_ucb_content(
+ nullptr, dialogURL, xCmdEnv, false /* no throw */ )) {
+ dialogURL.clear();
+ }
+
+ if (subType.equalsIgnoreAsciiCase("vnd.sun.star.basic-library"))
+ {
+ OUString scriptURL( makeURL( url, "script.xlb"));
+ if (! create_ucb_content(
+ nullptr, scriptURL, xCmdEnv, false /* no throw */ )) {
+ scriptURL.clear();
+ }
+
+ return new PackageImpl(
+ this, url, xCmdEnv, scriptURL,
+ dialogURL, bRemoved, identifier);
+ }
+ else if (subType.equalsIgnoreAsciiCase(
+ "vnd.sun.star.dialog-library")) {
+ return new PackageImpl(
+ this, url, xCmdEnv,
+ OUString() /* no script lib */,
+ dialogURL,
+ bRemoved, identifier);
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+
+// Package
+BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException(
+ "Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::PackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard & /* guard */,
+ ::rtl::Reference<AbortChannel> const & /* abortChannel */,
+ Reference<XCommandEnvironment> const & /* xCmdEnv */ )
+{
+ BackendImpl * that = getMyBackend();
+ Reference< deployment::XPackage > xThisPackage( this );
+
+ bool registered = that->hasActiveEntry(getURL());
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true /* IsPresent */,
+ beans::Ambiguous<sal_Bool>( registered, false /* IsAmbiguous */ ) );
+}
+
+void
+lcl_maybeRemoveScript(
+ bool const bExists,
+ OUString const& rName,
+ std::u16string_view rScriptURL,
+ Reference<css::script::XLibraryContainer3> const& xScriptLibs)
+{
+ if (bExists && xScriptLibs.is() && xScriptLibs->hasByName(rName))
+ {
+ const OUString sScriptUrl = xScriptLibs->getOriginalLibraryLinkURL(rName);
+ if (sScriptUrl == rScriptURL)
+ xScriptLibs->removeLibrary(rName);
+ }
+}
+
+bool
+lcl_maybeAddScript(
+ bool const bExists,
+ OUString const& rName,
+ OUString const& rScriptURL,
+ Reference<css::script::XLibraryContainer3> const& xScriptLibs)
+{
+ if (!bExists || !xScriptLibs)
+ return false;
+
+ bool bCanAdd = true;
+ if (xScriptLibs->hasByName(rName))
+ {
+ const OUString sOriginalUrl = xScriptLibs->getOriginalLibraryLinkURL(rName);
+ //We assume here that library names in extensions are unique, which may not be the case
+ //ToDo: If the script exist in another extension, then both extensions must have the
+ //same id
+ if (sOriginalUrl.match("vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE")
+ || sOriginalUrl.match("vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE")
+ || sOriginalUrl.match("vnd.sun.star.expand:$BUNDLED_EXTENSIONS")
+ || sOriginalUrl.match("$(INST)/share/basic/Access2Base/"))
+ {
+ xScriptLibs->removeLibrary(rName);
+ bCanAdd = true;
+ }
+ else
+ {
+ bCanAdd = false;
+ }
+ }
+
+ if (bCanAdd)
+ {
+ xScriptLibs->createLibraryLink(rName, rScriptURL, false);
+ return xScriptLibs->hasByName(rName);
+ }
+
+ return false;
+}
+
+void BackendImpl::PackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard & /* guard */,
+ bool doRegisterPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & /* abortChannel */,
+ Reference<XCommandEnvironment> const & /* xCmdEnv */ )
+{
+ BackendImpl * that = getMyBackend();
+
+ Reference< deployment::XPackage > xThisPackage( this );
+ Reference<XComponentContext> const & xComponentContext = that->getComponentContext();
+
+ bool bScript = !m_scriptURL.isEmpty();
+ Reference<css::script::XLibraryContainer3> xScriptLibs;
+
+ bool bDialog = !m_dialogURL.isEmpty();
+ Reference<css::script::XLibraryContainer3> xDialogLibs;
+
+ bool bRunning = !startup && office_is_running();
+ if( bRunning )
+ {
+ if( bScript )
+ {
+ xScriptLibs.set(
+ xComponentContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.script.ApplicationScriptLibraryContainer",
+ xComponentContext ), UNO_QUERY_THROW );
+ }
+
+ if( bDialog )
+ {
+ xDialogLibs.set(
+ xComponentContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.script.ApplicationDialogLibraryContainer",
+ xComponentContext ), UNO_QUERY_THROW );
+ }
+ }
+ bool bRegistered = getMyBackend()->hasActiveEntry(getURL());
+ if( !doRegisterPackage )
+ {
+ //We cannot just call removeLibrary(name) because this could remove a
+ //script which was added by an extension in a different repository. For
+ //example, extension foo is contained in the bundled repository and then
+ //the user adds it to the user repository. The extension manager will
+ //then register the new script and revoke the script from the bundled
+ //extension. removeLibrary(name) would now remove the script from the
+ //user repository. That is, the script of the newly added user extension does
+ //not work anymore. Therefore we must check if the currently active
+ //script comes in fact from the currently processed extension.
+
+ if (bRegistered)
+ {
+ //we also prevent and live deployment at startup
+ if (!isRemoved() && !startup)
+ {
+ lcl_maybeRemoveScript(bScript, m_name, m_scriptURL, xScriptLibs);
+ lcl_maybeRemoveScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs);
+ }
+ getMyBackend()->revokeEntryFromDb(getURL());
+ return;
+ }
+ }
+ if (bRegistered)
+ return; // Already registered
+
+ // Update LibraryContainer
+ bool bScriptSuccess = false;
+ bool bDialogSuccess = false;
+ if (!startup)
+ {
+ //If there is a bundled extension, and the user installs the same extension
+ //then the script from the bundled extension must be removed. If this does not work
+ //then live deployment does not work for scripts.
+ bScriptSuccess = lcl_maybeAddScript(bScript, m_name, m_scriptURL, xScriptLibs);
+ bDialogSuccess = lcl_maybeAddScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs);
+ }
+ bool bSuccess = bScript || bDialog; // Something must have happened
+ if( bRunning )
+ if( (bScript && !bScriptSuccess) || (bDialog && !bDialogSuccess) )
+ bSuccess = false;
+
+ if (bSuccess)
+ getMyBackend()->addDataToDb(getURL());
+}
+
+} // anon namespace
+
+} // namespace dp_registry::backend::script
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::script::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx
new file mode 100644
index 0000000000..476f43953a
--- /dev/null
+++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "dp_scriptbackenddb.hxx"
+
+
+using namespace ::com::sun::star::uno;
+
+constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/script-registry/2010";
+constexpr OUStringLiteral NS_PREFIX = u"script";
+constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"script-backend-db";
+constexpr OUStringLiteral KEY_ELEMENT_NAME = u"script";
+
+namespace dp_registry::backend::script {
+
+ScriptBackendDb::ScriptBackendDb(
+ Reference<XComponentContext> const & xContext,
+ OUString const & url):RegisteredDb(xContext, url)
+{
+
+}
+
+OUString ScriptBackendDb::getDbNSName()
+{
+ return EXTENSION_REG_NS;
+}
+
+OUString ScriptBackendDb::getNSPrefix()
+{
+ return NS_PREFIX;
+}
+
+OUString ScriptBackendDb::getRootElementName()
+{
+ return ROOT_ELEMENT_NAME;
+}
+
+OUString ScriptBackendDb::getKeyElementName()
+{
+ return KEY_ELEMENT_NAME;
+}
+
+
+} // namespace dp_registry::backend::script
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx
new file mode 100644
index 0000000000..21d4b1f6b3
--- /dev/null
+++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <dp_backenddb.hxx>
+#include <optional>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+namespace dp_registry::backend::script {
+
+/* The XML file stores the extensions which are currently registered.
+ They will be removed when they are revoked.
+ */
+class ScriptBackendDb: public dp_registry::backend::RegisteredDb
+{
+protected:
+ virtual OUString getDbNSName() override;
+
+ virtual OUString getNSPrefix() override;
+
+ virtual OUString getRootElementName() override;
+
+ virtual OUString getKeyElementName() override;
+
+
+public:
+
+ ScriptBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext,
+ OUString const & url);
+};
+
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx
new file mode 100644
index 0000000000..530924a078
--- /dev/null
+++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <dp_misc.h>
+#include "dp_parceldesc.hxx"
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+namespace dp_registry::backend::sfwk
+{
+
+
+// XDocumentHandler
+void SAL_CALL
+ParcelDescDocHandler::startDocument()
+{
+ m_bIsParsed = false;
+}
+
+void SAL_CALL
+ParcelDescDocHandler::endDocument()
+{
+ m_bIsParsed = true;
+}
+
+void SAL_CALL
+ParcelDescDocHandler::characters( const OUString & )
+{
+}
+
+void SAL_CALL
+ParcelDescDocHandler::ignorableWhitespace( const OUString & )
+{
+}
+
+void SAL_CALL
+ParcelDescDocHandler::processingInstruction(
+ const OUString &, const OUString & )
+{
+}
+
+void SAL_CALL
+ParcelDescDocHandler::setDocumentLocator(
+ const Reference< xml::sax::XLocator >& )
+{
+}
+
+void SAL_CALL
+ParcelDescDocHandler::startElement( const OUString& aName,
+ const Reference< xml::sax::XAttributeList > & xAttribs )
+{
+
+ dp_misc::TRACE("ParcelDescDocHandler::startElement() for " +
+ aName + "\n");
+ if ( !skipIndex )
+ {
+ if ( aName == "parcel" )
+ {
+ m_sLang = xAttribs->getValueByName( "language" );
+ }
+ ++skipIndex;
+ }
+ else
+ {
+ dp_misc::TRACE("ParcelDescDocHandler::startElement() skipping for "
+ + aName + "\n");
+ }
+
+}
+
+void SAL_CALL ParcelDescDocHandler::endElement( const OUString & aName )
+{
+ if ( skipIndex )
+ {
+ --skipIndex;
+ dp_misc::TRACE("ParcelDescDocHandler::endElement() skipping for "
+ + aName + "\n");
+ }
+}
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx
new file mode 100644
index 0000000000..6b5bde8bdd
--- /dev/null
+++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/xml/sax/XAttributeList.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+
+namespace dp_registry::backend::sfwk
+{
+
+class ParcelDescDocHandler : public ::cppu::WeakImplHelper< css::xml::sax::XDocumentHandler >
+{
+private:
+ bool m_bIsParsed;
+ OUString m_sLang;
+ sal_Int32 skipIndex;
+public:
+ ParcelDescDocHandler():m_bIsParsed( false ), skipIndex( 0 ){}
+ const OUString& getParcelLanguage() const { return m_sLang; }
+ bool isParsed() const { return m_bIsParsed; }
+ // XDocumentHandler
+ virtual void SAL_CALL startDocument() override;
+
+ virtual void SAL_CALL endDocument() override;
+
+ virtual void SAL_CALL startElement( const OUString& aName,
+ const css::uno::Reference< css::xml::sax::XAttributeList > & xAttribs ) override;
+
+ virtual void SAL_CALL endElement( const OUString & aName ) override;
+
+ virtual void SAL_CALL characters( const OUString & aChars ) override;
+
+ virtual void SAL_CALL ignorableWhitespace( const OUString & aWhitespaces ) override;
+
+ virtual void SAL_CALL processingInstruction(
+ const OUString & aTarget, const OUString & aData ) override;
+
+ virtual void SAL_CALL setDocumentLocator(
+ const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override;
+};
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx
new file mode 100644
index 0000000000..b617d7fa4e
--- /dev/null
+++ b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <strings.hrc>
+#include <dp_backend.h>
+#include <dp_misc.h>
+#include <dp_ucb.h>
+#include "dp_parceldesc.hxx"
+#include <rtl/uri.hxx>
+#include <ucbhelper/content.hxx>
+#include <svl/inettype.hxx>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <utility>
+
+
+using namespace ::dp_misc;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::script;
+
+
+namespace dp_registry::backend::sfwk
+{
+
+namespace {
+
+class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
+{
+ class PackageImpl : public ::dp_registry::backend::Package
+ {
+ BackendImpl * getMyBackend() const;
+
+ Reference< container::XNameContainer > m_xNameCntrPkgHandler;
+ OUString m_descr;
+
+ void initPackageHandler();
+
+ // Package
+ virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
+ ::osl::ResettableMutexGuard & guard,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+ virtual void processPackage_(
+ ::osl::ResettableMutexGuard & guard,
+ bool registerPackage,
+ bool startup,
+ ::rtl::Reference<AbortChannel> const & abortChannel,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ public:
+ PackageImpl(
+ ::rtl::Reference<BackendImpl> const & myBackend,
+ OUString const & url, OUString libType, bool bRemoved,
+ OUString const & identifier);
+ // XPackage
+ virtual OUString SAL_CALL getDescription() override;
+ virtual OUString SAL_CALL getLicenseText() override;
+ };
+ friend class PackageImpl;
+
+ // PackageRegistryBackend
+ virtual Reference<deployment::XPackage> bindPackage_(
+ OUString const & url, OUString const & mediaType,
+ bool bRemoved, OUString const & identifier,
+ Reference<XCommandEnvironment> const & xCmdEnv ) override;
+
+ const Reference<deployment::XPackageTypeInfo> m_xTypeInfo;
+
+
+public:
+ BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext );
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XPackageRegistry
+ virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
+ getSupportedPackageTypes() override;
+ virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
+};
+
+}
+
+BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
+{
+ BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
+ if (nullptr == pBackend)
+ {
+ //May throw a DisposedException
+ check();
+ //We should never get here...
+ throw RuntimeException("Failed to get the BackendImpl",
+ static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
+ }
+ return pBackend;
+}
+
+OUString BackendImpl::PackageImpl::getDescription()
+{
+ if (m_descr.isEmpty())
+ return Package::getDescription();
+ else
+ return m_descr;
+}
+
+OUString BackendImpl::PackageImpl::getLicenseText()
+{
+ return Package::getDescription();
+}
+
+BackendImpl::PackageImpl::PackageImpl(
+ ::rtl::Reference<BackendImpl> const & myBackend,
+ OUString const & url, OUString libType, bool bRemoved,
+ OUString const & identifier)
+ : Package( myBackend, url, OUString(), OUString(),
+ myBackend->m_xTypeInfo, bRemoved, identifier),
+ m_descr(std::move(libType))
+{
+ initPackageHandler();
+
+ sal_Int32 segmEnd = url.getLength();
+ if ( url.endsWith("/") )
+ --segmEnd;
+ sal_Int32 segmStart = url.lastIndexOf( '/', segmEnd ) + 1;
+ if (segmStart < 0)
+ segmStart = 0;
+ // name and display name default the same:
+ m_displayName = ::rtl::Uri::decode(
+ url.copy( segmStart, segmEnd - segmStart ),
+ rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ m_name = m_displayName;
+
+ dp_misc::TRACE("PackageImpl displayName is " + m_displayName);
+}
+
+
+BackendImpl::BackendImpl(
+ Sequence<Any> const & args,
+ Reference<XComponentContext> const & xComponentContext )
+ : PackageRegistryBackend( args, xComponentContext ),
+ m_xTypeInfo( new Package::TypeInfo(
+ "application/vnd.sun.star.framework-script",
+ OUString() /* no file filter */,
+ "Scripting Framework Script Library"
+ ) )
+{
+}
+
+
+// XServiceInfo
+OUString BackendImpl::getImplementationName()
+{
+ return "com.sun.star.comp.deployment.sfwk.PackageRegistryBackend";
+}
+
+sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
+{
+ return { BACKEND_SERVICE_NAME };
+}
+
+// XPackageRegistry
+
+Sequence< Reference<deployment::XPackageTypeInfo> >
+BackendImpl::getSupportedPackageTypes()
+{
+ return Sequence< Reference<deployment::XPackageTypeInfo> >(&m_xTypeInfo, 1);
+}
+
+void BackendImpl::packageRemoved(OUString const & /*url*/, OUString const & /*mediaType*/)
+{
+}
+
+// PackageRegistryBackend
+
+Reference<deployment::XPackage> BackendImpl::bindPackage_(
+ OUString const & url, OUString const & mediaType_, bool bRemoved,
+ OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
+{
+ OUString mediaType( mediaType_ );
+ if (mediaType.isEmpty())
+ {
+ // detect media-type:
+ ::ucbhelper::Content ucbContent;
+ if (create_ucb_content( &ucbContent, url, xCmdEnv ) &&
+ ucbContent.isFolder())
+ {
+ // probe for parcel-descriptor.xml:
+ if (create_ucb_content(
+ nullptr, makeURL( url, "parcel-descriptor.xml" ),
+ xCmdEnv, false /* no throw */ ))
+ {
+ mediaType = "application/vnd.sun.star.framework-script";
+ }
+ }
+ if (mediaType.isEmpty())
+ throw lang::IllegalArgumentException(
+ StrCannotDetectMediaType() + url,
+ static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
+ }
+
+ OUString type, subType;
+ INetContentTypeParameterList params;
+ if (INetContentTypes::parse( mediaType, type, subType, &params ))
+ {
+ if (type.equalsIgnoreAsciiCase("application"))
+ {
+ if (subType.equalsIgnoreAsciiCase("vnd.sun.star.framework-script"))
+ {
+ OUString lang = "Script";
+ OUString sParcelDescURL = makeURL(
+ url, "parcel-descriptor.xml" );
+
+ ::ucbhelper::Content ucb_content;
+
+ if (create_ucb_content( &ucb_content, sParcelDescURL,
+ xCmdEnv, false /* no throw */ ))
+ {
+ rtl::Reference<ParcelDescDocHandler> pHandler =
+ new ParcelDescDocHandler();
+
+ Reference<XComponentContext>
+ xContext( getComponentContext() );
+
+ Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(xContext);
+
+ xParser->setDocumentHandler( pHandler );
+ xml::sax::InputSource source;
+ source.aInputStream = ucb_content.openStream();
+ source.sSystemId = ucb_content.getURL();
+ xParser->parseStream( source );
+
+ if ( pHandler->isParsed() )
+ {
+ lang = pHandler->getParcelLanguage();
+ }
+ }
+
+ OUString sfwkLibType = DpResId( RID_STR_SFWK_LIB );
+ // replace %MACRONAME placeholder with language name
+ OUString MACRONAME( "%MACROLANG" );
+ sal_Int32 startOfReplace = sfwkLibType.indexOf( MACRONAME );
+ sal_Int32 charsToReplace = MACRONAME.getLength();
+ sfwkLibType = sfwkLibType.replaceAt( startOfReplace, charsToReplace, lang );
+ dp_misc::TRACE("******************************\n");
+ dp_misc::TRACE(" BackEnd detected lang = " + lang + "\n");
+ dp_misc::TRACE(" for url " + sParcelDescURL + "\n");
+ dp_misc::TRACE("******************************\n");
+ return new PackageImpl( this, url, sfwkLibType, bRemoved, identifier);
+ }
+ }
+ }
+ throw lang::IllegalArgumentException(
+ StrUnsupportedMediaType() + mediaType,
+ static_cast<OWeakObject *>(this),
+ static_cast<sal_Int16>(-1) );
+}
+
+
+void BackendImpl::PackageImpl:: initPackageHandler()
+{
+ if (m_xNameCntrPkgHandler.is())
+ return;
+
+ BackendImpl * that = getMyBackend();
+ Any aContext;
+
+ if ( that->m_eContext == Context::User )
+ {
+ aContext <<= OUString("user");
+ }
+ else if ( that->m_eContext == Context::Shared )
+ {
+ aContext <<= OUString("share");
+ }
+ else if ( that->m_eContext == Context::Bundled )
+ {
+ aContext <<= OUString("bundled");
+ }
+ else
+ {
+ OSL_ASSERT( false );
+ // NOT supported at the moment // TODO
+ }
+
+ Reference< provider::XScriptProviderFactory > xFac =
+ provider::theMasterScriptProviderFactory::get( that->getComponentContext() );
+
+ Reference< container::XNameContainer > xName( xFac->createScriptProvider( aContext ), UNO_QUERY );
+ if ( xName.is() )
+ {
+ m_xNameCntrPkgHandler.set( xName );
+ }
+ // TODO what happens if above fails??
+}
+
+// Package
+
+beans::Optional< beans::Ambiguous<sal_Bool> >
+BackendImpl::PackageImpl::isRegistered_(
+ ::osl::ResettableMutexGuard &,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ return beans::Optional< beans::Ambiguous<sal_Bool> >(
+ true /* IsPresent */,
+ beans::Ambiguous<sal_Bool>(
+ m_xNameCntrPkgHandler.is() && m_xNameCntrPkgHandler->hasByName(
+ m_url ),
+ false /* IsAmbiguous */ ) );
+}
+
+
+void BackendImpl::PackageImpl::processPackage_(
+ ::osl::ResettableMutexGuard &,
+ bool doRegisterPackage,
+ bool /* startup */,
+ ::rtl::Reference<AbortChannel> const &,
+ Reference<XCommandEnvironment> const & )
+{
+ if ( !m_xNameCntrPkgHandler.is() )
+ {
+ dp_misc::TRACE("no package handler!!!!\n");
+ throw RuntimeException( "No package Handler " );
+ }
+
+ if (doRegisterPackage)
+ {
+ // will throw if it fails
+ m_xNameCntrPkgHandler->insertByName( m_url, Any( Reference< XPackage >(this) ) );
+
+ }
+ else // revokePackage()
+ {
+ m_xNameCntrPkgHandler->removeByName( m_url );
+ }
+}
+
+} // namespace dp_registry::backend::sfwk
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new dp_registry::backend::sfwk::BackendImpl(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/inc/helpids.h b/desktop/source/inc/helpids.h
new file mode 100644
index 0000000000..b7d7c43597
--- /dev/null
+++ b/desktop/source/inc/helpids.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+inline constexpr OUString HID_EXTENSION_MANAGER_LISTBOX_ENABLE = u"DESKTOP_HID_EXTENSION_MANAGER_LISTBOX_ENABLE"_ustr;
+inline constexpr OUString HID_EXTENSION_MANAGER_LISTBOX_DISABLE = u"DESKTOP_HID_EXTENSION_MANAGER_LISTBOX_DISABLE"_ustr;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx
new file mode 100644
index 0000000000..ab12a160b9
--- /dev/null
+++ b/desktop/source/lib/init.cxx
@@ -0,0 +1,8129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sfx2/lokhelper.hxx>
+#include <sal/types.h>
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdpagv.hxx>
+#include <config_buildconfig.h>
+#include <config_cairo_rgba.h>
+#include <config_features.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef IOS
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unicode/udata.h>
+#include <unicode/ucnv.h>
+#include <premac.h>
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+#include <postmac.h>
+#endif
+
+#undef HAVE_MALLOC_TRIM
+
+#ifdef LINUX
+#include <fcntl.h>
+#if defined __GLIBC__
+# include <malloc.h>
+# define HAVE_MALLOC_TRIM
+#endif
+#endif
+
+#ifdef ANDROID
+#include <osl/detail/android-bootstrap.h>
+#endif
+
+#ifdef EMSCRIPTEN
+#include <osl/detail/emscripten-bootstrap.h>
+#endif
+
+#include <algorithm>
+#include <memory>
+#include <iostream>
+#include <string_view>
+#include <queue>
+
+#include <boost/property_tree/json_parser.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <LibreOfficeKit/LibreOfficeKit.h>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/errinf.hxx>
+#include <vcl/lok.hxx>
+#include <o3tl/any.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/uri.hxx>
+#include <svl/zforlist.hxx>
+#include <linguistic/misc.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/profilezone.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/threadpool.hxx>
+#include <comphelper/types.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+
+#include <com/sun/star/connection/XConnection.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/document/XDocumentLanguages.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/DispatchResultEvent.hpp>
+#include <com/sun/star/frame/DispatchResultState.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/frame/XDispatchResultListener.hpp>
+#include <com/sun/star/frame/XSynchronousDispatch.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XTransferable2.hpp>
+#include <com/sun/star/text/TextContentAnchorType.hpp>
+#include <com/sun/star/document/XRedlinesSupplier.hpp>
+#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
+#include <com/sun/star/bridge/BridgeFactory.hpp>
+#include <com/sun/star/bridge/XBridgeFactory.hpp>
+#include <com/sun/star/bridge/XBridge.hpp>
+#include <com/sun/star/uno/XNamingService.hpp>
+
+#include <com/sun/star/xml/crypto/SEInitializer.hpp>
+#include <com/sun/star/xml/crypto/XSEInitializer.hpp>
+#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp>
+#include <com/sun/star/xml/crypto/XCertificateCreator.hpp>
+#include <com/sun/star/security/XCertificate.hpp>
+
+#include <com/sun/star/linguistic2/DictionaryList.hpp>
+#include <com/sun/star/linguistic2/LanguageGuessing.hpp>
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+#include <com/sun/star/linguistic2/XSpellChecker.hpp>
+#include <com/sun/star/linguistic2/XProofreader.hpp>
+#include <com/sun/star/i18n/LocaleCalendar2.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+
+#include <editeng/flstitem.hxx>
+#ifdef IOS
+#include <sfx2/app.hxx>
+#endif
+#include <sfx2/objsh.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/msgpool.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/lokcomponenthelpers.hxx>
+#include <sfx2/DocumentSigner.hxx>
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <svl/numformat.hxx>
+#include <svx/dialmgr.hxx>
+#include <svx/strings.hrc>
+#include <svx/svdview.hxx>
+#include <svx/svxids.hrc>
+#include <svx/ucsubset.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/GestureEventPan.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/resmgr.hxx>
+#include <tools/fract.hxx>
+#include <tools/json_writer.hxx>
+#include <svtools/ctrltool.hxx>
+#include <svtools/langtab.hxx>
+#include <vcl/fontcharmap.hxx>
+#ifdef IOS
+#include <vcl/sysdata.hxx>
+#endif
+#include <vcl/virdev.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/ITiledRenderable.hxx>
+#include <vcl/dialoghelper.hxx>
+#ifdef _WIN32
+#include <vcl/BitmapReadAccess.hxx>
+#endif
+#include <unicode/uchar.h>
+#include <unotools/securityoptions.hxx>
+#include <unotools/confignode.hxx>
+#include <unotools/syslocaleoptions.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/pathoptions.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/streamwrap.hxx>
+#include <osl/module.hxx>
+#include <comphelper/sequence.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <svl/undo.hxx>
+#include <unotools/datetime.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/abstdlg.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/jsdialog/executor.hxx>
+
+// Needed for getUndoManager()
+#include <com/sun/star/document/XUndoManager.hpp>
+#include <com/sun/star/document/XUndoManagerSupplier.hpp>
+#include <com/sun/star/document/XLinkTargetSupplier.hpp>
+#include <editeng/sizeitem.hxx>
+#include <svx/rulritem.hxx>
+#include <svx/pageitem.hxx>
+
+#include <app.hxx>
+
+#include "../app/cmdlineargs.hxx"
+// We also need to hackily be able to start the main libreoffice thread:
+#include "../app/sofficemain.h"
+#include "../app/officeipcthread.hxx"
+#include <lib/init.hxx>
+
+#include "lokinteractionhandler.hxx"
+#include "lokclipboard.hxx"
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Office/Impress.hxx>
+#include <officecfg/Office/Linguistic.hxx>
+#include <officecfg/Office/UI/ToolbarMode.hxx>
+#include <unotools/optionsdlg.hxx>
+#include <svl/ctloptions.hxx>
+#include <svtools/accessibilityoptions.hxx>
+#include <svtools/colorcfg.hxx>
+#include <svtools/miscopt.hxx>
+#include <svtools/slidesorterbaropt.hxx>
+#include <unotools/cmdoptions.hxx>
+#include <unotools/compatibility.hxx>
+#include <unotools/fltrcfg.hxx>
+#include <unotools/lingucfg.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <unotools/searchopt.hxx>
+#include <unotools/useroptions.hxx>
+#include <unotools/viewoptions.hxx>
+#include <vcl/settings.hxx>
+
+#include <officecfg/Setup.hxx>
+#include <com/sun/star/ui/XAcceleratorConfiguration.hpp>
+#include <svtools/acceleratorexecute.hxx>
+
+using namespace css;
+using namespace vcl;
+using namespace desktop;
+using namespace utl;
+using namespace bridge;
+using namespace uno;
+using namespace lang;
+
+using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool;
+
+static LibLibreOffice_Impl *gImpl = nullptr;
+static bool lok_preinit_2_called = false;
+static std::weak_ptr< LibreOfficeKitClass > gOfficeClass;
+static std::weak_ptr< LibreOfficeKitDocumentClass > gDocumentClass;
+
+static void SetLastExceptionMsg(const OUString& s = OUString())
+{
+ SAL_WARN_IF(!s.isEmpty(), "lok", "lok exception '" + s + "'");
+ if (gImpl)
+ gImpl->maLastExceptionMsg = s;
+}
+
+namespace {
+
+struct ExtensionMap
+{
+ std::string_view extn;
+ OUString filterName;
+};
+
+class TraceEventDumper : public AutoTimer
+{
+ static const int dumpTimeoutMS = 5000;
+
+public:
+ TraceEventDumper() : AutoTimer( "Trace Event dumper" )
+ {
+ SetTimeout(dumpTimeoutMS);
+ Start();
+ }
+
+ virtual void Invoke() override
+ {
+ flushRecordings();
+ }
+
+ static void flushRecordings()
+ {
+ const css::uno::Sequence<OUString> aEvents =
+ comphelper::TraceEvent::getRecordingAndClear();
+ OStringBuffer aOutput;
+ for (const auto &s : aEvents)
+ {
+ aOutput.append(OUStringToOString(s, RTL_TEXTENCODING_UTF8)
+ + "\n");
+ }
+ if (aOutput.getLength() > 0)
+ {
+ OString aChunk = aOutput.makeStringAndClear();
+ if (gImpl && gImpl->mpCallback)
+ gImpl->mpCallback(LOK_CALLBACK_PROFILE_FRAME, aChunk.getStr(), gImpl->mpCallbackData);
+ }
+ }
+};
+
+TraceEventDumper *traceEventDumper = nullptr;
+
+constexpr ExtensionMap aWriterExtensionMap[] =
+{
+ { "doc", u"MS Word 97"_ustr },
+ { "docm", u"MS Word 2007 XML VBA"_ustr },
+ { "docx", u"MS Word 2007 XML"_ustr },
+ { "fodt", u"OpenDocument Text Flat XML"_ustr },
+ { "html", u"HTML (StarWriter)"_ustr },
+ { "odt", u"writer8"_ustr },
+ { "ott", u"writer8_template"_ustr },
+ { "pdf", u"writer_pdf_Export"_ustr },
+ { "epub", u"EPUB"_ustr },
+ { "rtf", u"Rich Text Format"_ustr },
+ { "txt", u"Text"_ustr },
+ { "xhtml", u"XHTML Writer File"_ustr },
+ { "png", u"writer_png_Export"_ustr },
+ { "xml", u"writer_indexing_export"_ustr },
+};
+
+constexpr ExtensionMap aCalcExtensionMap[] =
+{
+ { "csv", u"Text - txt - csv (StarCalc)"_ustr },
+ { "fods", u"OpenDocument Spreadsheet Flat XML"_ustr },
+ { "html", u"HTML (StarCalc)"_ustr },
+ { "ods", u"calc8"_ustr },
+ { "ots", u"calc8_template"_ustr },
+ { "pdf", u"calc_pdf_Export"_ustr },
+ { "xhtml", u"XHTML Calc File"_ustr },
+ { "xls", u"MS Excel 97"_ustr },
+ { "xlsm", u"Calc MS Excel 2007 VBA XML"_ustr },
+ { "xlsx", u"Calc MS Excel 2007 XML"_ustr },
+ { "png", u"calc_png_Export"_ustr },
+};
+
+constexpr ExtensionMap aImpressExtensionMap[] =
+{
+ { "fodp", u"OpenDocument Presentation Flat XML"_ustr },
+ { "html", u"impress_html_Export"_ustr },
+ { "odg", u"impress8_draw"_ustr },
+ { "odp", u"impress8"_ustr },
+ { "otp", u"impress8_template"_ustr },
+ { "pdf", u"impress_pdf_Export"_ustr },
+ { "potm", u"Impress MS PowerPoint 2007 XML Template"_ustr },
+ { "pot", u"MS PowerPoint 97 Vorlage"_ustr },
+ { "pptm", u"Impress MS PowerPoint 2007 XML VBA"_ustr },
+ { "pptx", u"Impress MS PowerPoint 2007 XML"_ustr },
+ { "pps", u"MS PowerPoint 97 Autoplay"_ustr },
+ { "ppt", u"MS PowerPoint 97"_ustr },
+ { "svg", u"impress_svg_Export"_ustr },
+ { "xhtml", u"XHTML Impress File"_ustr },
+ { "png", u"impress_png_Export"_ustr },
+};
+
+constexpr ExtensionMap aDrawExtensionMap[] =
+{
+ { "fodg", u"draw_ODG_FlatXML"_ustr },
+ { "html", u"draw_html_Export"_ustr },
+ { "odg", u"draw8"_ustr },
+ { "pdf", u"draw_pdf_Export"_ustr },
+ { "svg", u"draw_svg_Export"_ustr },
+ { "xhtml", u"XHTML Draw File"_ustr },
+ { "png", u"draw_png_Export"_ustr },
+};
+
+OUString getUString(const char* pString)
+{
+ if (pString == nullptr)
+ return OUString();
+
+ return OStringToOUString(pString, RTL_TEXTENCODING_UTF8);
+}
+
+// Tolerate embedded \0s etc.
+char *convertOString(const OString &rStr)
+{
+ char* pMemory = static_cast<char*>(malloc(rStr.getLength() + 1));
+ assert(pMemory); // don't tolerate failed allocations.
+ memcpy(pMemory, rStr.getStr(), rStr.getLength() + 1);
+ return pMemory;
+}
+
+char *convertOUString(std::u16string_view aStr)
+{
+ return convertOString(OUStringToOString(aStr, RTL_TEXTENCODING_UTF8));
+}
+
+/// Try to convert a relative URL to an absolute one, unless it already looks like a URL.
+OUString getAbsoluteURL(const char* pURL)
+{
+ OUString aURL(getUString(pURL));
+ if (aURL.isEmpty())
+ return aURL;
+
+ // convert relative paths to absolute ones
+ OUString aWorkingDir;
+ osl_getProcessWorkingDir(&aWorkingDir.pData);
+ if (!aWorkingDir.endsWith("/"))
+ aWorkingDir += "/";
+
+ try
+ {
+ return rtl::Uri::convertRelToAbs(aWorkingDir, aURL);
+ }
+ catch (const rtl::MalformedUriException &)
+ {
+ }
+
+ return OUString();
+}
+
+} // unnamed namespace
+
+std::vector<beans::PropertyValue> desktop::jsonToPropertyValuesVector(const char* pJSON)
+{
+ std::vector<beans::PropertyValue> aArguments;
+ if (pJSON && pJSON[0] != '\0')
+ {
+ aArguments = comphelper::JsonToPropertyValues(pJSON);
+ }
+ return aArguments;
+}
+
+static void extractLinks(const uno::Reference< container::XNameAccess >& xLinks, bool subcontent, tools::JsonWriter& aJson)
+{
+ for (const OUString& aLink : xLinks->getElementNames())
+ {
+ uno::Any aAny;
+
+ try
+ {
+ aAny = xLinks->getByName( aLink );
+ }
+ catch(const uno::Exception&)
+ {
+ // if the name of the target was invalid (like empty headings)
+ // no object can be provided
+ continue;
+ }
+
+ uno::Reference< beans::XPropertySet > xTarget;
+ if( aAny >>= xTarget )
+ {
+ try
+ {
+ // get name to display
+ aAny = xTarget->getPropertyValue(u"LinkDisplayName"_ustr);
+ OUString aStrDisplayname;
+ aAny >>= aStrDisplayname;
+
+ if (subcontent)
+ {
+ aJson.put(aStrDisplayname, aLink);
+ }
+ else
+ {
+ uno::Reference<lang::XServiceInfo> xSI(xTarget, uno::UNO_QUERY_THROW);
+ if (xSI->supportsService(u"com.sun.star.document.LinkTarget"_ustr))
+ {
+ aJson.put(aStrDisplayname, aLink);
+ continue;
+ }
+ else
+ {
+ auto aNode = aJson.startNode(
+ OUStringToOString(aStrDisplayname, RTL_TEXTENCODING_UTF8));
+
+ uno::Reference< document::XLinkTargetSupplier > xLTS( xTarget, uno::UNO_QUERY );
+ if( xLTS.is() )
+ extractLinks(xLTS->getLinks(), true, aJson);
+ }
+ }
+ }
+ catch(...)
+ {
+ SAL_WARN("lok", "extractLinks: Exception");
+ }
+ }
+ }
+}
+
+static void unoAnyToJson(tools::JsonWriter& rJson, std::string_view pNodeName, const uno::Any& anyItem)
+{
+ auto aNode = rJson.startNode(pNodeName);
+ OUString aType = anyItem.getValueTypeName();
+ rJson.put("type", aType);
+
+ if (aType == "string")
+ rJson.put("value", anyItem.get<OUString>());
+ else if (aType == "unsigned long")
+ rJson.put("value", OString::number(anyItem.get<sal_uInt32>()));
+ else if (aType == "long")
+ rJson.put("value", OString::number(anyItem.get<sal_Int32>()));
+ else if (aType == "[]any")
+ {
+ uno::Sequence<uno::Any> aSeq;
+ if (anyItem >>= aSeq)
+ {
+ auto valueNode = rJson.startNode("value");
+
+ for (auto i = 0; i < aSeq.getLength(); ++i)
+ {
+ unoAnyToJson(rJson, OString::number(i), aSeq[i]);
+ }
+ }
+ }
+}
+
+static int lcl_getViewId(std::string_view payload);
+
+namespace desktop {
+
+RectangleAndPart RectangleAndPart::Create(const OString& rPayload)
+{
+ RectangleAndPart aRet;
+ if (rPayload.startsWith("EMPTY")) // payload starts with "EMPTY"
+ {
+ aRet.m_aRectangle = tools::Rectangle(0, 0, SfxLokHelper::MaxTwips, SfxLokHelper::MaxTwips);
+ if (comphelper::LibreOfficeKit::isPartInInvalidation())
+ {
+ int nSeparatorPos = rPayload.indexOf(',', 6);
+ bool bHasMode = nSeparatorPos > 0;
+ if (bHasMode)
+ {
+ aRet.m_nPart = o3tl::toInt32(rPayload.subView(6, nSeparatorPos - 6));
+ assert(rPayload.getLength() > nSeparatorPos);
+ aRet.m_nMode = o3tl::toInt32(rPayload.subView(nSeparatorPos + 1));
+ }
+ else
+ {
+ aRet.m_nPart = o3tl::toInt32(rPayload.subView(6));
+ aRet.m_nMode = 0;
+ }
+ }
+
+ return aRet;
+ }
+
+ // Read '<left>, <top>, <width>, <height>[, <part>, <mode>]'. C++ streams are simpler but slower.
+ const char* pos = rPayload.getStr();
+ const char* end = rPayload.getStr() + rPayload.getLength();
+ tools::Long nLeft = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+ while (pos < end && *pos != ',')
+ ++pos;
+ if (pos < end)
+ ++pos;
+ assert(pos < end);
+ tools::Long nTop = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+ while (pos < end && *pos != ',')
+ ++pos;
+ if (pos < end)
+ ++pos;
+ assert(pos < end);
+ tools::Long nWidth = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+ while (pos < end && *pos != ',')
+ ++pos;
+ if (pos < end)
+ ++pos;
+ assert(pos < end);
+ tools::Long nHeight = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+ tools::Long nPart = INT_MIN;
+ tools::Long nMode = 0;
+ if (comphelper::LibreOfficeKit::isPartInInvalidation())
+ {
+ while (pos < end && *pos != ',')
+ ++pos;
+ if (pos < end)
+ ++pos;
+ assert(pos < end);
+ nPart = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+
+ while (pos < end && *pos != ',')
+ ++pos;
+ if (pos < end)
+ {
+ ++pos;
+ assert(pos < end);
+ nMode = rtl_str_toInt64_WithLength(pos, 10, end - pos);
+ }
+ }
+
+ aRet.m_aRectangle = SanitizedRectangle(nLeft, nTop, nWidth, nHeight);
+ aRet.m_nPart = nPart;
+ aRet.m_nMode = nMode;
+ return aRet;
+}
+
+tools::Rectangle RectangleAndPart::SanitizedRectangle(tools::Long nLeft, tools::Long nTop, tools::Long nWidth, tools::Long nHeight)
+{
+ if (nWidth <= 0 || nHeight <= 0)
+ return tools::Rectangle();
+
+ // The top-left corner starts at (0, 0).
+ // Anything negative is invalid.
+ if (nLeft < 0)
+ {
+ nWidth += nLeft;
+ nLeft = 0;
+ }
+
+ if (nTop < 0)
+ {
+ nHeight += nTop;
+ nTop = 0;
+ }
+
+ if (nWidth > 0 && nHeight > 0)
+ return tools::Rectangle(nLeft, nTop, nLeft + nWidth, nTop + nHeight);
+ // Else set empty rect.
+ return tools::Rectangle();
+}
+
+tools::Rectangle RectangleAndPart::SanitizedRectangle(const tools::Rectangle& rect)
+{
+ return SanitizedRectangle(rect.Left(), rect.Top(), rect.getOpenWidth(), rect.getOpenHeight());
+}
+
+const OString& CallbackFlushHandler::CallbackData::getPayload() const
+{
+ if(PayloadString.isEmpty())
+ {
+ // Do to-string conversion on demand, as many calls will get dropped without
+ // needing the string.
+ if(PayloadObject.which() == 1)
+ PayloadString = getRectangleAndPart().toString();
+ }
+ return PayloadString;
+}
+
+void CallbackFlushHandler::CallbackData::updateRectangleAndPart(const RectangleAndPart& rRectAndPart)
+{
+ PayloadObject = rRectAndPart;
+ PayloadString.clear(); // will be set on demand if needed
+}
+
+const RectangleAndPart& CallbackFlushHandler::CallbackData::getRectangleAndPart() const
+{
+ // TODO: In case of unittests, they do not pass invalidations in binary but as text messages.
+ // LO core should preferably always pass binary for performance.
+ if(PayloadObject.which() != 1)
+ PayloadObject = RectangleAndPart::Create(PayloadString);
+ return boost::get<RectangleAndPart>(PayloadObject);
+}
+
+boost::property_tree::ptree& CallbackFlushHandler::CallbackData::setJson(const std::string& payload)
+{
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream(payload);
+ boost::property_tree::read_json(aStream, aTree);
+
+ // Let boost normalize the payload so it always matches the cache.
+ setJson(aTree);
+
+ // Return reference to the cached object.
+ return boost::get<boost::property_tree::ptree>(PayloadObject);
+}
+
+void CallbackFlushHandler::CallbackData::setJson(const boost::property_tree::ptree& rTree)
+{
+ std::stringstream aJSONStream;
+ constexpr bool bPretty = false; // Don't waste time and bloat logs.
+ boost::property_tree::write_json(aJSONStream, rTree, bPretty);
+ PayloadString = OString(o3tl::trim(aJSONStream.str()));
+
+ PayloadObject = rTree;
+}
+
+const boost::property_tree::ptree& CallbackFlushHandler::CallbackData::getJson() const
+{
+ assert(PayloadObject.which() == 2);
+ return boost::get<boost::property_tree::ptree>(PayloadObject);
+}
+
+int CallbackFlushHandler::CallbackData::getViewId() const
+{
+ if (isCached())
+ {
+ assert(PayloadObject.which() == 3);
+ return boost::get<int>(PayloadObject);
+ }
+ return lcl_getViewId(getPayload());
+}
+
+bool CallbackFlushHandler::CallbackData::validate() const
+{
+ switch (PayloadObject.which())
+ {
+ // Not cached.
+ case 0:
+ return true;
+
+ // RectangleAndPart.
+ case 1:
+ return getRectangleAndPart().toString().getStr() == getPayload();
+
+ // Json.
+ case 2:
+ {
+ std::stringstream aJSONStream;
+ boost::property_tree::write_json(aJSONStream, getJson(), false);
+ const std::string aExpected = boost::trim_copy(aJSONStream.str());
+ return getPayload() == std::string_view(aExpected);
+ }
+
+ // View id.
+ case 3:
+ return getViewId() == lcl_getViewId( getPayload());
+
+ default:
+ assert(!"Unknown variant type; please add an entry to validate.");
+ }
+
+ return false;
+}
+
+} // namespace desktop
+
+static bool lcl_isViewCallbackType(const int type)
+{
+ switch (type)
+ {
+ case LOK_CALLBACK_CELL_VIEW_CURSOR:
+ case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
+ case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static bool isUpdatedType(int type)
+{
+ switch (type)
+ {
+ case LOK_CALLBACK_TEXT_SELECTION:
+ case LOK_CALLBACK_TEXT_SELECTION_START:
+ case LOK_CALLBACK_TEXT_SELECTION_END:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool isUpdatedTypePerViewId(int type)
+{
+ switch (type)
+ {
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int lcl_getViewId(std::string_view payload)
+{
+ // this is a cheap way how to get the viewId from a JSON message; proper
+ // parsing is terribly expensive, and we just need the viewId here
+ size_t viewIdPos = payload.find("viewId");
+ if (viewIdPos == std::string::npos)
+ return 0;
+
+ size_t numberPos = payload.find(":", viewIdPos + 6);
+ if (numberPos == std::string::npos)
+ return 0;
+
+ for (++numberPos; numberPos < payload.length(); ++numberPos)
+ {
+ if (payload[numberPos] == ',' || payload[numberPos] == '}' || (payload[numberPos] >= '0' && payload[numberPos] <= '9'))
+ break;
+ }
+
+ if (numberPos < payload.length() && payload[numberPos] >= '0' && payload[numberPos] <= '9')
+ return o3tl::toInt32(payload.substr(numberPos));
+
+ return 0;
+}
+
+namespace {
+
+std::string extractCertificate(const std::string & certificate)
+{
+ static constexpr std::string_view header("-----BEGIN CERTIFICATE-----");
+ static constexpr std::string_view footer("-----END CERTIFICATE-----");
+
+ std::string result;
+
+ size_t pos1 = certificate.find(header);
+ if (pos1 == std::string::npos)
+ return result;
+
+ size_t pos2 = certificate.find(footer, pos1 + 1);
+ if (pos2 == std::string::npos)
+ return result;
+
+ pos1 = pos1 + header.length();
+ pos2 = pos2 - pos1;
+
+ return certificate.substr(pos1, pos2);
+}
+
+std::string extractPrivateKey(const std::string & privateKey)
+{
+ static constexpr std::string_view header("-----BEGIN PRIVATE KEY-----");
+ static constexpr std::string_view footer("-----END PRIVATE KEY-----");
+
+ std::string result;
+
+ size_t pos1 = privateKey.find(header);
+ if (pos1 == std::string::npos)
+ return result;
+
+ size_t pos2 = privateKey.find(footer, pos1 + 1);
+ if (pos2 == std::string::npos)
+ return result;
+
+ pos1 = pos1 + header.length();
+ pos2 = pos2 - pos1;
+
+ return privateKey.substr(pos1, pos2);
+}
+
+OUString lcl_getCurrentDocumentMimeType(const LibLODocument_Impl* pDocument)
+{
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return "";
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ if (!pObjectShell)
+ return "";
+
+ SfxMedium* pMedium = pObjectShell->GetMedium();
+ if (!pMedium)
+ return "";
+
+ auto pFilter = pMedium->GetFilter();
+ if (!pFilter)
+ return "";
+
+ return pFilter->GetMimeType();
+}
+
+// Gets an undo manager to enter and exit undo context. Needed by ToggleOrientation
+css::uno::Reference< css::document::XUndoManager > getUndoManager( const css::uno::Reference< css::frame::XFrame >& rxFrame )
+{
+ const css::uno::Reference< css::frame::XController >& xController = rxFrame->getController();
+ if ( xController.is() )
+ {
+ const css::uno::Reference< css::frame::XModel >& xModel = xController->getModel();
+ if ( xModel.is() )
+ {
+ const css::uno::Reference< css::document::XUndoManagerSupplier > xSuppUndo( xModel, css::uno::UNO_QUERY_THROW );
+ return css::uno::Reference< css::document::XUndoManager >( xSuppUndo->getUndoManager(), css::uno::UNO_SET_THROW );
+ }
+ }
+
+ return css::uno::Reference< css::document::XUndoManager > ();
+}
+
+// Adjusts page margins for Writer doc. Needed by ToggleOrientation
+void ExecuteMarginLRChange(
+ const tools::Long nPageLeftMargin,
+ const tools::Long nPageRightMargin,
+ SvxLongLRSpaceItem* pPageLRMarginItem)
+{
+ pPageLRMarginItem->SetLeft( nPageLeftMargin );
+ pPageLRMarginItem->SetRight( nPageRightMargin );
+ SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_LRSPACE,
+ SfxCallMode::RECORD, { pPageLRMarginItem });
+}
+
+// Adjusts page margins for Writer doc. Needed by ToggleOrientation
+void ExecuteMarginULChange(
+ const tools::Long nPageTopMargin,
+ const tools::Long nPageBottomMargin,
+ SvxLongULSpaceItem* pPageULMarginItem)
+{
+ pPageULMarginItem->SetUpper( nPageTopMargin );
+ pPageULMarginItem->SetLower( nPageBottomMargin );
+ SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_ULSPACE,
+ SfxCallMode::RECORD, { pPageULMarginItem });
+}
+
+// Main function which toggles page orientation of the Writer doc. Needed by ToggleOrientation
+void ExecuteOrientationChange()
+{
+ SfxViewFrame* pViewFrm = SfxViewFrame::Current();
+ if (!pViewFrm)
+ return;
+
+ std::unique_ptr<SvxPageItem> pPageItem(new SvxPageItem(SID_ATTR_PAGE));
+
+ // 1mm in twips rounded
+ // This should be in sync with MINBODY in sw/source/uibase/sidebar/PageMarginControl.hxx
+ constexpr tools::Long MINBODY = o3tl::toTwips(1, o3tl::Length::mm);
+
+ css::uno::Reference< css::document::XUndoManager > mxUndoManager(
+ getUndoManager( pViewFrm->GetFrame().GetFrameInterface() ) );
+
+ if ( mxUndoManager.is() )
+ mxUndoManager->enterUndoContext( "" );
+
+ SfxPoolItemHolder aResult;
+ pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_SIZE, aResult);
+ std::unique_ptr<SvxSizeItem> pPageSizeItem(static_cast<const SvxSizeItem*>(aResult.getItem())->Clone());
+
+ pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_LRSPACE, aResult);
+ std::unique_ptr<SvxLongLRSpaceItem> pPageLRMarginItem(static_cast<const SvxLongLRSpaceItem*>(aResult.getItem())->Clone());
+
+ pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_ULSPACE, aResult);
+ std::unique_ptr<SvxLongULSpaceItem> pPageULMarginItem(static_cast<const SvxLongULSpaceItem*>(aResult.getItem())->Clone());
+
+ {
+ bool bIsLandscape = false;
+ if ( pPageSizeItem->GetSize().Width() > pPageSizeItem->GetSize().Height())
+ bIsLandscape = true;
+
+ // toggle page orientation
+ pPageItem->SetLandscape(!bIsLandscape);
+
+
+ // swap the width and height of the page size
+ const tools::Long nRotatedWidth = pPageSizeItem->GetSize().Height();
+ const tools::Long nRotatedHeight = pPageSizeItem->GetSize().Width();
+ pPageSizeItem->SetSize(Size(nRotatedWidth, nRotatedHeight));
+
+
+ // apply changed attributes
+ if (SfxViewShell::Current())
+ {
+ SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_SIZE,
+ SfxCallMode::RECORD, { pPageSizeItem.get(), pPageItem.get() });
+ }
+ }
+
+
+ // check, if margin values still fit to the changed page size.
+ // if not, adjust margin values
+ {
+ const tools::Long nML = pPageLRMarginItem->GetLeft();
+ const tools::Long nMR = pPageLRMarginItem->GetRight();
+ const tools::Long nTmpPW = nML + nMR + MINBODY;
+
+ const tools::Long nPW = pPageSizeItem->GetSize().Width();
+
+ if ( nTmpPW > nPW )
+ {
+ if ( nML <= nMR )
+ {
+ ExecuteMarginLRChange( pPageLRMarginItem->GetLeft(), nMR - (nTmpPW - nPW ), pPageLRMarginItem.get() );
+ }
+ else
+ {
+ ExecuteMarginLRChange( nML - (nTmpPW - nPW ), pPageLRMarginItem->GetRight(), pPageLRMarginItem.get() );
+ }
+ }
+
+ const tools::Long nMT = pPageULMarginItem->GetUpper();
+ const tools::Long nMB = pPageULMarginItem->GetLower();
+ const tools::Long nTmpPH = nMT + nMB + MINBODY;
+
+ const tools::Long nPH = pPageSizeItem->GetSize().Height();
+
+ if ( nTmpPH > nPH )
+ {
+ if ( nMT <= nMB )
+ {
+ ExecuteMarginULChange( pPageULMarginItem->GetUpper(), nMB - ( nTmpPH - nPH ), pPageULMarginItem.get() );
+ }
+ else
+ {
+ ExecuteMarginULChange( nMT - ( nTmpPH - nPH ), pPageULMarginItem->GetLower(), pPageULMarginItem.get() );
+ }
+ }
+ }
+
+ if ( mxUndoManager.is() )
+ mxUndoManager->leaveUndoContext();
+}
+
+void setupSidebar(std::u16string_view sidebarDeckId = u"")
+{
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
+ if (pViewFrame)
+ {
+ if (!pViewFrame->GetChildWindow(SID_SIDEBAR))
+ pViewFrame->SetChildWindow(SID_SIDEBAR, false /* create it */, true /* focus */);
+
+ pViewFrame->ShowChildWindow(SID_SIDEBAR, true);
+
+ // Force synchronous population of panels
+ SfxChildWindow *pChild = pViewFrame->GetChildWindow(SID_SIDEBAR);
+ if (!pChild)
+ return;
+
+ auto pDockingWin = dynamic_cast<sfx2::sidebar::SidebarDockingWindow *>(pChild->GetWindow());
+ if (!pDockingWin)
+ return;
+
+ pViewFrame->ShowChildWindow( SID_SIDEBAR );
+
+ const rtl::Reference<sfx2::sidebar::SidebarController>& xController
+ = pDockingWin->GetOrCreateSidebarController();
+
+ xController->FadeIn();
+ xController->RequestOpenDeck();
+
+ if (!sidebarDeckId.empty())
+ {
+ xController->SwitchToDeck(sidebarDeckId);
+ }
+ else
+ {
+ xController->SwitchToDefaultDeck();
+ }
+
+ pDockingWin->SyncUpdate();
+ }
+ else
+ SetLastExceptionMsg(u"No view shell or sidebar"_ustr);
+}
+
+void hideSidebar()
+{
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
+ if (pViewFrame)
+ pViewFrame->SetChildWindow(SID_SIDEBAR, false , false );
+ else
+ SetLastExceptionMsg(u"No view shell or sidebar"_ustr);
+}
+
+} // end anonymous namespace
+
+// Could be anonymous in principle, but for the unit testing purposes, we
+// declare it in init.hxx.
+OUString desktop::extractParameter(OUString& rOptions, std::u16string_view rName)
+{
+ OUString aValue;
+
+ OUString aNameEquals(OUString::Concat(rName) + "=");
+ OUString aCommaNameEquals(OUString::Concat(",") + rName + "=");
+
+ int nIndex = -1;
+ if (rOptions.startsWith(aNameEquals))
+ {
+ size_t nLen = aNameEquals.getLength();
+ int nComma = rOptions.indexOf(",", nLen);
+ if (nComma >= 0)
+ {
+ aValue = rOptions.copy(nLen, nComma - nLen);
+ rOptions = rOptions.copy(nComma + 1);
+ }
+ else
+ {
+ aValue = rOptions.copy(nLen);
+ rOptions.clear();
+ }
+ }
+ else if ((nIndex = rOptions.indexOf(aCommaNameEquals)) >= 0)
+ {
+ size_t nLen = aCommaNameEquals.getLength();
+ int nComma = rOptions.indexOf(",", nIndex + nLen);
+ if (nComma >= 0)
+ {
+ aValue = rOptions.copy(nIndex + nLen, nComma - nIndex - nLen);
+ rOptions = OUString::Concat(rOptions.subView(0, nIndex)) + rOptions.subView(nComma);
+ }
+ else
+ {
+ aValue = rOptions.copy(nIndex + nLen);
+ rOptions = rOptions.copy(0, nIndex);
+ }
+ }
+
+ return aValue;
+}
+
+extern "C"
+{
+
+static void doc_destroy(LibreOfficeKitDocument* pThis);
+static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* pUrl, const char* pFormat, const char* pFilterOptions);
+static int doc_getDocumentType(LibreOfficeKitDocument* pThis);
+static int doc_getParts(LibreOfficeKitDocument* pThis);
+static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis);
+static int doc_getPart(LibreOfficeKitDocument* pThis);
+static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart);
+static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect);
+static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate);
+static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart);
+static void doc_setPartMode(LibreOfficeKitDocument* pThis, int nPartMode);
+static int doc_getEditMode(LibreOfficeKitDocument* pThis);
+static void doc_paintTile(LibreOfficeKitDocument* pThis,
+ unsigned char* pBuffer,
+ const int nCanvasWidth, const int nCanvasHeight,
+ const int nTilePosX, const int nTilePosY,
+ const int nTileWidth, const int nTileHeight);
+static void doc_paintPartTile(LibreOfficeKitDocument* pThis,
+ unsigned char* pBuffer,
+ const int nPart,
+ const int nMode,
+ const int nCanvasWidth, const int nCanvasHeight,
+ const int nTilePosX, const int nTilePosY,
+ const int nTileWidth, const int nTileHeight);
+static int doc_getTileMode(LibreOfficeKitDocument* pThis);
+static void doc_getDocumentSize(LibreOfficeKitDocument* pThis,
+ long* pWidth,
+ long* pHeight);
+static void doc_getDataArea(LibreOfficeKitDocument* pThis,
+ long nTab,
+ long* pCol,
+ long* pRow);
+static void doc_initializeForRendering(LibreOfficeKitDocument* pThis,
+ const char* pArguments);
+
+static void doc_registerCallback(LibreOfficeKitDocument* pThis,
+ LibreOfficeKitCallback pCallback,
+ void* pData);
+static void doc_postKeyEvent(LibreOfficeKitDocument* pThis,
+ int nType,
+ int nCharCode,
+ int nKeyCode);
+static void doc_setBlockedCommandList(LibreOfficeKitDocument* pThis,
+ int nViewId,
+ const char* blockedCommandList);
+
+static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis,
+ unsigned nWindowId,
+ int nType,
+ const char* pText);
+static void doc_removeTextContext(LibreOfficeKitDocument* pThis,
+ unsigned nLOKWindowId,
+ int nCharBefore,
+ int nCharAfter);
+static void doc_sendDialogEvent(LibreOfficeKitDocument* pThis,
+ unsigned long long int nLOKWindowId,
+ const char* pArguments);
+static void doc_postWindowKeyEvent(LibreOfficeKitDocument* pThis,
+ unsigned nLOKWindowId,
+ int nType,
+ int nCharCode,
+ int nKeyCode);
+static void doc_postMouseEvent (LibreOfficeKitDocument* pThis,
+ int nType,
+ int nX,
+ int nY,
+ int nCount,
+ int nButtons,
+ int nModifier);
+static void doc_postWindowMouseEvent (LibreOfficeKitDocument* pThis,
+ unsigned nLOKWindowId,
+ int nType,
+ int nX,
+ int nY,
+ int nCount,
+ int nButtons,
+ int nModifier);
+static void doc_postWindowGestureEvent(LibreOfficeKitDocument* pThis,
+ unsigned nLOKWindowId,
+ const char* pType,
+ int nX,
+ int nY,
+ int nOffset);
+static void doc_postUnoCommand(LibreOfficeKitDocument* pThis,
+ const char* pCommand,
+ const char* pArguments,
+ bool bNotifyWhenFinished);
+static void doc_setWindowTextSelection(LibreOfficeKitDocument* pThis,
+ unsigned nLOKWindowId,
+ bool swap,
+ int nX,
+ int nY);
+static void doc_setTextSelection (LibreOfficeKitDocument* pThis,
+ int nType,
+ int nX,
+ int nY);
+static char* doc_getTextSelection(LibreOfficeKitDocument* pThis,
+ const char* pMimeType,
+ char** pUsedMimeType);
+static int doc_getSelectionType(LibreOfficeKitDocument* pThis);
+static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis,
+ const char* pMimeType,
+ char** pText,
+ char** pUsedMimeType);
+static int doc_getClipboard (LibreOfficeKitDocument* pThis,
+ const char **pMimeTypes,
+ size_t *pOutCount,
+ char ***pOutMimeTypes,
+ size_t **pOutSizes,
+ char ***pOutStreams);
+static int doc_setClipboard (LibreOfficeKitDocument* pThis,
+ const size_t nInCount,
+ const char **pInMimeTypes,
+ const size_t *pInSizes,
+ const char **pInStreams);
+static bool doc_paste(LibreOfficeKitDocument* pThis,
+ const char* pMimeType,
+ const char* pData,
+ size_t nSize);
+static void doc_setGraphicSelection (LibreOfficeKitDocument* pThis,
+ int nType,
+ int nX,
+ int nY);
+static void doc_resetSelection (LibreOfficeKitDocument* pThis);
+static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand);
+static void doc_setClientZoom(LibreOfficeKitDocument* pThis,
+ int nTilePixelWidth,
+ int nTilePixelHeight,
+ int nTileTwipWidth,
+ int nTileTwipHeight);
+static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight);
+static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden);
+static int doc_createView(LibreOfficeKitDocument* pThis);
+static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis, const char* pOptions);
+static void doc_destroyView(LibreOfficeKitDocument* pThis, int nId);
+static void doc_setView(LibreOfficeKitDocument* pThis, int nId);
+static int doc_getView(LibreOfficeKitDocument* pThis);
+static int doc_getViewsCount(LibreOfficeKitDocument* pThis);
+static bool doc_getViewIds(LibreOfficeKitDocument* pThis, int* pArray, size_t nSize);
+static void doc_setViewLanguage(LibreOfficeKitDocument* pThis, int nId, const char* language);
+static unsigned char* doc_renderFontOrientation(LibreOfficeKitDocument* pThis,
+ const char *pFontName,
+ const char *pChar,
+ int* pFontWidth,
+ int* pFontHeight,
+ int pOrientation);
+static unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis,
+ const char *pFontName,
+ const char *pChar,
+ int* pFontWidth,
+ int* pFontHeight);
+static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart);
+
+static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
+ const int nX, const int nY,
+ const int nWidth, const int nHeight);
+
+static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
+ const int nX, const int nY,
+ const int nWidth, const int nHeight,
+ const double fDPIScale);
+
+static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
+ const int nX, const int nY,
+ const int nWidth, const int nHeight,
+ const double fDPIScale, int viewId);
+
+static void doc_postWindow(LibreOfficeKitDocument* pThis, unsigned
+ nLOKWindowId, int nAction, const char* pData);
+
+static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart);
+
+static bool doc_insertCertificate(LibreOfficeKitDocument* pThis,
+ const unsigned char* pCertificateBinary,
+ const int nCertificateBinarySize,
+ const unsigned char* pPrivateKeyBinary,
+ const int nPrivateKeyBinarySize);
+
+static bool doc_addCertificate(LibreOfficeKitDocument* pThis,
+ const unsigned char* pCertificateBinary,
+ const int nCertificateBinarySize);
+
+static int doc_getSignatureState(LibreOfficeKitDocument* pThis);
+
+static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput);
+
+static void doc_resizeWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
+ const int nWidth, const int nHeight);
+
+static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char*);
+
+
+static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis,
+ const char* pArguments);
+
+static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis,
+ const char* pSearchResult, unsigned char** pBitmapBuffer,
+ int* pWidth, int* pHeight, size_t* pByteSize);
+
+static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments);
+
+static void doc_setViewTimezone(LibreOfficeKitDocument* pThis, int nId, const char* timezone);
+
+static void doc_setAccessibilityState(LibreOfficeKitDocument* pThis, int nId, bool bEnabled);
+
+static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis);
+
+static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis);
+} // extern "C"
+
+namespace {
+ITiledRenderable* getTiledRenderable(LibreOfficeKitDocument* pThis)
+{
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ return dynamic_cast<ITiledRenderable*>(pDocument->mxComponent.get());
+}
+
+#ifndef IOS
+
+/*
+ * Unfortunately clipboard creation using UNO is insanely baroque.
+ * we also need to ensure that this works for the first view which
+ * has no clear 'createView' called for it (unfortunately).
+ */
+rtl::Reference<LOKClipboard> forceSetClipboardForCurrentView(LibreOfficeKitDocument *pThis)
+{
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView());
+
+ SAL_INFO("lok", "Set to clipboard for view " << xClip.get());
+ // FIXME: using a hammer here - should not be necessary if all tests used createView.
+ pDoc->setClipboard(uno::Reference<datatransfer::clipboard::XClipboard>(xClip->getXI(), UNO_QUERY));
+
+ return xClip;
+}
+
+#endif
+
+const vcl::Font* FindFont(std::u16string_view rFontName)
+{
+ SfxObjectShell* pDocSh = SfxObjectShell::Current();
+ if (!pDocSh)
+ return nullptr;
+ const SvxFontListItem* pFonts
+ = static_cast<const SvxFontListItem*>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST));
+ const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr;
+ if (pList && !rFontName.empty())
+ if (sal_Handle hMetric = pList->GetFirstFontMetric(rFontName))
+ return &FontList::GetFontMetric(hMetric);
+ return nullptr;
+}
+
+vcl::Font FindFont_FallbackToDefault(std::u16string_view rFontName)
+{
+ if (auto pFound = FindFont(rFontName))
+ return *pFound;
+
+ return OutputDevice::GetDefaultFont(DefaultFontType::SANS_UNICODE, LANGUAGE_NONE,
+ GetDefaultFontFlags::NONE);
+}
+
+int getDocumentType (LibreOfficeKitDocument* pThis)
+{
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ try
+ {
+ uno::Reference<lang::XServiceInfo> xDocument(pDocument->mxComponent, uno::UNO_QUERY_THROW);
+
+ if (xDocument->supportsService(u"com.sun.star.sheet.SpreadsheetDocument"_ustr))
+ {
+ return LOK_DOCTYPE_SPREADSHEET;
+ }
+ else if (xDocument->supportsService(u"com.sun.star.presentation.PresentationDocument"_ustr))
+ {
+ return LOK_DOCTYPE_PRESENTATION;
+ }
+ else if (xDocument->supportsService(u"com.sun.star.drawing.DrawingDocument"_ustr))
+ {
+ return LOK_DOCTYPE_DRAWING;
+ }
+ else if (xDocument->supportsService(u"com.sun.star.text.TextDocument"_ustr) || xDocument->supportsService(u"com.sun.star.text.WebDocument"_ustr))
+ {
+ return LOK_DOCTYPE_TEXT;
+ }
+ else
+ {
+ SetLastExceptionMsg(u"unknown document type"_ustr);
+ }
+ }
+ catch (const uno::Exception& exception)
+ {
+ SetLastExceptionMsg("exception: " + exception.Message);
+ }
+ return LOK_DOCTYPE_OTHER;
+}
+
+} // anonymous namespace
+
+LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xComponent, int nDocumentId)
+ : mxComponent(std::move(xComponent))
+ , mnDocumentId(nDocumentId)
+{
+ assert(nDocumentId != -1 && "Cannot set mnDocumentId to -1");
+
+ m_pDocumentClass = gDocumentClass.lock();
+ if (!m_pDocumentClass)
+ {
+ m_pDocumentClass = std::make_shared<LibreOfficeKitDocumentClass>();
+
+ m_pDocumentClass->nSize = sizeof(LibreOfficeKitDocumentClass);
+
+ m_pDocumentClass->destroy = doc_destroy;
+ m_pDocumentClass->saveAs = doc_saveAs;
+ m_pDocumentClass->getDocumentType = doc_getDocumentType;
+ m_pDocumentClass->getParts = doc_getParts;
+ m_pDocumentClass->getPartPageRectangles = doc_getPartPageRectangles;
+ m_pDocumentClass->getPart = doc_getPart;
+ m_pDocumentClass->setPart = doc_setPart;
+ m_pDocumentClass->selectPart = doc_selectPart;
+ m_pDocumentClass->moveSelectedParts = doc_moveSelectedParts;
+ m_pDocumentClass->getPartName = doc_getPartName;
+ m_pDocumentClass->setPartMode = doc_setPartMode;
+ m_pDocumentClass->getEditMode = doc_getEditMode;
+ m_pDocumentClass->paintTile = doc_paintTile;
+ m_pDocumentClass->paintPartTile = doc_paintPartTile;
+ m_pDocumentClass->getTileMode = doc_getTileMode;
+ m_pDocumentClass->getDocumentSize = doc_getDocumentSize;
+ m_pDocumentClass->getDataArea = doc_getDataArea;
+ m_pDocumentClass->initializeForRendering = doc_initializeForRendering;
+ m_pDocumentClass->registerCallback = doc_registerCallback;
+ m_pDocumentClass->postKeyEvent = doc_postKeyEvent;
+ m_pDocumentClass->postWindowExtTextInputEvent = doc_postWindowExtTextInputEvent;
+ m_pDocumentClass->removeTextContext = doc_removeTextContext;
+ m_pDocumentClass->postWindowKeyEvent = doc_postWindowKeyEvent;
+ m_pDocumentClass->postMouseEvent = doc_postMouseEvent;
+ m_pDocumentClass->postWindowMouseEvent = doc_postWindowMouseEvent;
+ m_pDocumentClass->sendDialogEvent = doc_sendDialogEvent;
+ m_pDocumentClass->postUnoCommand = doc_postUnoCommand;
+ m_pDocumentClass->setTextSelection = doc_setTextSelection;
+ m_pDocumentClass->setWindowTextSelection = doc_setWindowTextSelection;
+ m_pDocumentClass->getTextSelection = doc_getTextSelection;
+ m_pDocumentClass->getSelectionType = doc_getSelectionType;
+ m_pDocumentClass->getSelectionTypeAndText = doc_getSelectionTypeAndText;
+ m_pDocumentClass->getClipboard = doc_getClipboard;
+ m_pDocumentClass->setClipboard = doc_setClipboard;
+ m_pDocumentClass->paste = doc_paste;
+ m_pDocumentClass->setGraphicSelection = doc_setGraphicSelection;
+ m_pDocumentClass->resetSelection = doc_resetSelection;
+ m_pDocumentClass->getCommandValues = doc_getCommandValues;
+ m_pDocumentClass->setClientZoom = doc_setClientZoom;
+ m_pDocumentClass->setClientVisibleArea = doc_setClientVisibleArea;
+ m_pDocumentClass->setOutlineState = doc_setOutlineState;
+
+ m_pDocumentClass->createView = doc_createView;
+ m_pDocumentClass->destroyView = doc_destroyView;
+ m_pDocumentClass->setView = doc_setView;
+ m_pDocumentClass->getView = doc_getView;
+ m_pDocumentClass->getViewsCount = doc_getViewsCount;
+ m_pDocumentClass->getViewIds = doc_getViewIds;
+
+ m_pDocumentClass->renderFont = doc_renderFont;
+ m_pDocumentClass->renderFontOrientation = doc_renderFontOrientation;
+ m_pDocumentClass->getPartHash = doc_getPartHash;
+
+ m_pDocumentClass->paintWindow = doc_paintWindow;
+ m_pDocumentClass->paintWindowDPI = doc_paintWindowDPI;
+ m_pDocumentClass->paintWindowForView = doc_paintWindowForView;
+ m_pDocumentClass->postWindow = doc_postWindow;
+ m_pDocumentClass->resizeWindow = doc_resizeWindow;
+
+ m_pDocumentClass->setViewLanguage = doc_setViewLanguage;
+
+ m_pDocumentClass->getPartInfo = doc_getPartInfo;
+
+ m_pDocumentClass->insertCertificate = doc_insertCertificate;
+ m_pDocumentClass->addCertificate = doc_addCertificate;
+ m_pDocumentClass->getSignatureState = doc_getSignatureState;
+
+ m_pDocumentClass->renderShapeSelection = doc_renderShapeSelection;
+ m_pDocumentClass->postWindowGestureEvent = doc_postWindowGestureEvent;
+
+ m_pDocumentClass->createViewWithOptions = doc_createViewWithOptions;
+ m_pDocumentClass->completeFunction = doc_completeFunction;
+
+ m_pDocumentClass->sendFormFieldEvent = doc_sendFormFieldEvent;
+ m_pDocumentClass->renderSearchResult = doc_renderSearchResult;
+
+ m_pDocumentClass->setBlockedCommandList = doc_setBlockedCommandList;
+
+ m_pDocumentClass->sendContentControlEvent = doc_sendContentControlEvent;
+
+ m_pDocumentClass->setViewTimezone = doc_setViewTimezone;
+
+ m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState;
+
+ m_pDocumentClass->getA11yFocusedParagraph = doc_getA11yFocusedParagraph;
+ m_pDocumentClass->getA11yCaretPosition = doc_getA11yCaretPosition;
+
+ gDocumentClass = m_pDocumentClass;
+ }
+ pClass = m_pDocumentClass.get();
+
+#ifndef IOS
+ forceSetClipboardForCurrentView(this);
+#endif
+}
+
+LibLODocument_Impl::~LibLODocument_Impl()
+{
+ try
+ {
+ mxComponent->dispose();
+ }
+ catch (const css::lang::DisposedException&)
+ {
+ TOOLS_WARN_EXCEPTION("lok", "failed to dispose document");
+ }
+}
+
+static OUString getGenerator()
+{
+ OUString sGenerator(
+ Translate::ExpandVariables(u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION (%1)"_ustr));
+ OUString os(u"$_OS"_ustr);
+ ::rtl::Bootstrap::expandMacros(os);
+ return sGenerator.replaceFirst("%1", os);
+}
+
+extern "C" {
+
+CallbackFlushHandler::TimeoutIdle::TimeoutIdle( CallbackFlushHandler* handler )
+ : Timer( "lokit timer callback" )
+ , mHandler( handler )
+{
+ // A second timer with higher priority, it'll ensure we flush in reasonable time if we get too busy
+ // to get POST_PAINT priority processing. Otherwise it could take a long time to flush.
+ SetPriority(TaskPriority::DEFAULT);
+ SetTimeout( 100 ); // 100 ms
+}
+
+void CallbackFlushHandler::TimeoutIdle::Invoke()
+{
+ mHandler->Invoke();
+}
+
+// One of these is created per view to handle events cf. doc_registerCallback
+CallbackFlushHandler::CallbackFlushHandler(LibreOfficeKitDocument* pDocument, LibreOfficeKitCallback pCallback, void* pData)
+ : Idle( "lokit idle callback" ),
+ m_pDocument(pDocument),
+ m_pCallback(pCallback),
+ m_pData(pData),
+ m_nDisableCallbacks(0),
+ m_TimeoutIdle( this )
+{
+ SetPriority(TaskPriority::POST_PAINT);
+
+ // Add the states that are safe to skip duplicates on, even when
+ // not consequent (i.e. do no emit them if unchanged from last).
+ m_states.emplace(LOK_CALLBACK_TEXT_SELECTION, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_GRAPHIC_SELECTION, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_STATE_CHANGED, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_MOUSE_POINTER, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_CELL_CURSOR, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_CELL_FORMULA, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_CELL_ADDRESS, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_CURSOR_VISIBLE, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_SET_PART, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_TABLE_SELECTED, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_TAB_STOP_LIST, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_RULER_UPDATE, "NIL"_ostr);
+ m_states.emplace(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE, "NIL"_ostr);
+}
+
+CallbackFlushHandler::~CallbackFlushHandler()
+{
+ Stop();
+}
+
+CallbackFlushHandler::queue_type2::iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::iterator pos)
+{
+ int delta = std::distance(m_queue1.begin(), pos);
+ return m_queue2.begin() + delta;
+}
+
+CallbackFlushHandler::queue_type2::reverse_iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::reverse_iterator pos)
+{
+ int delta = std::distance(m_queue1.rbegin(), pos);
+ return m_queue2.rbegin() + delta;
+}
+
+void CallbackFlushHandler::setUpdatedType( int nType, bool value )
+{
+ assert(isUpdatedType(nType));
+ if( m_updatedTypes.size() <= o3tl::make_unsigned( nType ))
+ m_updatedTypes.resize( nType + 1 ); // new are default-constructed, i.e. false
+ m_updatedTypes[ nType ] = value;
+ if(value)
+ startTimer();
+}
+
+void CallbackFlushHandler::resetUpdatedType( int nType )
+{
+ setUpdatedType( nType, false );
+}
+
+void CallbackFlushHandler::setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value )
+{
+ assert(isUpdatedTypePerViewId(nType));
+ std::vector<PerViewIdData>& types = m_updatedTypesPerViewId[ nViewId ];
+ if( types.size() <= o3tl::make_unsigned( nType ))
+ types.resize( nType + 1 ); // new are default-constructed, i.e. 'set' is false
+ types[ nType ] = PerViewIdData{ value, nSourceViewId };
+ if(value)
+ startTimer();
+}
+
+void CallbackFlushHandler::resetUpdatedTypePerViewId( int nType, int nViewId )
+{
+ assert(isUpdatedTypePerViewId(nType));
+ bool allViewIds = false;
+ // Handle specially messages that do not have viewId for backwards compatibility.
+ if( nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+ allViewIds = true;
+ if( !allViewIds )
+ {
+ setUpdatedTypePerViewId( nType, nViewId, -1, false );
+ return;
+ }
+ for( auto& it : m_updatedTypesPerViewId )
+ {
+ std::vector<PerViewIdData>& types = it.second;
+ if( types.size() >= o3tl::make_unsigned( nType ))
+ types[ nType ].set = false;
+ }
+}
+
+void CallbackFlushHandler::libreOfficeKitViewCallback(int nType, const OString& pPayload)
+{
+ CallbackData callbackData(pPayload);
+ queue(nType, callbackData);
+}
+
+void CallbackFlushHandler::libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId)
+{
+ CallbackData callbackData(pPayload, nViewId);
+ queue(nType, callbackData);
+}
+
+void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode)
+{
+ CallbackData callbackData(pRect, nPart, nMode);
+ queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData);
+}
+
+void CallbackFlushHandler::libreOfficeKitViewUpdatedCallback(int nType)
+{
+ assert(isUpdatedType( nType ));
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ SAL_INFO("lok", "Updated: [" << nType << "]");
+ setUpdatedType(nType, true);
+}
+
+void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId)
+{
+ assert(isUpdatedTypePerViewId( nType ));
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ SAL_INFO("lok", "Updated: [" << nType << "]");
+ setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true);
+}
+
+void CallbackFlushHandler::dumpState(rtl::OStringBuffer &rState)
+{
+ // NB. no locking
+ rState.append("\nView:\t");
+ rState.append(static_cast<sal_Int32>(m_viewId));
+ rState.append("\n\tDisableCallbacks:\t");
+ rState.append(static_cast<sal_Int32>(m_nDisableCallbacks));
+ rState.append("\n\tStates:\n");
+ for (const auto &i : m_states)
+ {
+ rState.append("\n\t\t");
+ rState.append(static_cast<sal_Int32>(i.first));
+ rState.append("\t");
+ rState.append(i.second);
+ }
+}
+
+void CallbackFlushHandler::libreOfficeKitViewAddPendingInvalidateTiles()
+{
+ // Invoke() will call flushPendingLOKInvalidateTiles(), so just make sure the timer is active.
+ startTimer();
+}
+
+void CallbackFlushHandler::queue(const int type, const OString& data)
+{
+ CallbackData callbackData(data);
+ queue(type, callbackData);
+}
+
+void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData)
+{
+ comphelper::ProfileZone aZone("CallbackFlushHandler::queue");
+
+ SAL_INFO("lok", "Queue: [" << type << "]: [" << aCallbackData.getPayload() << "] on " << m_queue1.size() << " entries.");
+
+ bool bIsChartActive = false;
+ bool bIsComment = false;
+ if (type == LOK_CALLBACK_GRAPHIC_SELECTION)
+ {
+ LokChartHelper aChartHelper(SfxViewShell::Current());
+ bIsChartActive = aChartHelper.GetWindow() != nullptr;
+ }
+ else if (type == LOK_CALLBACK_COMMENT)
+ {
+ bIsComment = true;
+ }
+
+ if (callbacksDisabled() && !bIsChartActive && !bIsComment)
+ {
+ // We drop notifications when this is set, except for important ones.
+ // When we issue a complex command (such as .uno:InsertAnnotation)
+ // there will be multiple notifications. On the first invalidation
+ // we will start painting, but other events will get fired
+ // while the complex command in question executes.
+ // We don't want to suppress everything here on the wrong assumption
+ // that no new events are fired during painting.
+ if (type != LOK_CALLBACK_STATE_CHANGED &&
+ type != LOK_CALLBACK_INVALIDATE_TILES &&
+ type != LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR &&
+ type != LOK_CALLBACK_CURSOR_VISIBLE &&
+ type != LOK_CALLBACK_VIEW_CURSOR_VISIBLE &&
+ type != LOK_CALLBACK_TEXT_SELECTION &&
+ type != LOK_CALLBACK_TEXT_SELECTION_START &&
+ type != LOK_CALLBACK_TEXT_SELECTION_END &&
+ type != LOK_CALLBACK_MEDIA_SHAPE &&
+ type != LOK_CALLBACK_REFERENCE_MARKS)
+ {
+ SAL_INFO("lok", "Skipping while painting [" << type << "]: [" << aCallbackData.getPayload() << "].");
+ return;
+ }
+
+ // In Writer we drop all notifications during painting.
+ if (doc_getDocumentType(m_pDocument) == LOK_DOCTYPE_TEXT)
+ return;
+ }
+
+ // Suppress invalid payloads.
+ if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR &&
+ aCallbackData.getPayload().indexOf(", 0, 0, ") != -1 &&
+ aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1 &&
+ aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1)
+ {
+ // The cursor position is often the relative coordinates of the widget
+ // issuing it, instead of the absolute one that we expect.
+ // This is temporary however, and, once the control is created and initialized
+ // correctly, it eventually emits the correct absolute coordinates.
+ SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "].");
+ return;
+ }
+
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+
+ // Update types should be received via the updated callbacks for performance,
+ // getting them as normal callbacks is technically not wrong, but probably should be avoided.
+ // Reset the updated flag if we get a normal message.
+ if(isUpdatedType(type))
+ {
+ SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback");
+ resetUpdatedType(type);
+ }
+ if(isUpdatedTypePerViewId(type))
+ {
+ SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback");
+ resetUpdatedTypePerViewId(type, aCallbackData.getViewId());
+ }
+
+ // drop duplicate callbacks for the listed types
+ switch (type)
+ {
+ case LOK_CALLBACK_TEXT_SELECTION_START:
+ case LOK_CALLBACK_TEXT_SELECTION_END:
+ case LOK_CALLBACK_TEXT_SELECTION:
+ case LOK_CALLBACK_GRAPHIC_SELECTION:
+ case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+ case LOK_CALLBACK_STATE_CHANGED:
+ case LOK_CALLBACK_MOUSE_POINTER:
+ case LOK_CALLBACK_CELL_CURSOR:
+ case LOK_CALLBACK_CELL_VIEW_CURSOR:
+ case LOK_CALLBACK_CELL_FORMULA:
+ case LOK_CALLBACK_CELL_ADDRESS:
+ case LOK_CALLBACK_CELL_SELECTION_AREA:
+ case LOK_CALLBACK_CURSOR_VISIBLE:
+ case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
+ case LOK_CALLBACK_SET_PART:
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ case LOK_CALLBACK_INVALIDATE_HEADER:
+ case LOK_CALLBACK_WINDOW:
+ case LOK_CALLBACK_CALC_FUNCTION_LIST:
+ case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
+ case LOK_CALLBACK_REFERENCE_MARKS:
+ case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
+ case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
+ case LOK_CALLBACK_A11Y_CARET_CHANGED:
+ case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
+ case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED:
+ case LOK_CALLBACK_COLOR_PALETTES:
+ case LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE:
+ case LOK_CALLBACK_A11Y_SELECTION_CHANGED:
+ {
+ const auto& pos = std::find(m_queue1.rbegin(), m_queue1.rend(), type);
+ auto pos2 = toQueue2(pos);
+ if (pos != m_queue1.rend() && pos2->getPayload() == aCallbackData.getPayload())
+ {
+ SAL_INFO("lok", "Skipping queue duplicate [" << type << + "]: [" << aCallbackData.getPayload() << "].");
+ return;
+ }
+ }
+ break;
+ }
+
+ if (type == LOK_CALLBACK_TEXT_SELECTION && aCallbackData.isEmpty())
+ {
+ const auto& posStart = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_START);
+ auto posStart2 = toQueue2(posStart);
+ if (posStart != m_queue1.rend())
+ posStart2->clear();
+
+ const auto& posEnd = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_END);
+ auto posEnd2 = toQueue2(posEnd);
+ if (posEnd != m_queue1.rend())
+ posEnd2->clear();
+ }
+
+ // When payload is empty discards any previous state.
+ if (aCallbackData.isEmpty())
+ {
+ switch (type)
+ {
+ case LOK_CALLBACK_TEXT_SELECTION_START:
+ case LOK_CALLBACK_TEXT_SELECTION_END:
+ case LOK_CALLBACK_TEXT_SELECTION:
+ case LOK_CALLBACK_GRAPHIC_SELECTION:
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ case LOK_CALLBACK_INVALIDATE_TILES:
+ if (removeAll(type))
+ SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "].");
+ break;
+ }
+ }
+ else
+ {
+ switch (type)
+ {
+ // These are safe to use the latest state and ignore previous
+ // ones (if any) since the last overrides previous ones.
+ case LOK_CALLBACK_TEXT_SELECTION_START:
+ case LOK_CALLBACK_TEXT_SELECTION_END:
+ case LOK_CALLBACK_TEXT_SELECTION:
+ case LOK_CALLBACK_MOUSE_POINTER:
+ case LOK_CALLBACK_CELL_CURSOR:
+ case LOK_CALLBACK_CELL_FORMULA:
+ case LOK_CALLBACK_CELL_ADDRESS:
+ case LOK_CALLBACK_CURSOR_VISIBLE:
+ case LOK_CALLBACK_SET_PART:
+ case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
+ case LOK_CALLBACK_RULER_UPDATE:
+ case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
+ case LOK_CALLBACK_A11Y_CARET_CHANGED:
+ case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
+ case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED:
+ case LOK_CALLBACK_COLOR_PALETTES:
+ {
+ if (removeAll(type))
+ SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "].");
+ }
+ break;
+
+ // These are safe to use the latest state and ignore previous
+ // ones (if any) since the last overrides previous ones,
+ // but only if the view is the same.
+ case LOK_CALLBACK_CELL_VIEW_CURSOR:
+ case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
+ case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
+ case LOK_CALLBACK_CALC_FUNCTION_LIST:
+ case LOK_CALLBACK_FORM_FIELD_BUTTON:
+ {
+ // deleting the duplicate of visible cursor message can cause hyperlink popup not to show up on second/or more click on the same place.
+ // If the hyperlink is not empty we can bypass that to show the popup
+ const bool hyperLinkException = type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR &&
+ aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1 &&
+ aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1;
+ if(!hyperLinkException)
+ {
+ const int nViewId = aCallbackData.getViewId();
+ removeAll(type, [nViewId] (const CallbackData& elemData) {
+ return (nViewId == elemData.getViewId());
+ }
+ );
+ }
+ }
+ break;
+
+ case LOK_CALLBACK_INVALIDATE_TILES:
+ if (processInvalidateTilesEvent(type, aCallbackData))
+ return;
+ break;
+
+ // State changes with same name override previous ones with a different value.
+ // Ex. ".uno:PageStatus=Slide 20 of 83" overwrites any previous PageStatus.
+ case LOK_CALLBACK_STATE_CHANGED:
+ {
+ // Compare the state name=value and overwrite earlier entries with same name.
+ const auto pos = aCallbackData.getPayload().indexOf('=');
+ if (pos != -1)
+ {
+ const std::string_view name = aCallbackData.getPayload().subView(0, pos + 1);
+ // This is needed because otherwise it creates some problems when
+ // a save occurs while a cell is still edited in Calc.
+ if (name != ".uno:ModifiedStatus=")
+ {
+ removeAll(type, [&name] (const CallbackData& elemData) {
+ return elemData.getPayload().startsWith(name);
+ }
+ );
+ }
+ }
+ }
+ break;
+
+ case LOK_CALLBACK_WINDOW:
+ if (processWindowEvent(type, aCallbackData))
+ return;
+ break;
+
+ case LOK_CALLBACK_GRAPHIC_SELECTION:
+ {
+ // remove only selection ranges and 'EMPTY' messages
+ // always send 'INPLACE' and 'INPLACE EXIT' messages
+ removeAll(type, [] (const CallbackData& elemData)
+ { return (elemData.getPayload().indexOf("INPLACE") == -1); });
+ }
+ break;
+ }
+ }
+
+ // Validate that the cached data and the payload string are identical.
+ assert(aCallbackData.validate() && "Cached callback payload object and string mismatch!");
+ m_queue1.emplace_back(type);
+ m_queue2.emplace_back(aCallbackData);
+ SAL_INFO("lok", "Queued #" << (m_queue1.size() - 1) <<
+ " [" << type << "]: [" << aCallbackData.getPayload() << "] to have " << m_queue1.size() << " entries.");
+
+#ifdef DBG_UTIL
+ {
+ // Dump the queue state and validate cached data.
+ int i = 1;
+ std::ostringstream oss;
+ if (m_queue1.empty())
+ oss << "Empty";
+ else
+ oss << m_queue1.size() << " items\n";
+ auto it1 = m_queue1.begin();
+ auto it2 = m_queue2.begin();
+ for (; it1 != m_queue1.end(); ++it1, ++it2)
+ oss << i++ << ": [" << *it1 << "] [" << it2->getPayload() << "].\n";
+ SAL_INFO("lok", "Current Queue: " << oss.str());
+ assert(
+ std::all_of(
+ m_queue2.begin(), m_queue2.end(),
+ [](const CallbackData& c) { return c.validate(); }));
+ }
+#endif
+
+ lock.unlock();
+ startTimer();
+}
+
+bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& aCallbackData)
+{
+ RectangleAndPart rcNew = aCallbackData.getRectangleAndPart();
+ if (rcNew.isEmpty())
+ {
+ SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "].");
+ return true;
+ }
+
+ // If we have to invalidate all tiles, we can skip any new tile invalidation.
+ // Find the last INVALIDATE_TILES entry, if any to see if it's invalidate-all.
+ const auto& pos
+ = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_INVALIDATE_TILES);
+ if (pos != m_queue1.rend())
+ {
+ auto pos2 = toQueue2(pos);
+ const RectangleAndPart& rcOld = pos2->getRectangleAndPart();
+ if (rcOld.isInfinite() && (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) &&
+ (rcOld.m_nMode == rcNew.m_nMode))
+ {
+ SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload()
+ << "] since all tiles need to be invalidated.");
+ return true;
+ }
+
+ if ((rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && (rcOld.m_nMode == rcNew.m_nMode))
+ {
+ // If fully overlapping.
+ if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle))
+ {
+ SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload()
+ << "] since overlaps existing all-parts.");
+ return true;
+ }
+ }
+ }
+
+ if (rcNew.isInfinite())
+ {
+ SAL_INFO("lok", "Have Empty [" << type << "]: [" << aCallbackData.getPayload()
+ << "] so removing all with part " << rcNew.m_nPart << ".");
+ removeAll(LOK_CALLBACK_INVALIDATE_TILES, [&rcNew](const CallbackData& elemData) {
+ // Remove exiting if new is all-encompassing, or if of the same part.
+ return ((rcNew.m_nPart == -1 || rcNew.m_nPart == elemData.getRectangleAndPart().m_nPart)
+ && (rcNew.m_nMode == elemData.getRectangleAndPart().m_nMode));
+ });
+ }
+ else
+ {
+ const auto rcOrig = rcNew;
+
+ SAL_INFO("lok", "Have [" << type << "]: [" << aCallbackData.getPayload() << "] so merging overlapping.");
+ removeAll(LOK_CALLBACK_INVALIDATE_TILES,[&rcNew](const CallbackData& elemData) {
+ const RectangleAndPart& rcOld = elemData.getRectangleAndPart();
+ if (rcNew.m_nPart != -1 && rcOld.m_nPart != -1 &&
+ (rcOld.m_nPart != rcNew.m_nPart || rcOld.m_nMode != rcNew.m_nMode))
+ {
+ SAL_INFO("lok", "Nothing to merge between new: "
+ << rcNew.toString() << ", and old: " << rcOld.toString());
+ return false;
+ }
+
+ if (rcNew.m_nPart == -1)
+ {
+ // Don't merge unless fully overlapped.
+ SAL_INFO("lok", "New " << rcNew.toString() << " has " << rcOld.toString()
+ << "?");
+ if (rcNew.m_aRectangle.Contains(rcOld.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode)
+ {
+ SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old "
+ << rcOld.toString() << ".");
+ return true;
+ }
+ }
+ else if (rcOld.m_nPart == -1)
+ {
+ // Don't merge unless fully overlapped.
+ SAL_INFO("lok", "Old " << rcOld.toString() << " has " << rcNew.toString()
+ << "?");
+ if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode)
+ {
+ SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old "
+ << rcOld.toString() << ".");
+ return true;
+ }
+ }
+ else
+ {
+ const tools::Rectangle rcOverlap
+ = rcNew.m_aRectangle.GetIntersection(rcOld.m_aRectangle);
+ const bool bOverlap = !rcOverlap.IsEmpty() && rcOld.m_nMode == rcNew.m_nMode;
+ SAL_INFO("lok", "Merging " << rcNew.toString() << " & " << rcOld.toString()
+ << " => " << rcOverlap.toString()
+ << " Overlap: " << bOverlap);
+ if (bOverlap)
+ {
+ rcNew.m_aRectangle.Union(rcOld.m_aRectangle);
+ SAL_INFO("lok", "Merged: " << rcNew.toString());
+ return true;
+ }
+ }
+
+ // Keep others.
+ return false;
+ });
+
+ if (rcNew.m_aRectangle != rcOrig.m_aRectangle)
+ {
+ SAL_INFO("lok", "Replacing: " << rcOrig.toString() << " by " << rcNew.toString());
+ if (rcNew.m_aRectangle.GetWidth() < rcOrig.m_aRectangle.GetWidth()
+ || rcNew.m_aRectangle.GetHeight() < rcOrig.m_aRectangle.GetHeight())
+ {
+ SAL_WARN("lok", "Error: merged rect smaller.");
+ }
+ }
+ }
+
+ aCallbackData.updateRectangleAndPart(rcNew);
+ // Queue this one.
+ return false;
+}
+
+bool CallbackFlushHandler::processWindowEvent(int type, CallbackData& aCallbackData)
+{
+ const OString& payload = aCallbackData.getPayload();
+
+ boost::property_tree::ptree& aTree = aCallbackData.setJson(std::string(payload));
+ const unsigned nLOKWindowId = aTree.get<unsigned>("id", 0);
+ const std::string aAction = aTree.get<std::string>("action", "");
+ if (aAction == "invalidate")
+ {
+ std::string aRectStr = aTree.get<std::string>("rectangle", "");
+ // no 'rectangle' field => invalidate all of the window =>
+ // remove all previous window part invalidations
+ if (aRectStr.empty())
+ {
+ removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) {
+ const boost::property_tree::ptree& aOldTree = elemData.getJson();
+ if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)
+ && aOldTree.get<std::string>("action", "") == "invalidate")
+ {
+ return true;
+ }
+ return false;
+ });
+ }
+ else
+ {
+ // if we have to invalidate all of the window, ignore
+ // any part invalidation message
+ bool invAllExist = false;
+ auto it1 = m_queue1.rbegin();
+ auto it2 = m_queue2.rbegin();
+ for (;it1 != m_queue1.rend(); ++it1, ++it2)
+ {
+ if (*it1 != LOK_CALLBACK_WINDOW)
+ continue;
+ const boost::property_tree::ptree& aOldTree = it2->getJson();
+ if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)
+ && aOldTree.get<std::string>("action", "") == "invalidate"
+ && aOldTree.get<std::string>("rectangle", "").empty())
+ {
+ invAllExist = true;
+ break;
+ }
+ }
+
+ // we found a invalidate-all window callback
+ if (invAllExist)
+ {
+ SAL_INFO("lok.dialog", "Skipping queue ["
+ << type << "]: [" << payload
+ << "] since whole window needs to be invalidated.");
+ return true;
+ }
+
+ std::istringstream aRectStream(aRectStr);
+ tools::Long nLeft, nTop, nWidth, nHeight;
+ char nComma;
+ aRectStream >> nLeft >> nComma >> nTop >> nComma >> nWidth >> nComma >> nHeight;
+ tools::Rectangle aNewRect(nLeft, nTop, nLeft + nWidth, nTop + nHeight);
+ bool currentIsRedundant = false;
+ removeAll(LOK_CALLBACK_WINDOW, [&aNewRect, &nLOKWindowId,
+ &currentIsRedundant](const CallbackData& elemData) {
+ const boost::property_tree::ptree& aOldTree = elemData.getJson();
+ if (aOldTree.get<std::string>("action", "") == "invalidate")
+ {
+ // Not possible that we encounter an empty rectangle here; we already handled this case above.
+ std::istringstream aOldRectStream(aOldTree.get<std::string>("rectangle", ""));
+ tools::Long nOldLeft, nOldTop, nOldWidth, nOldHeight;
+ char nOldComma;
+ aOldRectStream >> nOldLeft >> nOldComma >> nOldTop >> nOldComma >> nOldWidth
+ >> nOldComma >> nOldHeight;
+ const tools::Rectangle aOldRect = tools::Rectangle(
+ nOldLeft, nOldTop, nOldLeft + nOldWidth, nOldTop + nOldHeight);
+
+ if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
+ {
+ if (aNewRect == aOldRect)
+ {
+ SAL_INFO("lok.dialog", "Duplicate rect [" << aNewRect.toString()
+ << "]. Skipping new.");
+ // We have a rectangle in the queue already that makes the current Callback useless.
+ currentIsRedundant = true;
+ return false;
+ }
+ // new one engulfs the old one?
+ else if (aNewRect.Contains(aOldRect))
+ {
+ SAL_INFO("lok.dialog",
+ "New rect [" << aNewRect.toString() << "] engulfs old ["
+ << aOldRect.toString() << "]. Replacing old.");
+ return true;
+ }
+ // old one engulfs the new one?
+ else if (aOldRect.Contains(aNewRect))
+ {
+ SAL_INFO("lok.dialog",
+ "Old rect [" << aOldRect.toString() << "] engulfs new ["
+ << aNewRect.toString() << "]. Skipping new.");
+ // We have a rectangle in the queue already that makes the current Callback useless.
+ currentIsRedundant = true;
+ return false;
+ }
+ else
+ {
+ // Overlapping rects.
+ const tools::Rectangle aPreMergeRect = aNewRect;
+ aNewRect.Union(aOldRect);
+ SAL_INFO("lok.dialog", "Merging rects ["
+ << aPreMergeRect.toString() << "] & ["
+ << aOldRect.toString() << "] = ["
+ << aNewRect.toString()
+ << "]. Replacing old.");
+ return true;
+ }
+ }
+ }
+
+ // keep rest
+ return false;
+ });
+
+ // Do not enqueue if redundant.
+ if (currentIsRedundant)
+ return true;
+
+ aTree.put("rectangle", aNewRect.toString().getStr());
+ aCallbackData.setJson(aTree);
+ assert(aCallbackData.validate() && "Validation after setJson failed!");
+ }
+ }
+ else if (aAction == "created")
+ {
+ // Remove all previous actions on same dialog, if we are creating it anew.
+ removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) {
+ const boost::property_tree::ptree& aOldTree = elemData.getJson();
+ if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
+ return true;
+ return false;
+ });
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return false;
+ }
+
+#ifndef IOS
+ auto xClip = forceSetClipboardForCurrentView(m_pDocument);
+
+ uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(xClip);
+ pWindow->SetClipboard(xClipboard);
+#endif
+ }
+ else if (aAction == "size_changed")
+ {
+ // A size change is practically re-creation of the window.
+ // But at a minimum it's a full invalidation.
+ removeAll(LOK_CALLBACK_WINDOW, [&nLOKWindowId](const CallbackData& elemData) {
+ const boost::property_tree::ptree& aOldTree = elemData.getJson();
+ if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
+ {
+ const std::string aOldAction = aOldTree.get<std::string>("action", "");
+ if (aOldAction == "invalidate")
+ return true;
+ }
+ return false;
+ });
+ }
+
+ // Queue this one.
+ return false;
+}
+
+void CallbackFlushHandler::enqueueUpdatedTypes()
+{
+ if( m_updatedTypes.empty() && m_updatedTypesPerViewId.empty())
+ return;
+ assert(m_viewId >= 0);
+ SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
+ [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } );
+ assert(viewShell != nullptr);
+
+ // First move data to local structures, so that callbacks don't possibly modify it.
+ std::vector<bool> updatedTypes;
+ std::swap(updatedTypes, m_updatedTypes);
+ boost::container::flat_map<int, std::vector<PerViewIdData>> updatedTypesPerViewId;
+ std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId);
+
+ // Some types must always precede other types, for example
+ // LOK_CALLBACK_TEXT_SELECTION_START and LOK_CALLBACK_TEXT_SELECTION_END
+ // must always precede LOK_CALLBACK_TEXT_SELECTION if present.
+ // Only these types should be present (see isUpdatedType()) and should be processed in this order.
+ static const int orderedUpdatedTypes[] = {
+ LOK_CALLBACK_TEXT_SELECTION_START, LOK_CALLBACK_TEXT_SELECTION_END, LOK_CALLBACK_TEXT_SELECTION };
+ // Only these types should be present (see isUpdatedTypePerViewId()) and (as of now)
+ // the order doesn't matter.
+ static const int orderedUpdatedTypesPerViewId[] = {
+ LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
+ LOK_CALLBACK_INVALIDATE_VIEW_CURSOR,
+ LOK_CALLBACK_TEXT_VIEW_SELECTION };
+
+ for( int type : orderedUpdatedTypes )
+ {
+ if(o3tl::make_unsigned( type ) < updatedTypes.size() && updatedTypes[ type ])
+ {
+ enqueueUpdatedType( type, viewShell, m_viewId );
+ }
+ }
+ for( const auto& it : updatedTypesPerViewId )
+ {
+ int viewId = it.first;
+ const std::vector<PerViewIdData>& types = it.second;
+ for( int type : orderedUpdatedTypesPerViewId )
+ {
+ if(o3tl::make_unsigned( type ) < types.size() && types[ type ].set)
+ {
+ SfxViewShell* sourceViewShell = viewShell;
+ const int sourceViewId = types[ type ].sourceViewId;
+ if( sourceViewId != m_viewId )
+ {
+ assert(sourceViewId >= 0);
+ sourceViewShell = SfxViewShell::GetFirst( false,
+ [sourceViewId](const SfxViewShell* shell) { return shell->GetViewShellId().get() == sourceViewId; } );
+ }
+ if(sourceViewShell == nullptr)
+ {
+ SAL_INFO("lok", "View #" << sourceViewId << " no longer found for updated event [" << type << "]");
+ continue; // View removed, probably cleaning up.
+ }
+ enqueueUpdatedType( type, sourceViewShell, viewId );
+ }
+ }
+ }
+}
+
+void CallbackFlushHandler::enqueueUpdatedType( int type, const SfxViewShell* viewShell, int viewId )
+{
+ if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR)
+ {
+ if (const SfxViewShell* viewShell2 = LokStarMathHelper(viewShell).GetSmViewShell())
+ viewShell = viewShell2;
+ }
+ std::optional<OString> payload = viewShell->getLOKPayload( type, viewId );
+ if(!payload)
+ return; // No actual payload to send.
+ CallbackData callbackData(*payload, viewId);
+ m_queue1.emplace_back(type);
+ m_queue2.emplace_back(callbackData);
+ SAL_INFO("lok", "Queued updated [" << type << "]: [" << callbackData.getPayload()
+ << "] to have " << m_queue1.size() << " entries.");
+}
+
+void CallbackFlushHandler::Invoke()
+{
+ comphelper::ProfileZone aZone("CallbackFlushHandler::Invoke");
+
+ if (!m_pCallback)
+ return;
+
+ // Get any pending invalidate tile events. This will call our callbacks,
+ // so it must be done before taking the mutex.
+ assert(m_viewId >= 0);
+ if(SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
+ [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } ))
+ {
+ viewShell->flushPendingLOKInvalidateTiles();
+ }
+
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+
+ // Append messages for updated types, fetch them only now.
+ enqueueUpdatedTypes();
+
+ SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements.");
+ auto it1 = m_queue1.begin();
+ auto it2 = m_queue2.begin();
+ for (; it1 != m_queue1.end(); ++it1, ++it2)
+ {
+ const int type = *it1;
+ const auto& payload = it2->getPayload();
+ const int viewId = lcl_isViewCallbackType(type) ? it2->getViewId() : -1;
+
+ SAL_INFO("lok", "processing event: [" << type << ',' << viewId << "]: [" << payload << "].");
+
+ // common code-path for events on this view:
+ if (viewId == -1)
+ {
+ sal_Int32 idx;
+ // key-value pairs
+ if (type == LOK_CALLBACK_STATE_CHANGED &&
+ (idx = payload.indexOf('=')) != -1)
+ {
+ OString key = payload.copy(0, idx);
+ OString value = payload.copy(idx+1);
+ const auto stateIt = m_lastStateChange.find(key);
+ if (stateIt != m_lastStateChange.end())
+ {
+ // If the value didn't change, it's safe to ignore.
+ if (stateIt->second == value)
+ {
+ SAL_INFO("lok", "Skipping new state duplicate: [" << type << "]: [" << payload << "].");
+ continue;
+ }
+ SAL_INFO("lok", "Replacing a state element [" << type << "]: [" << payload << "].");
+ stateIt->second = value;
+ }
+ else
+ {
+ SAL_INFO("lok", "Inserted a new state element: [" << type << "]: [" << payload << "]");
+ m_lastStateChange.emplace(key, value);
+ }
+ }
+ else
+ {
+ const auto stateIt = m_states.find(type);
+ if (stateIt != m_states.end())
+ {
+ // If the state didn't change, it's safe to ignore.
+ if (stateIt->second == payload)
+ {
+ SAL_INFO("lok", "Skipping duplicate [" << type << "]: [" << payload << "].");
+ continue;
+ }
+ stateIt->second = payload;
+ }
+ }
+ }
+ else // less common path for events relating to other views
+ {
+ const auto statesIt = m_viewStates.find(viewId);
+ if (statesIt != m_viewStates.end())
+ {
+ auto& states = statesIt->second;
+ const auto stateIt = states.find(type);
+ if (stateIt != states.end())
+ {
+ // If the state didn't change, it's safe to ignore.
+ if (stateIt->second == payload)
+ {
+ SAL_INFO("lok", "Skipping view duplicate [" << type << ',' << viewId << "]: [" << payload << "].");
+ continue;
+ }
+
+ SAL_INFO("lok", "Replacing an element in view states [" << type << ',' << viewId << "]: [" << payload << "].");
+ stateIt->second = payload;
+ }
+ else
+ {
+ SAL_INFO("lok", "Inserted a new element in view states: [" << type << ',' << viewId << "]: [" << payload << "]");
+ states.emplace(type, payload);
+
+ }
+ }
+ }
+
+ m_pCallback(type, payload.getStr(), m_pData);
+ }
+
+ m_queue1.clear();
+ m_queue2.clear();
+ Stop();
+ m_TimeoutIdle.Stop();
+}
+
+void CallbackFlushHandler::startTimer()
+{
+ if (!IsActive())
+ Start();
+ if (!m_TimeoutIdle.IsActive())
+ m_TimeoutIdle.Start();
+}
+
+bool CallbackFlushHandler::removeAll(int type)
+{
+ bool bErased = false;
+ auto it1 = m_queue1.begin();
+ for(;;)
+ {
+ it1 = std::find(it1, m_queue1.end(), type);
+ if(it1 == m_queue1.end())
+ break;
+ m_queue2.erase(toQueue2(it1));
+ it1 = m_queue1.erase(it1);
+ bErased = true;
+ }
+ return bErased;
+}
+
+bool CallbackFlushHandler::removeAll(int type, const std::function<bool (const CallbackData&)>& rTestFunc)
+{
+ bool bErased = false;
+ auto it1 = m_queue1.begin();
+ for(;;)
+ {
+ it1 = std::find(it1, m_queue1.end(), type);
+ if(it1 == m_queue1.end())
+ break;
+ auto it2 = toQueue2(it1);
+ if (rTestFunc(*it2))
+ {
+ m_queue2.erase(it2);
+ it1 = m_queue1.erase(it1);
+ bErased = true;
+ }
+ else
+ ++it1;
+ }
+ return bErased;
+}
+
+void CallbackFlushHandler::addViewStates(int viewId)
+{
+ const auto& result = m_viewStates.emplace(viewId, decltype(m_viewStates)::mapped_type());
+ if (!result.second && result.first != m_viewStates.end())
+ {
+ result.first->second.clear();
+ }
+}
+
+void CallbackFlushHandler::removeViewStates(int viewId)
+{
+ m_viewStates.erase(viewId);
+}
+
+
+static void doc_destroy(LibreOfficeKitDocument *pThis)
+{
+ comphelper::ProfileZone aZone("doc_destroy");
+
+ SolarMutexGuard aGuard;
+
+#ifndef IOS
+ LOKClipboardFactory::releaseClipboardForView(-1);
+#endif
+
+ LibLODocument_Impl *pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ delete pDocument;
+}
+
+static void lo_destroy (LibreOfficeKit* pThis);
+static int lo_initialize (LibreOfficeKit* pThis, const char* pInstallPath, const char* pUserProfilePath);
+static LibreOfficeKitDocument* lo_documentLoad (LibreOfficeKit* pThis, const char* pURL);
+static char * lo_getError (LibreOfficeKit* pThis);
+static void lo_freeError (char* pFree);
+static LibreOfficeKitDocument* lo_documentLoadWithOptions (LibreOfficeKit* pThis,
+ const char* pURL,
+ const char* pOptions);
+static void lo_registerCallback (LibreOfficeKit* pThis,
+ LibreOfficeKitCallback pCallback,
+ void* pData);
+static char* lo_getFilterTypes(LibreOfficeKit* pThis);
+static void lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long features);
+static void lo_setDocumentPassword(LibreOfficeKit* pThis,
+ const char* pURL,
+ const char* pPassword);
+static char* lo_getVersionInfo(LibreOfficeKit* pThis);
+static int lo_runMacro (LibreOfficeKit* pThis, const char* pURL);
+
+static bool lo_signDocument(LibreOfficeKit* pThis,
+ const char* pUrl,
+ const unsigned char* pCertificateBinary,
+ const int nCertificateBinarySize,
+ const unsigned char* pPrivateKeyBinary,
+ const int nPrivateKeyBinarySize);
+
+static char* lo_extractRequest(LibreOfficeKit* pThis,
+ const char* pFilePath);
+
+static void lo_trimMemory(LibreOfficeKit* pThis, int nTarget);
+
+static void*
+lo_startURP(LibreOfficeKit* pThis, void* pReceiveURPFromLOContext, void* pSendURPToLOContext,
+ int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
+ int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen));
+
+static void lo_stopURP(LibreOfficeKit* pThis, void* pSendURPToLOContext);
+
+static void lo_runLoop(LibreOfficeKit* pThis,
+ LibreOfficeKitPollCallback pPollCallback,
+ LibreOfficeKitWakeCallback pWakeCallback,
+ void* pData);
+
+static void lo_sendDialogEvent(LibreOfficeKit* pThis,
+ unsigned long long int nLOKWindowId,
+ const char* pArguments);
+
+static void lo_setOption(LibreOfficeKit* pThis, const char* pOption, const char* pValue);
+
+static void lo_dumpState(LibreOfficeKit* pThis, const char* pOptions, char** pState);
+
+LibLibreOffice_Impl::LibLibreOffice_Impl()
+ : m_pOfficeClass( gOfficeClass.lock() )
+ , maThread(nullptr)
+ , mpCallback(nullptr)
+ , mpCallbackData(nullptr)
+ , mOptionalFeatures(0)
+{
+ if(!m_pOfficeClass) {
+ m_pOfficeClass = std::make_shared<LibreOfficeKitClass>();
+ m_pOfficeClass->nSize = sizeof(LibreOfficeKitClass);
+
+ m_pOfficeClass->destroy = lo_destroy;
+ m_pOfficeClass->documentLoad = lo_documentLoad;
+ m_pOfficeClass->getError = lo_getError;
+ m_pOfficeClass->freeError = lo_freeError;
+ m_pOfficeClass->documentLoadWithOptions = lo_documentLoadWithOptions;
+ m_pOfficeClass->registerCallback = lo_registerCallback;
+ m_pOfficeClass->getFilterTypes = lo_getFilterTypes;
+ m_pOfficeClass->setOptionalFeatures = lo_setOptionalFeatures;
+ m_pOfficeClass->setDocumentPassword = lo_setDocumentPassword;
+ m_pOfficeClass->getVersionInfo = lo_getVersionInfo;
+ m_pOfficeClass->runMacro = lo_runMacro;
+ m_pOfficeClass->signDocument = lo_signDocument;
+ m_pOfficeClass->runLoop = lo_runLoop;
+ m_pOfficeClass->sendDialogEvent = lo_sendDialogEvent;
+ m_pOfficeClass->setOption = lo_setOption;
+ m_pOfficeClass->dumpState = lo_dumpState;
+ m_pOfficeClass->extractRequest = lo_extractRequest;
+ m_pOfficeClass->trimMemory = lo_trimMemory;
+ m_pOfficeClass->startURP = lo_startURP;
+ m_pOfficeClass->stopURP = lo_stopURP;
+
+ gOfficeClass = m_pOfficeClass;
+ }
+
+ pClass = m_pOfficeClass.get();
+}
+
+LibLibreOffice_Impl::~LibLibreOffice_Impl()
+{
+}
+
+namespace
+{
+
+void setLanguageAndLocale(OUString const & aLangISO)
+{
+ SvtSysLocaleOptions aLocalOptions;
+ aLocalOptions.SetLocaleConfigString(aLangISO);
+ aLocalOptions.SetUILocaleConfigString(aLangISO);
+ aLocalOptions.Commit();
+}
+
+void setFormatSpecificFilterData(std::u16string_view sFormat, comphelper::SequenceAsHashMap & rFilterDataMap)
+{
+ if (sFormat == u"pdf")
+ {
+ // always export bookmarks, which is needed for annotations
+ rFilterDataMap[u"ExportBookmarks"_ustr] <<= true;
+ }
+}
+
+} // anonymous namespace
+
+// Wonder global state ...
+static uno::Reference<css::uno::XComponentContext> xContext;
+static uno::Reference<css::lang::XMultiServiceFactory> xSFactory;
+static uno::Reference<css::lang::XMultiComponentFactory> xFactory;
+
+static LibreOfficeKitDocument* lo_documentLoad(LibreOfficeKit* pThis, const char* pURL)
+{
+ return lo_documentLoadWithOptions(pThis, pURL, nullptr);
+}
+
+static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, const char* pURL, const char* pOptions)
+{
+ comphelper::ProfileZone aZone("lo_documentLoadWithOptions");
+
+ SolarMutexGuard aGuard;
+
+ static int nDocumentIdCounter = 0;
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ pLib->maLastExceptionMsg.clear();
+
+ const OUString aURL(getAbsoluteURL(pURL));
+ if (aURL.isEmpty())
+ {
+ pLib->maLastExceptionMsg = u"Filename to load was not provided."_ustr;
+ SAL_INFO("lok", "URL for load is empty");
+ return nullptr;
+ }
+
+ pLib->maLastExceptionMsg.clear();
+
+ if (!xContext.is())
+ {
+ pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr;
+ SAL_INFO("lok", "ComponentContext is not available");
+ return nullptr;
+ }
+
+ uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
+
+ if (!xComponentLoader.is())
+ {
+ pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr;
+ SAL_INFO("lok", "ComponentLoader is not available");
+ return nullptr;
+ }
+
+ try
+ {
+ // 'Language=...' is an option that LOK consumes by itself, and does
+ // not pass it as a parameter to the filter
+ OUString aOptions = getUString(pOptions);
+ const OUString aLanguage = extractParameter(aOptions, u"Language");
+
+ if (!aLanguage.isEmpty() && LanguageTag::isValidBcp47(aLanguage, nullptr))
+ {
+ static bool isLoading = true;
+ if (isLoading)
+ {
+ // Capture the language used to load the document.
+ SfxLokHelper::setLoadLanguage(aLanguage);
+ isLoading = false;
+ }
+
+ SfxLokHelper::setDefaultLanguage(aLanguage);
+ // Set the LOK language tag, used for dialog tunneling.
+ comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage));
+ comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage));
+
+ SAL_INFO("lok", "Set document language to " << aLanguage);
+ // use with care - it sets it for the entire core, not just the
+ // document
+ setLanguageAndLocale(aLanguage);
+ // Need to reset the static initialized values
+ SvNumberFormatter::resetTheCurrencyTable();
+ }
+
+ // Set the timezone, if not empty.
+ const OUString aTimezone = extractParameter(aOptions, u"Timezone");
+ if (!aTimezone.isEmpty())
+ {
+ SfxLokHelper::setDefaultTimezone(true, aTimezone);
+ }
+ else
+ {
+ // Default to the TZ envar, if set.
+ const char* tz = ::getenv("TZ");
+ if (tz)
+ {
+ SfxLokHelper::setDefaultTimezone(true,
+ OStringToOUString(tz, RTL_TEXTENCODING_UTF8));
+ }
+ else
+ {
+ SfxLokHelper::setDefaultTimezone(false, OUString());
+ }
+ }
+
+ const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor");
+ SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor);
+
+ const OUString aBatch = extractParameter(aOptions, u"Batch");
+ if (!aBatch.isEmpty())
+ {
+ Application::SetDialogCancelMode(DialogCancelMode::LOKSilent);
+ }
+
+ const OUString sFilterOptions = aOptions;
+
+ rtl::Reference<LOKInteractionHandler> const pInteraction(
+ new LOKInteractionHandler("load"_ostr, pLib));
+ auto const pair(pLib->mInteractionMap.insert(std::make_pair(aURL.toUtf8(), pInteraction)));
+ comphelper::ScopeGuard const g([&] () {
+ if (pair.second)
+ {
+ pLib->mInteractionMap.erase(aURL.toUtf8());
+ }
+ });
+ uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
+
+ int nMacroSecurityLevel = 1;
+ const OUString aMacroSecurityLevel = extractParameter(aOptions, u"MacroSecurityLevel");
+ if (!aMacroSecurityLevel.isEmpty())
+ {
+ double nNumber;
+ sal_uInt32 nFormat = 1;
+ SvNumberFormatter aFormatter(::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
+ if (aFormatter.IsNumberFormat(aMacroSecurityLevel, nFormat, nNumber))
+ nMacroSecurityLevel = static_cast<int>(nNumber);
+ }
+ SvtSecurityOptions::SetMacroSecurityLevel(nMacroSecurityLevel);
+
+#if defined(ANDROID) && HAVE_FEATURE_ANDROID_LOK
+ sal_Int16 nMacroExecMode = document::MacroExecMode::USE_CONFIG;
+#else
+ const OUString aEnableMacrosExecution = extractParameter(aOptions, u"EnableMacrosExecution");
+ sal_Int16 nMacroExecMode = aEnableMacrosExecution == "true" ? document::MacroExecMode::USE_CONFIG :
+ document::MacroExecMode::NEVER_EXECUTE;
+#endif
+
+ // set AsTemplate explicitly false to be able to load template files
+ // as regular files, otherwise we cannot save them; it will try
+ // to bring saveas dialog which cannot work with LOK case
+ uno::Sequence<css::beans::PropertyValue> aFilterOptions{
+ comphelper::makePropertyValue(u"FilterOptions"_ustr, sFilterOptions),
+ comphelper::makePropertyValue(u"InteractionHandler"_ustr, xInteraction),
+ comphelper::makePropertyValue(u"MacroExecutionMode"_ustr, nMacroExecMode),
+ comphelper::makePropertyValue(u"AsTemplate"_ustr, false),
+ comphelper::makePropertyValue(u"Silent"_ustr, !aBatch.isEmpty())
+ };
+
+ /* TODO
+ sal_Int16 nUpdateDoc = document::UpdateDocMode::ACCORDING_TO_CONFIG;
+ aFilterOptions[3].Name = "UpdateDocMode";
+ aFilterOptions[3].Value <<= nUpdateDoc;
+ */
+
+ OutputDevice::StartTrackingFontMappingUse();
+
+ const int nThisDocumentId = nDocumentIdCounter++;
+ SfxViewShell::SetCurrentDocId(ViewShellDocId(nThisDocumentId));
+ uno::Reference<lang::XComponent> xComponent = xComponentLoader->loadComponentFromURL(
+ aURL, u"_blank"_ustr, 0,
+ aFilterOptions);
+
+ assert(!xComponent.is() || pair.second); // concurrent loading of same URL ought to fail
+
+ if (!xComponent.is())
+ {
+ pLib->maLastExceptionMsg = u"loadComponentFromURL returned an empty reference"_ustr;
+ SAL_INFO("lok", "Document can't be loaded - " << pLib->maLastExceptionMsg);
+ return nullptr;
+ }
+
+ LibLODocument_Impl* pDocument = new LibLODocument_Impl(xComponent, nThisDocumentId);
+
+ // After loading the document, its initial view is the "current" view.
+ if (pLib->mpCallback)
+ {
+ int nState = doc_getSignatureState(pDocument);
+ pLib->mpCallback(LOK_CALLBACK_SIGNATURE_STATUS, OString::number(nState).getStr(), pLib->mpCallbackData);
+ }
+
+ auto aFontMappingUseData = OutputDevice::FinishTrackingFontMappingUse();
+
+ if (aFontMappingUseData.size() > 0)
+ {
+ SAL_INFO("lok.fontsubst", "================ Original substitutions:");
+ for (const auto &i : aFontMappingUseData)
+ {
+ SAL_INFO("lok.fontsubst", i.mOriginalFont);
+ for (const auto &j : i.mUsedFonts)
+ SAL_INFO("lok.fontsubst", " " << j);
+ }
+ }
+
+ // Filter out font substitutions that actually aren't any substitutions, like "Liberation
+ // Serif" -> "Liberation Serif/Regular". If even one of the "substitutions" of a font is to
+ // the same font, don't count that as a missing font.
+
+ std::erase_if
+ (aFontMappingUseData,
+ [](OutputDevice::FontMappingUseItem x)
+ {
+ // If the original font had an empty style and one of its
+ // replacement fonts has the same family name, we assume the font is
+ // present. The root problem here is that the code that collects
+ // font substitutions tends to get just empty styles for the font
+ // that is being substituted, as vcl::Font::GetStyleName() tends to
+ // return an empty string. (Italicness is instead indicated by what
+ // vcl::Font::GetItalic() returns and boldness by what
+ // vcl::Font::GetWeight() returns.)
+
+ if (x.mOriginalFont.indexOf('/') == -1)
+ for (const auto &j : x.mUsedFonts)
+ if (j == x.mOriginalFont ||
+ j.startsWith(Concat2View(x.mOriginalFont + "/")))
+ return true;
+
+ return false;
+ });
+
+ // Filter out substitutions where a proprietary font has been substituted by a
+ // metric-compatible one. Obviously this is just a heuristic and implemented only for some
+ // well-known cases.
+
+ std::erase_if
+ (aFontMappingUseData,
+ [](OutputDevice::FontMappingUseItem x)
+ {
+ // Again, handle only cases where the original font does not include
+ // a style. Unclear whether there ever will be a style part included
+ // in the mOriginalFont.
+
+ if (x.mOriginalFont.indexOf('/') == -1)
+ for (const auto &j : x.mUsedFonts)
+ if ((x.mOriginalFont == "Arial" &&
+ j.startsWith("Liberation Sans/")) ||
+ (x.mOriginalFont == "Times New Roman" &&
+ j.startsWith("Liberation Serif/")) ||
+ (x.mOriginalFont == "Courier New" &&
+ j.startsWith("Liberation Mono/")) ||
+ (x.mOriginalFont == "Arial Narrow" &&
+ j.startsWith("Liberation Sans Narrow/")) ||
+ (x.mOriginalFont == "Cambria" &&
+ j.startsWith("Caladea/")) ||
+ (x.mOriginalFont == "Calibri" &&
+ j.startsWith("Carlito/")) ||
+ (x.mOriginalFont == "Palatino Linotype" &&
+ j.startsWith("P052/")) ||
+ // Perhaps a risky heuristic? If some glyphs from Symbol
+ // have been mapped to ones in OpenSymbol, don't warn
+ // that Symbol is missing.
+ (x.mOriginalFont == "Symbol" &&
+ j.startsWith("OpenSymbol/")))
+ {
+ return true;
+ }
+
+ return false;
+ });
+
+ if (aFontMappingUseData.size() > 0)
+ {
+ SAL_INFO("lok.fontsubst", "================ Pruned substitutions:");
+ for (const auto &i : aFontMappingUseData)
+ {
+ SAL_INFO("lok.fontsubst", i.mOriginalFont);
+ for (const auto &j : i.mUsedFonts)
+ SAL_INFO("lok.fontsubst", " " << j);
+ }
+ }
+
+ for (std::size_t i = 0; i < aFontMappingUseData.size(); ++i)
+ {
+ pDocument->maFontsMissing.insert(aFontMappingUseData[i].mOriginalFont);
+ }
+
+ return pDocument;
+ }
+ catch (const uno::Exception& exception)
+ {
+ pLib->maLastExceptionMsg = exception.Message;
+ TOOLS_INFO_EXCEPTION("lok", "Document can't be loaded");
+ }
+
+ return nullptr;
+}
+
+static int lo_runMacro(LibreOfficeKit* pThis, const char *pURL)
+{
+ comphelper::ProfileZone aZone("lo_runMacro");
+
+ SolarMutexGuard aGuard;
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ pLib->maLastExceptionMsg.clear();
+
+ OUString sURL( pURL, strlen(pURL), RTL_TEXTENCODING_UTF8 );
+ if (sURL.isEmpty())
+ {
+ pLib->maLastExceptionMsg = u"Macro to run was not provided."_ustr;
+ SAL_INFO("lok", "Macro URL is empty");
+ return false;
+ }
+
+ if (!sURL.startsWith("macro://"))
+ {
+ pLib->maLastExceptionMsg = u"This doesn't look like macro URL"_ustr;
+ SAL_INFO("lok", "Macro URL is invalid");
+ return false;
+ }
+
+ pLib->maLastExceptionMsg.clear();
+
+ if (!xContext.is())
+ {
+ pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr;
+ SAL_INFO("lok", "ComponentContext is not available");
+ return false;
+ }
+
+ util::URL aURL;
+ aURL.Complete = sURL;
+
+ uno::Reference < util::XURLTransformer > xParser( util::URLTransformer::create( xContext ) );
+
+ if( xParser.is() )
+ xParser->parseStrict( aURL );
+
+ uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
+
+ if (!xComponentLoader.is())
+ {
+ pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr;
+ SAL_INFO("lok", "ComponentLoader is not available");
+ return false;
+ }
+
+ xFactory = xContext->getServiceManager();
+
+ if (!xFactory)
+ return false;
+
+ uno::Reference<frame::XDispatchProvider> xDP;
+ xSFactory.set(xFactory, uno::UNO_QUERY_THROW);
+ xDP.set( xSFactory->createInstance(u"com.sun.star.comp.sfx2.SfxMacroLoader"_ustr), uno::UNO_QUERY );
+ uno::Reference<frame::XDispatch> xD = xDP->queryDispatch( aURL, OUString(), 0);
+
+ if (!xD.is())
+ {
+ pLib->maLastExceptionMsg = u"Macro loader is not available"_ustr;
+ SAL_INFO("lok", "Macro loader is not available");
+ return false;
+ }
+
+ uno::Reference < frame::XSynchronousDispatch > xSyncDisp( xD, uno::UNO_QUERY_THROW );
+ uno::Sequence<css::beans::PropertyValue> aEmpty;
+ css::beans::PropertyValue aErr;
+ uno::Any aRet = xSyncDisp->dispatchWithReturnValue( aURL, aEmpty );
+ aRet >>= aErr;
+
+ if (aErr.Name == "ErrorCode")
+ {
+ sal_uInt32 nErrCode = 0; // ERRCODE_NONE
+ aErr.Value >>= nErrCode;
+
+ pLib->maLastExceptionMsg = "An error occurred running macro (error code: " + OUString::number( nErrCode ) + ")";
+ SAL_INFO("lok", "Macro execution terminated with error code " << nErrCode);
+
+ return false;
+ }
+
+ return true;
+}
+
+static bool lo_signDocument(LibreOfficeKit* /*pThis*/,
+ const char* pURL,
+ const unsigned char* pCertificateBinary,
+ const int nCertificateBinarySize,
+ const unsigned char* pPrivateKeyBinary,
+ const int nPrivateKeyBinarySize)
+{
+ comphelper::ProfileZone aZone("lo_signDocument");
+
+ OUString aURL(getAbsoluteURL(pURL));
+ if (aURL.isEmpty())
+ return false;
+
+ if (!xContext.is())
+ return false;
+
+ uno::Sequence<sal_Int8> aCertificateSequence;
+
+ std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
+ std::string aCertificateBase64String = extractCertificate(aCertificateString);
+ if (!aCertificateBase64String.empty())
+ {
+ OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
+ comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
+ }
+ else
+ {
+ aCertificateSequence.realloc(nCertificateBinarySize);
+ std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
+ }
+
+ uno::Sequence<sal_Int8> aPrivateKeySequence;
+ std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeyBinarySize);
+ std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString);
+ if (!aPrivateKeyBase64String.empty())
+ {
+ OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
+ comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
+ }
+ else
+ {
+ aPrivateKeySequence.realloc(nPrivateKeyBinarySize);
+ std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeyBinarySize, aPrivateKeySequence.getArray());
+ }
+
+ uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
+ uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
+ if (!xSecurityContext.is())
+ return false;
+
+ uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
+ uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
+
+ if (!xCertificateCreator.is())
+ return false;
+
+ uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
+
+ if (!xCertificate.is())
+ return false;
+
+ sfx2::DocumentSigner aDocumentSigner(aURL);
+ if (!aDocumentSigner.signDocument(xCertificate))
+ return false;
+
+ return true;
+}
+
+
+static char* lo_extractRequest(LibreOfficeKit* /*pThis*/, const char* pFilePath)
+{
+ uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
+ uno::Reference< css::lang::XComponent > xComp;
+ OUString aURL(getAbsoluteURL(pFilePath));
+ if (!aURL.isEmpty())
+ {
+ if (xComponentLoader.is())
+ {
+ try
+ {
+ uno::Sequence<css::beans::PropertyValue> aFilterOptions(comphelper::InitPropertySequence(
+ {
+ {u"Hidden"_ustr, css::uno::Any(true)},
+ {u"ReadOnly"_ustr, css::uno::Any(true)}
+ }));
+ xComp = xComponentLoader->loadComponentFromURL( aURL, u"_blank"_ustr, 0, aFilterOptions );
+ }
+ catch ( const lang::IllegalArgumentException& ex )
+ {
+ SAL_WARN("lok", "lo_extractRequest: IllegalArgumentException: " << ex.Message);
+ }
+ catch (...)
+ {
+ SAL_WARN("lok", "lo_extractRequest: Exception on loadComponentFromURL, url= " << aURL);
+ }
+
+ if (xComp.is())
+ {
+ uno::Reference< document::XLinkTargetSupplier > xLTS( xComp, uno::UNO_QUERY );
+
+ if( xLTS.is() )
+ {
+ tools::JsonWriter aJson;
+ {
+ auto aNode = aJson.startNode("Targets");
+ extractLinks(xLTS->getLinks(), false, aJson);
+ }
+ return convertOString(aJson.finishAndGetAsOString());
+ }
+ xComp->dispose();
+ }
+ }
+ }
+ return strdup("{ }");
+}
+
+static void lo_trimMemory(LibreOfficeKit* /* pThis */, int nTarget)
+{
+ vcl::lok::trimMemory(nTarget);
+
+ if (nTarget > 2000)
+ {
+ SolarMutexGuard aGuard;
+
+ // Flush all buffered VOC primitives from the pages.
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (pViewShell)
+ {
+ const SdrView* pView = pViewShell->GetDrawView();
+ if (pView)
+ {
+ SdrPageView* pPageView = pView->GetSdrPageView();
+ if (pPageView)
+ {
+ SdrPage* pCurPage = pPageView->GetPage();
+ if (pCurPage)
+ {
+ SdrModel& sdrModel = pCurPage->getSdrModelFromSdrPage();
+ for (sal_uInt16 i = 0; i < sdrModel.GetPageCount(); ++i)
+ {
+ SdrPage* pPage = sdrModel.GetPage(i);
+ if (pPage)
+ pPage->GetViewContact().flushViewObjectContacts();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (nTarget > 1000)
+ {
+#ifdef HAVE_MALLOC_TRIM
+ malloc_trim(0);
+#endif
+ }
+}
+
+namespace
+{
+class FunctionBasedURPInstanceProvider
+ : public ::cppu::WeakImplHelper<css::bridge::XInstanceProvider>
+{
+private:
+ css::uno::Reference<css::uno::XComponentContext> m_rContext;
+
+public:
+ FunctionBasedURPInstanceProvider(
+ const css::uno::Reference<css::uno::XComponentContext>& rxContext);
+
+ // XInstanceProvider
+ virtual css::uno::Reference<css::uno::XInterface>
+ SAL_CALL getInstance(const OUString& aName) override;
+};
+
+// InstanceProvider
+FunctionBasedURPInstanceProvider::FunctionBasedURPInstanceProvider(
+ const Reference<XComponentContext>& rxContext)
+ : m_rContext(rxContext)
+{
+}
+
+Reference<XInterface> FunctionBasedURPInstanceProvider::getInstance(const OUString& aName)
+{
+ Reference<XInterface> rInstance;
+
+ if (aName == "StarOffice.ServiceManager")
+ {
+ rInstance.set(m_rContext->getServiceManager());
+ }
+ else if (aName == "StarOffice.ComponentContext")
+ {
+ rInstance = m_rContext;
+ }
+ else if (aName == "StarOffice.NamingService")
+ {
+ Reference<XNamingService> rNamingService(
+ m_rContext->getServiceManager()->createInstanceWithContext(
+ u"com.sun.star.uno.NamingService"_ustr, m_rContext),
+ UNO_QUERY);
+ if (rNamingService.is())
+ {
+ rNamingService->registerObject(u"StarOffice.ServiceManager"_ustr,
+ m_rContext->getServiceManager());
+ rNamingService->registerObject(u"StarOffice.ComponentContext"_ustr, m_rContext);
+ rInstance = rNamingService;
+ }
+ }
+ return rInstance;
+}
+
+class FunctionBasedURPConnection : public cppu::WeakImplHelper<css::connection::XConnection>
+{
+public:
+ explicit FunctionBasedURPConnection(void*, int (*)(void* pContext, const signed char* pBuffer, int nLen),
+ void*, int (*)(void* pContext, signed char* pBuffer, int nLen));
+ ~FunctionBasedURPConnection();
+
+ // These overridden member functions use "read" and "write" from the point of view of LO,
+ // i.e. the opposite to how startURP() uses them.
+ virtual sal_Int32 SAL_CALL read(Sequence<sal_Int8>& rReadBytes,
+ sal_Int32 nBytesToRead) override;
+ virtual void SAL_CALL write(const Sequence<sal_Int8>& aData) override;
+ virtual void SAL_CALL flush() override;
+ virtual void SAL_CALL close() override;
+ virtual OUString SAL_CALL getDescription() override;
+ void setBridge(Reference<XBridge>);
+ void* getContext();
+ inline static int g_connectionCount = 0;
+
+private:
+ void* m_pRecieveFromLOContext;
+ void* m_pSendURPToLOContext;
+ int (*m_fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen);
+ int (*m_fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen);
+ Reference<XBridge> m_URPBridge;
+};
+
+FunctionBasedURPConnection::FunctionBasedURPConnection(
+ void* pRecieveFromLOContext,
+ int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
+ void* pSendURPToLOContext,
+ int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen))
+ : m_pRecieveFromLOContext(pRecieveFromLOContext)
+ , m_pSendURPToLOContext(pSendURPToLOContext)
+ , m_fnReceiveURPFromLO(fnReceiveURPFromLO)
+ , m_fnSendURPToLO(fnSendURPToLO)
+{
+ g_connectionCount++;
+}
+
+FunctionBasedURPConnection::~FunctionBasedURPConnection()
+{
+ Reference<XComponent> xComp(m_URPBridge, UNO_QUERY_THROW);
+ xComp->dispose(); // TODO: check this doesn't deadlock
+}
+
+void* FunctionBasedURPConnection::getContext() { return this; }
+
+sal_Int32 FunctionBasedURPConnection::read(Sequence<sal_Int8>& rReadBytes, sal_Int32 nBytesToRead)
+{
+ if (nBytesToRead < 0)
+ return 0;
+
+ if (rReadBytes.getLength() != nBytesToRead)
+ rReadBytes.realloc(nBytesToRead);
+
+ // As with osl::StreamPipe, we must always read nBytesToRead...
+ return m_fnSendURPToLO(m_pSendURPToLOContext, rReadBytes.getArray(), nBytesToRead);
+}
+
+void FunctionBasedURPConnection::write(const Sequence<sal_Int8>& rData)
+{
+ m_fnReceiveURPFromLO(m_pRecieveFromLOContext, rData.getConstArray(), rData.getLength());
+}
+
+void FunctionBasedURPConnection::flush() {}
+
+void FunctionBasedURPConnection::close()
+{
+ SAL_INFO("lok.urp", "Requested to close FunctionBasedURPConnection");
+}
+
+OUString FunctionBasedURPConnection::getDescription() { return ""; }
+
+void FunctionBasedURPConnection::setBridge(Reference<XBridge> xBridge) { m_URPBridge = xBridge; }
+}
+
+static void*
+lo_startURP(LibreOfficeKit* /* pThis */, void* pRecieveFromLOContext, void* pSendToLOContext,
+ int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
+ int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen))
+{
+ // Here we will roughly do what desktop LO does when one passes a command-line switch like
+ // --accept=socket,port=nnnn;urp;StarOffice.ServiceManager. Except that no listening socket will
+ // be created. The communication to the URP will be through the nReceiveURPFromLO and nSendURPToLO
+ // functions.
+
+ rtl::Reference<FunctionBasedURPConnection> connection(
+ new FunctionBasedURPConnection(pRecieveFromLOContext, fnReceiveURPFromLO,
+ pSendToLOContext, fnSendURPToLO));
+
+ Reference<XBridgeFactory> xBridgeFactory = css::bridge::BridgeFactory::create(xContext);
+
+ Reference<XInstanceProvider> xInstanceProvider(new FunctionBasedURPInstanceProvider(xContext));
+
+ Reference<XBridge> xBridge(xBridgeFactory->createBridge(
+ "functionurp" + OUString::number(FunctionBasedURPConnection::g_connectionCount), u"urp"_ustr,
+ connection, xInstanceProvider));
+
+ connection->setBridge(std::move(xBridge));
+
+ return connection->getContext();
+}
+
+/**
+ * Stop a function based URP connection that you started with lo_startURP above
+ *
+ * @param pSendToLOContext a pointer to the context returned by lo_startURP */
+static void lo_stopURP(LibreOfficeKit* /* pThis */,
+ void* pFunctionBasedURPConnection/* FunctionBasedURPConnection* */)
+{
+ static_cast<FunctionBasedURPConnection*>(pFunctionBasedURPConnection)->close();
+}
+
+static void lo_registerCallback (LibreOfficeKit* pThis,
+ LibreOfficeKitCallback pCallback,
+ void* pData)
+{
+ SolarMutexGuard aGuard;
+
+ Application* pApp = GetpApp();
+ assert(pApp);
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ pLib->maLastExceptionMsg.clear();
+
+ pApp->m_pCallback = pLib->mpCallback = pCallback;
+ pApp->m_pCallbackData = pLib->mpCallbackData = pData;
+}
+
+static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* sUrl, const char* pFormat, const char* pFilterOptions)
+{
+ comphelper::ProfileZone aZone("doc_saveAs");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ OUString sFormat = getUString(pFormat);
+ OUString aURL(getAbsoluteURL(sUrl));
+
+ uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
+
+ if (aURL.isEmpty())
+ {
+ SetLastExceptionMsg(u"Filename to save to was not provided."_ustr);
+ SAL_INFO("lok", "URL for save is empty");
+ return false;
+ }
+
+ try
+ {
+ std::span<const ExtensionMap> pMap;
+
+ switch (doc_getDocumentType(pThis))
+ {
+ case LOK_DOCTYPE_SPREADSHEET:
+ pMap = aCalcExtensionMap;
+ break;
+ case LOK_DOCTYPE_PRESENTATION:
+ pMap = aImpressExtensionMap;
+ break;
+ case LOK_DOCTYPE_DRAWING:
+ pMap = aDrawExtensionMap;
+ break;
+ case LOK_DOCTYPE_TEXT:
+ pMap = aWriterExtensionMap;
+ break;
+ case LOK_DOCTYPE_OTHER:
+ default:
+ SAL_INFO("lok", "Can't save document - unsupported document type.");
+ return false;
+ }
+
+ if (pFormat == nullptr)
+ {
+ // sniff from the extension
+ sal_Int32 idx = aURL.lastIndexOf(".");
+ if( idx > 0 )
+ {
+ sFormat = aURL.copy( idx + 1 );
+ }
+ else
+ {
+ SetLastExceptionMsg("input URL '" + aURL + "' lacks a suffix");
+ return false;
+ }
+ }
+
+ OUString aFilterName;
+ for (const auto& item : pMap)
+ {
+ if (sFormat.equalsIgnoreAsciiCaseAscii(item.extn))
+ {
+ aFilterName = item.filterName;
+ break;
+ }
+ }
+ if (aFilterName.isEmpty())
+ {
+ SetLastExceptionMsg(u"no output filter found for provided suffix"_ustr);
+ return false;
+ }
+
+ OUString aFilterOptions = getUString(pFilterOptions);
+
+ // Check if watermark for pdf is passed by filteroptions...
+ // It is not a real filter option so it must be filtered out.
+ OUString watermarkText;
+ std::u16string_view sFullSheetPreview;
+ int aIndex = -1;
+ if ((aIndex = aFilterOptions.indexOf(",Watermark=")) >= 0)
+ {
+ int bIndex = aFilterOptions.indexOf("WATERMARKEND");
+ watermarkText = aFilterOptions.subView(aIndex+11, bIndex-(aIndex+11));
+ aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+12);
+ }
+
+ if ((aIndex = aFilterOptions.indexOf(",FullSheetPreview=")) >= 0)
+ {
+ int bIndex = aFilterOptions.indexOf("FULLSHEETPREVEND");
+ sFullSheetPreview = aFilterOptions.subView(aIndex+18, bIndex-(aIndex+18));
+ aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+16);
+ }
+
+ bool bFullSheetPreview = sFullSheetPreview == u"true";
+
+ OUString filePassword;
+ if ((aIndex = aFilterOptions.indexOf(",Password=")) >= 0)
+ {
+ int bIndex = aFilterOptions.indexOf("PASSWORDEND");
+ filePassword = aFilterOptions.subView(aIndex + 10, bIndex - (aIndex + 10));
+ aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex))
+ + aFilterOptions.subView(bIndex + 11);
+ }
+ OUString filePasswordToModify;
+ if ((aIndex = aFilterOptions.indexOf(",PasswordToModify=")) >= 0)
+ {
+ int bIndex = aFilterOptions.indexOf("PASSWORDTOMODIFYEND");
+ filePassword = aFilterOptions.subView(aIndex + 18, bIndex - (aIndex + 18));
+ aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex))
+ + aFilterOptions.subView(bIndex + 19);
+ }
+
+ // Select a pdf version if specified a valid one. If not specified then ignore.
+ // If invalid then fail.
+ sal_Int32 pdfVer = 0;
+ if ((aIndex = aFilterOptions.indexOf(",PDFVer=")) >= 0)
+ {
+ int bIndex = aFilterOptions.indexOf("PDFVEREND");
+ std::u16string_view sPdfVer = aFilterOptions.subView(aIndex+8, bIndex-(aIndex+8));
+ aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+9);
+
+ if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-1b"))
+ pdfVer = 1;
+ else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-2b"))
+ pdfVer = 2;
+ else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-3b"))
+ pdfVer = 3;
+ else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.5"))
+ pdfVer = 15;
+ else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.6"))
+ pdfVer = 16;
+ else
+ {
+ SetLastExceptionMsg(u"wrong PDF version"_ustr);
+ return false;
+ }
+ }
+
+ // 'TakeOwnership' == this is a 'real' SaveAs (that is, the document
+ // gets a new name). When this is not provided, the meaning of
+ // saveAs() is more like save-a-copy, which allows saving to any
+ // random format like PDF or PNG.
+ // It is not a real filter option, so we have to filter it out.
+ const uno::Sequence<OUString> aOptionSeq = comphelper::string::convertCommaSeparated(aFilterOptions);
+ std::vector<OUString> aFilteredOptionVec;
+ bool bTakeOwnership = false;
+ MediaDescriptor aSaveMediaDescriptor;
+ for (const auto& rOption : aOptionSeq)
+ {
+ if (rOption == "TakeOwnership")
+ bTakeOwnership = true;
+ else if (rOption == "NoFileSync")
+ aSaveMediaDescriptor[u"NoFileSync"_ustr] <<= true;
+ else
+ aFilteredOptionVec.push_back(rOption);
+ }
+
+ aSaveMediaDescriptor[u"Overwrite"_ustr] <<= true;
+ aSaveMediaDescriptor[u"FilterName"_ustr] <<= aFilterName;
+
+ auto aFilteredOptionSeq = comphelper::containerToSequence<OUString>(aFilteredOptionVec);
+ aFilterOptions = comphelper::string::convertCommaSeparated(aFilteredOptionSeq);
+ aSaveMediaDescriptor[MediaDescriptor::PROP_FILTEROPTIONS] <<= aFilterOptions;
+
+ comphelper::SequenceAsHashMap aFilterDataMap;
+
+ // If filter options is JSON string, then make sure aFilterDataMap stays empty, otherwise we
+ // would ignore the filter options.
+ if (!aFilterOptions.startsWith("{"))
+ {
+ setFormatSpecificFilterData(sFormat, aFilterDataMap);
+ }
+
+ if (!watermarkText.isEmpty())
+ aFilterDataMap[u"TiledWatermark"_ustr] <<= watermarkText;
+
+ if (bFullSheetPreview)
+ aFilterDataMap[u"SinglePageSheets"_ustr] <<= true;
+
+ if (pdfVer)
+ aFilterDataMap[u"SelectPdfVersion"_ustr] <<= pdfVer;
+
+ if (!aFilterDataMap.empty())
+ {
+ aSaveMediaDescriptor[u"FilterData"_ustr] <<= aFilterDataMap.getAsConstPropertyValueList();
+ }
+ if (!filePassword.isEmpty())
+ aSaveMediaDescriptor[u"Password"_ustr] <<= filePassword;
+ if (!filePasswordToModify.isEmpty())
+ aSaveMediaDescriptor[u"PasswordToModify"_ustr] <<= filePasswordToModify;
+
+ // add interaction handler too
+ if (gImpl)
+ {
+ // gImpl does not have to exist when running from a unit test
+ rtl::Reference<LOKInteractionHandler> const pInteraction(
+ new LOKInteractionHandler("saveas"_ostr, gImpl, pDocument));
+ uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
+
+ aSaveMediaDescriptor[MediaDescriptor::PROP_INTERACTIONHANDLER] <<= xInteraction;
+ }
+
+
+ if (bTakeOwnership)
+ xStorable->storeAsURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList());
+ else
+ xStorable->storeToURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList());
+
+ return true;
+ }
+ catch (const uno::Exception& exception)
+ {
+ SetLastExceptionMsg("exception: " + exception.Message);
+ }
+ return false;
+}
+
+/**
+ * Initialize UNO commands, in the sense that from now on, the LOK client gets updates for status
+ * changes of these commands. This is necessary, because (unlike in the desktop case) there are no
+ * toolbars hosting widgets these UNO commands, so no such status updates would be sent to the
+ * headless LOK clients out of the box.
+ */
+static void doc_iniUnoCommands ()
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ static constexpr OUString sUnoCommands[] =
+ {
+ u".uno:AlignLeft"_ustr,
+ u".uno:AlignHorizontalCenter"_ustr,
+ u".uno:AlignRight"_ustr,
+ u".uno:BackColor"_ustr,
+ u".uno:BackgroundColor"_ustr,
+ u".uno:TableCellBackgroundColor"_ustr,
+ u".uno:Bold"_ustr,
+ u".uno:CenterPara"_ustr,
+ u".uno:CharBackColor"_ustr,
+ u".uno:CharBackgroundExt"_ustr,
+ u".uno:CharFontName"_ustr,
+ u".uno:Color"_ustr,
+ u".uno:ControlCodes"_ustr,
+ u".uno:DecrementIndent"_ustr,
+ u".uno:DefaultBullet"_ustr,
+ u".uno:DefaultNumbering"_ustr,
+ u".uno:FontColor"_ustr,
+ u".uno:FontHeight"_ustr,
+ u".uno:IncrementIndent"_ustr,
+ u".uno:Italic"_ustr,
+ u".uno:JustifyPara"_ustr,
+ u".uno:JumpToMark"_ustr,
+ u".uno:OutlineFont"_ustr,
+ u".uno:LeftPara"_ustr,
+ u".uno:LanguageStatus"_ustr,
+ u".uno:RightPara"_ustr,
+ u".uno:Shadowed"_ustr,
+ u".uno:SubScript"_ustr,
+ u".uno:SuperScript"_ustr,
+ u".uno:Strikeout"_ustr,
+ u".uno:StyleApply"_ustr,
+ u".uno:Underline"_ustr,
+ u".uno:ModifiedStatus"_ustr,
+ u".uno:Undo"_ustr,
+ u".uno:Redo"_ustr,
+ u".uno:InsertPage"_ustr,
+ u".uno:DeletePage"_ustr,
+ u".uno:DuplicatePage"_ustr,
+ u".uno:InsertSlide"_ustr,
+ u".uno:DeleteSlide"_ustr,
+ u".uno:DuplicateSlide"_ustr,
+ u".uno:ChangeTheme"_ustr,
+ u".uno:Cut"_ustr,
+ u".uno:Copy"_ustr,
+ u".uno:Paste"_ustr,
+ u".uno:SelectAll"_ustr,
+ u".uno:ReplyComment"_ustr,
+ u".uno:ResolveComment"_ustr,
+ u".uno:ResolveCommentThread"_ustr,
+ u".uno:InsertRowsBefore"_ustr,
+ u".uno:InsertRowsAfter"_ustr,
+ u".uno:InsertColumnsBefore"_ustr,
+ u".uno:InsertColumnsAfter"_ustr,
+ u".uno:DeleteRows"_ustr,
+ u".uno:DeleteColumns"_ustr,
+ u".uno:DeleteTable"_ustr,
+ u".uno:SelectTable"_ustr,
+ u".uno:EntireRow"_ustr,
+ u".uno:EntireColumn"_ustr,
+ u".uno:EntireCell"_ustr,
+ u".uno:AssignLayout"_ustr,
+ u".uno:StatusDocPos"_ustr,
+ u".uno:RowColSelCount"_ustr,
+ u".uno:StatusPageStyle"_ustr,
+ u".uno:InsertMode"_ustr,
+ u".uno:SpellOnline"_ustr,
+ u".uno:StatusSelectionMode"_ustr,
+ u".uno:StateTableCell"_ustr,
+ u".uno:StatusBarFunc"_ustr,
+ u".uno:StatePageNumber"_ustr,
+ u".uno:StateWordCount"_ustr,
+ u".uno:SelectionMode"_ustr,
+ u".uno:PageStatus"_ustr,
+ u".uno:LayoutStatus"_ustr,
+ u".uno:Scale"_ustr,
+ u".uno:Context"_ustr,
+ u".uno:WrapText"_ustr,
+ u".uno:ToggleMergeCells"_ustr,
+ u".uno:NumberFormatCurrency"_ustr,
+ u".uno:NumberFormatPercent"_ustr,
+ u".uno:NumberFormatDecimal"_ustr,
+ u".uno:NumberFormatIncDecimals"_ustr,
+ u".uno:NumberFormatDecDecimals"_ustr,
+ u".uno:NumberFormatDate"_ustr,
+ u".uno:EditHeaderAndFooter"_ustr,
+ u".uno:FrameLineColor"_ustr,
+ u".uno:SortAscending"_ustr,
+ u".uno:SortDescending"_ustr,
+ u".uno:TrackChanges"_ustr,
+ u".uno:ShowTrackedChanges"_ustr,
+ u".uno:NextTrackedChange"_ustr,
+ u".uno:PreviousTrackedChange"_ustr,
+ u".uno:AcceptAllTrackedChanges"_ustr,
+ u".uno:RejectAllTrackedChanges"_ustr,
+ u".uno:TableDialog"_ustr,
+ u".uno:FormatCellDialog"_ustr,
+ u".uno:FontDialog"_ustr,
+ u".uno:ParagraphDialog"_ustr,
+ u".uno:OutlineBullet"_ustr,
+ u".uno:InsertIndexesEntry"_ustr,
+ u".uno:DocumentRepair"_ustr,
+ u".uno:TransformDialog"_ustr,
+ u".uno:InsertPageHeader"_ustr,
+ u".uno:InsertPageFooter"_ustr,
+ u".uno:OnlineAutoFormat"_ustr,
+ u".uno:InsertObjectChart"_ustr,
+ u".uno:InsertSection"_ustr,
+ u".uno:InsertAnnotation"_ustr,
+ u".uno:DeleteAnnotation"_ustr,
+ u".uno:InsertPagebreak"_ustr,
+ u".uno:InsertColumnBreak"_ustr,
+ u".uno:HyperlinkDialog"_ustr,
+ u".uno:InsertSymbol"_ustr,
+ u".uno:EditRegion"_ustr,
+ u".uno:ThesaurusDialog"_ustr,
+ u".uno:FormatArea"_ustr,
+ u".uno:FormatLine"_ustr,
+ u".uno:FormatColumns"_ustr,
+ u".uno:Watermark"_ustr,
+ u".uno:ResetAttributes"_ustr,
+ u".uno:Orientation"_ustr,
+ u".uno:ObjectAlignLeft"_ustr,
+ u".uno:ObjectAlignRight"_ustr,
+ u".uno:AlignCenter"_ustr,
+ u".uno:TransformPosX"_ustr,
+ u".uno:TransformPosY"_ustr,
+ u".uno:TransformWidth"_ustr,
+ u".uno:TransformHeight"_ustr,
+ u".uno:ObjectBackOne"_ustr,
+ u".uno:SendToBack"_ustr,
+ u".uno:ObjectForwardOne"_ustr,
+ u".uno:BringToFront"_ustr,
+ u".uno:WrapRight"_ustr,
+ u".uno:WrapThrough"_ustr,
+ u".uno:WrapLeft"_ustr,
+ u".uno:WrapIdeal"_ustr,
+ u".uno:WrapOn"_ustr,
+ u".uno:WrapOff"_ustr,
+ u".uno:UpdateCurIndex"_ustr,
+ u".uno:InsertCaptionDialog"_ustr,
+ u".uno:FormatGroup"_ustr,
+ u".uno:SplitTable"_ustr,
+ u".uno:SplitCell"_ustr,
+ u".uno:MergeCells"_ustr,
+ u".uno:DeleteNote"_ustr,
+ u".uno:AcceptChanges"_ustr,
+ u".uno:FormatPaintbrush"_ustr,
+ u".uno:SetDefault"_ustr,
+ u".uno:ParaLeftToRight"_ustr,
+ u".uno:ParaRightToLeft"_ustr,
+ u".uno:ParaspaceIncrease"_ustr,
+ u".uno:ParaspaceDecrease"_ustr,
+ u".uno:AcceptTrackedChange"_ustr,
+ u".uno:RejectTrackedChange"_ustr,
+ u".uno:ShowResolvedAnnotations"_ustr,
+ u".uno:InsertBreak"_ustr,
+ u".uno:InsertEndnote"_ustr,
+ u".uno:InsertFootnote"_ustr,
+ u".uno:InsertReferenceField"_ustr,
+ u".uno:InsertBookmark"_ustr,
+ u".uno:InsertAuthoritiesEntry"_ustr,
+ u".uno:InsertMultiIndex"_ustr,
+ u".uno:InsertField"_ustr,
+ u".uno:PageNumberWizard"_ustr,
+ u".uno:InsertPageNumberField"_ustr,
+ u".uno:InsertPageCountField"_ustr,
+ u".uno:InsertDateField"_ustr,
+ u".uno:InsertTitleField"_ustr,
+ u".uno:InsertFieldCtrl"_ustr,
+ u".uno:CharmapControl"_ustr,
+ u".uno:EnterGroup"_ustr,
+ u".uno:LeaveGroup"_ustr,
+ u".uno:AlignUp"_ustr,
+ u".uno:AlignMiddle"_ustr,
+ u".uno:AlignDown"_ustr,
+ u".uno:TraceChangeMode"_ustr,
+ u".uno:Combine"_ustr,
+ u".uno:Merge"_ustr,
+ u".uno:Dismantle"_ustr,
+ u".uno:Substract"_ustr,
+ u".uno:DistributeSelection"_ustr,
+ u".uno:Intersect"_ustr,
+ u".uno:BorderInner"_ustr,
+ u".uno:BorderOuter"_ustr,
+ u".uno:FreezePanes"_ustr,
+ u".uno:FreezePanesColumn"_ustr,
+ u".uno:FreezePanesRow"_ustr,
+ u".uno:Sidebar"_ustr,
+ u".uno:SheetRightToLeft"_ustr,
+ u".uno:RunMacro"_ustr,
+ u".uno:SpacePara1"_ustr,
+ u".uno:SpacePara15"_ustr,
+ u".uno:SpacePara2"_ustr,
+ u".uno:InsertSparkline"_ustr,
+ u".uno:DeleteSparkline"_ustr,
+ u".uno:DeleteSparklineGroup"_ustr,
+ u".uno:EditSparklineGroup"_ustr,
+ u".uno:EditSparkline"_ustr,
+ u".uno:GroupSparklines"_ustr,
+ u".uno:UngroupSparklines"_ustr,
+ u".uno:FormatSparklineMenu"_ustr,
+ u".uno:DataDataPilotRun"_ustr,
+ u".uno:RecalcPivotTable"_ustr,
+ u".uno:DeletePivotTable"_ustr,
+ u".uno:Protect"_ustr,
+ u".uno:UnsetCellsReadOnly"_ustr,
+ u".uno:ContentControlProperties"_ustr,
+ u".uno:InsertCheckboxContentControl"_ustr,
+ u".uno:InsertContentControl"_ustr,
+ u".uno:InsertDateContentControl"_ustr,
+ u".uno:InsertDropdownContentControl"_ustr,
+ u".uno:InsertPlainTextContentControl"_ustr,
+ u".uno:InsertPictureContentControl"_ustr,
+ u".uno:DataFilterAutoFilter"_ustr,
+ u".uno:CellProtection"_ustr,
+ };
+
+ util::URL aCommandURL;
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
+
+ // check if Frame-Controller were created.
+ if (!pViewFrame)
+ {
+ SAL_WARN("lok", "iniUnoCommands: No Frame-Controller created.");
+ return;
+ }
+
+ if (!xContext.is())
+ xContext = comphelper::getProcessComponentContext();
+ if (!xContext.is())
+ {
+ SAL_WARN("lok", "iniUnoCommands: Component context is not available");
+ return;
+ }
+
+#if !defined IOS && !defined ANDROID && !defined __EMSCRIPTEN__
+ uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
+ if (!xSEInitializer.is())
+ {
+ SAL_WARN("lok", "iniUnoCommands: XSEInitializer is not available");
+ return;
+ }
+
+ uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext =
+ xSEInitializer->createSecurityContext(OUString());
+ if (!xSecurityContext.is())
+ {
+ SAL_WARN("lok", "iniUnoCommands: failed to create security context");
+ }
+#endif
+
+ SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame);
+ uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(xContext));
+
+ for (const auto & sUnoCommand : sUnoCommands)
+ {
+ aCommandURL.Complete = sUnoCommand;
+ xParser->parseStrict(aCommandURL);
+
+ // when null, this command is not supported by the given component
+ // (like eg. Calc does not have ".uno:DefaultBullet" etc.)
+ if (const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path))
+ {
+ // Initialize slot to dispatch .uno: Command.
+ pViewFrame->GetBindings().GetDispatch(pSlot, aCommandURL, false);
+ }
+ }
+}
+
+static int doc_getDocumentType (LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getDocumentType");
+
+ SolarMutexGuard aGuard;
+ return getDocumentType(pThis);
+}
+
+static int doc_getParts (LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getParts");
+
+ SolarMutexGuard aGuard;
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return 0;
+ }
+
+ return pDoc->getParts();
+}
+
+static int doc_getPart (LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getPart");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return 0;
+ }
+
+ return pDoc->getPart();
+}
+
+static void doc_setPartImpl(LibreOfficeKitDocument* pThis, int nPart, bool bAllowChangeFocus = true)
+{
+ comphelper::ProfileZone aZone("doc_setPart");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->setPart( nPart, bAllowChangeFocus );
+}
+
+static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart)
+{
+ doc_setPartImpl(pThis, nPart, true);
+}
+
+static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart)
+{
+ comphelper::ProfileZone aZone("doc_getPartInfo");
+
+ SolarMutexGuard aGuard;
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ return convertOUString(pDoc->getPartInfo(nPart));
+}
+
+static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->selectPart( nPart, nSelect );
+}
+
+static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->moveSelectedParts(nPosition, bDuplicate);
+}
+
+static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getPartPageRectangles");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ return convertOUString(pDoc->getPartPageRectangles());
+}
+
+static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ {
+ return convertOUString(pViewShell->getA11yFocusedParagraph());
+
+ }
+ return nullptr;
+}
+
+static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return -1;
+ }
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ {
+ return pViewShell->getA11yCaretPosition();
+
+ }
+ return -1;
+
+}
+
+static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart)
+{
+ comphelper::ProfileZone aZone("doc_getPartName");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ return convertOUString(pDoc->getPartName(nPart));
+}
+
+static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart)
+{
+ comphelper::ProfileZone aZone("doc_getPartHash");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ return convertOUString(pDoc->getPartHash(nPart));
+}
+
+static void doc_setPartMode(LibreOfficeKitDocument* pThis,
+ int nPartMode)
+{
+ comphelper::ProfileZone aZone("doc_setPartMode");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+
+ int nCurrentPart = pDoc->getPart();
+
+ pDoc->setPartMode(nPartMode);
+
+ // We need to make sure the internal state is updated, just changing the mode
+ // might not update the relevant shells (i.e. impress will keep rendering the
+ // previous mode unless we do this).
+ // TODO: we might want to do this within the relevant components rather than
+ // here, but that's also dependent on how we implement embedded object
+ // rendering I guess?
+ // TODO: we could be clever and e.g. set to 0 when we change to/from
+ // embedded object mode, and not when changing between slide/notes/combined
+ // modes?
+ if ( nCurrentPart < pDoc->getParts() )
+ {
+ pDoc->setPart( nCurrentPart );
+ }
+ else
+ {
+ pDoc->setPart( 0 );
+ }
+}
+
+static int doc_getEditMode(LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getEditMode");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return 0;
+ }
+
+ return pDoc->getEditMode();
+}
+
+static void doc_paintTile(LibreOfficeKitDocument* pThis,
+ unsigned char* pBuffer,
+ const int nCanvasWidth, const int nCanvasHeight,
+ const int nTilePosX, const int nTilePosY,
+ const int nTileWidth, const int nTileHeight)
+{
+ comphelper::ProfileZone aZone("doc_paintTile");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ SAL_INFO( "lok.tiledrendering", "paintTile: painting [" << nTileWidth << "x" << nTileHeight <<
+ "]@(" << nTilePosX << ", " << nTilePosY << ") to [" <<
+ nCanvasWidth << "x" << nCanvasHeight << "]px" );
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+#if defined(UNX) && !defined(MACOSX) || defined(_WIN32)
+
+ // Painting of zoomed or HiDPI spreadsheets is special, we actually draw everything at 100%,
+ // and only set cairo's (or CoreGraphic's, in the iOS case) scale factor accordingly, so that
+ // everything is painted bigger or smaller. This is different to what Calc's internal scaling
+ // would do - because that one is trying to fit the lines between cells to integer multiples of
+ // pixels.
+ comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); });
+
+#if defined(IOS)
+ double fDPIScale = 1.0;
+
+ // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags
+ // to kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big
+ CGContextRef pCGContext = CGBitmapContextCreate(pBuffer, nCanvasWidth, nCanvasHeight, 8,
+ nCanvasWidth * 4, CGColorSpaceCreateDeviceRGB(),
+ kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big);
+
+ CGContextTranslateCTM(pCGContext, 0, nCanvasHeight);
+ CGContextScaleCTM(pCGContext, fDPIScale, -fDPIScale);
+
+ SAL_INFO( "lok.tiledrendering", "doc_paintTile: painting [" << nTileWidth << "x" << nTileHeight <<
+ "]@(" << nTilePosX << ", " << nTilePosY << ") to [" <<
+ nCanvasWidth << "x" << nCanvasHeight << "]px" );
+
+ Size aCanvasSize(nCanvasWidth, nCanvasHeight);
+
+ SystemGraphicsData aData;
+ aData.rCGContext = reinterpret_cast<CGContextRef>(pCGContext);
+
+ ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixel(aCanvasSize);
+ pDoc->paintTile(*pDevice, aCanvasSize.Width(), aCanvasSize.Height(),
+ nTilePosX, nTilePosY, nTileWidth, nTileHeight);
+
+ CGContextRelease(pCGContext);
+#else
+ ScopedVclPtrInstance< VirtualDevice > pDevice(DeviceFormat::WITHOUT_ALPHA);
+
+ // Set background to transparent by default.
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+
+ pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(
+ Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(),
+ pBuffer);
+
+ pDoc->paintTile(*pDevice, nCanvasWidth, nCanvasHeight,
+ nTilePosX, nTilePosY, nTileWidth, nTileHeight);
+
+ static bool bDebug = getenv("LOK_DEBUG_TILES") != nullptr;
+ if (bDebug)
+ {
+ // Draw a small red rectangle in the top left corner so that it's easy to see where a new tile begins.
+ tools::Rectangle aRect(0, 0, 5, 5);
+ aRect = pDevice->PixelToLogic(aRect);
+ pDevice->Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ pDevice->SetFillColor(COL_LIGHTRED);
+ pDevice->SetLineColor();
+ pDevice->DrawRect(aRect);
+ pDevice->Pop();
+ }
+
+#ifdef _WIN32
+ // pBuffer was not used there
+ pDevice->EnableMapMode(false);
+ BitmapEx aBmpEx = pDevice->GetBitmapEx({ 0, 0 }, { nCanvasWidth, nCanvasHeight });
+ Bitmap aBmp = aBmpEx.GetBitmap();
+ AlphaMask aAlpha = aBmpEx.GetAlphaMask();
+ BitmapScopedReadAccess sraBmp(aBmp);
+ BitmapScopedReadAccess sraAlpha(aAlpha);
+
+ assert(sraBmp->Height() == nCanvasHeight);
+ assert(sraBmp->Width() == nCanvasWidth);
+ assert(!sraAlpha || sraBmp->Height() == sraAlpha->Height());
+ assert(!sraAlpha || sraBmp->Width() == sraAlpha->Width());
+ auto p = pBuffer;
+ for (tools::Long y = 0; y < sraBmp->Height(); ++y)
+ {
+ Scanline dataBmp = sraBmp->GetScanline(y);
+ Scanline dataAlpha = sraAlpha ? sraAlpha->GetScanline(y) : nullptr;
+ for (tools::Long x = 0; x < sraBmp->Width(); ++x)
+ {
+ BitmapColor color = sraBmp->GetPixelFromData(dataBmp, x);
+ sal_uInt8 alpha = dataAlpha ? sraAlpha->GetPixelFromData(dataAlpha, x).GetBlue() : 255;
+ *p++ = color.GetBlue();
+ *p++ = color.GetGreen();
+ *p++ = color.GetRed();
+ *p++ = alpha;
+ }
+ }
+#endif
+#endif
+
+#else
+ (void) pBuffer;
+#endif
+}
+
+static void doc_paintPartTile(LibreOfficeKitDocument* pThis,
+ unsigned char* pBuffer,
+ const int nPart,
+ const int nMode,
+ const int nCanvasWidth, const int nCanvasHeight,
+ const int nTilePosX, const int nTilePosY,
+ const int nTileWidth, const int nTileHeight)
+{
+ comphelper::ProfileZone aZone("doc_paintPartTile");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ SAL_INFO( "lok.tiledrendering", "paintPartTile: painting @ " << nPart << " : " << nMode << " ["
+ << nTileWidth << "x" << nTileHeight << "]@("
+ << nTilePosX << ", " << nTilePosY << ") to ["
+ << nCanvasWidth << "x" << nCanvasHeight << "]px" );
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ int nOrigViewId = doc_getView(pThis);
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ if (nOrigViewId < 0)
+ {
+ // tile painting always needs a SfxViewShell::Current(), but actually
+ // it does not really matter which one - all of them should paint the
+ // same thing. It's important to get a view for the correct document,
+ // though.
+ // doc_getViewsCount() returns the count of views for the document in the current view.
+ int viewCount = doc_getViewsCount(pThis);
+ if (viewCount == 0)
+ return;
+
+ std::vector<int> viewIds(viewCount);
+ doc_getViewIds(pThis, viewIds.data(), viewCount);
+
+ nOrigViewId = viewIds[0];
+ doc_setView(pThis, nOrigViewId);
+ }
+
+ // Disable callbacks while we are painting.
+ if (nOrigViewId >= 0)
+ {
+ const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId);
+ if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
+ handlerIt->second->disableCallbacks();
+ }
+
+ try
+ {
+ // Text documents have a single coordinate system; don't change part.
+ int nOrigPart = 0;
+ const int aType = doc_getDocumentType(pThis);
+ const bool isText = (aType == LOK_DOCTYPE_TEXT);
+ const bool isCalc = (aType == LOK_DOCTYPE_SPREADSHEET);
+ int nOrigEditMode = 0;
+ bool bPaintTextEdit = true;
+ int nViewId = nOrigViewId;
+ int nLastNonEditorView = -1;
+ int nViewMatchingMode = -1;
+ SfxViewShell* pCurrentViewShell = SfxViewShell::Current();
+
+ if (!isText)
+ {
+ // Check if just switching to another view is enough, that has
+ // less side-effects.
+ if (nPart != doc_getPart(pThis) || nMode != pDoc->getEditMode())
+ {
+ SfxViewShell* pViewShell = SfxViewShell::GetFirst();
+ while (pViewShell)
+ {
+ bool bIsInEdit = pViewShell->GetDrawView() &&
+ pViewShell->GetDrawView()->GetTextEditOutliner();
+
+ OString sCurrentViewRenderState = pDoc->getViewRenderState(pCurrentViewShell);
+ OString sNewRenderState = pDoc->getViewRenderState(pViewShell);
+
+ if (sCurrentViewRenderState == sNewRenderState && !bIsInEdit)
+ nLastNonEditorView = pViewShell->GetViewShellId().get();
+
+ if (pViewShell->getPart() == nPart &&
+ pViewShell->getEditMode() == nMode &&
+ sCurrentViewRenderState == sNewRenderState &&
+ !bIsInEdit)
+ {
+ nViewId = pViewShell->GetViewShellId().get();
+ nViewMatchingMode = nViewId;
+ nLastNonEditorView = nViewId;
+ doc_setView(pThis, nViewId);
+ break;
+ }
+ else if (pViewShell->getEditMode() == nMode && sCurrentViewRenderState == sNewRenderState && !bIsInEdit)
+ {
+ nViewMatchingMode = pViewShell->GetViewShellId().get();
+ }
+
+ pViewShell = SfxViewShell::GetNext(*pViewShell);
+ }
+ }
+
+ // if not found view with correct part
+ // - at least avoid rendering active textbox, This is for Impress.
+ // - prefer view with the same mode
+ if (nViewMatchingMode >= 0 && nViewMatchingMode != nViewId)
+ {
+ nViewId = nViewMatchingMode;
+ doc_setView(pThis, nViewId);
+ }
+ else if (!isCalc && nLastNonEditorView >= 0 && nLastNonEditorView != nViewId &&
+ pCurrentViewShell && pCurrentViewShell->GetDrawView() &&
+ pCurrentViewShell->GetDrawView()->GetTextEditOutliner())
+ {
+ nViewId = nLastNonEditorView;
+ doc_setView(pThis, nViewId);
+ }
+
+ // Disable callbacks while we are painting - after setting the view
+ if (nViewId != nOrigViewId && nViewId >= 0)
+ {
+ const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId);
+ if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
+ handlerIt->second->disableCallbacks();
+ }
+
+ nOrigPart = doc_getPart(pThis);
+ if (nPart != nOrigPart)
+ {
+ doc_setPartImpl(pThis, nPart, false);
+ }
+
+ nOrigEditMode = pDoc->getEditMode();
+ if (nOrigEditMode != nMode)
+ {
+ SfxLokHelper::setEditMode(nMode, pDoc);
+ }
+
+ bPaintTextEdit = (nPart == nOrigPart && nMode == nOrigEditMode);
+ pDoc->setPaintTextEdit(bPaintTextEdit);
+ }
+
+ doc_paintTile(pThis, pBuffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
+
+ if (!isText)
+ {
+ pDoc->setPaintTextEdit(true);
+
+ if (nMode != nOrigEditMode)
+ {
+ SfxLokHelper::setEditMode(nOrigEditMode, pDoc);
+ }
+
+ if (nPart != nOrigPart)
+ {
+ doc_setPartImpl(pThis, nOrigPart, false);
+ }
+
+ if (nViewId != nOrigViewId)
+ {
+ if (nViewId >= 0)
+ {
+ const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId);
+ if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
+ handlerIt->second->enableCallbacks();
+ }
+
+ doc_setView(pThis, nOrigViewId);
+ }
+ }
+ }
+ catch (const std::exception&)
+ {
+ // Nothing to do but restore the PartTilePainting flag.
+ }
+
+ if (nOrigViewId >= 0)
+ {
+ const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId);
+ if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
+ handlerIt->second->enableCallbacks();
+ }
+}
+
+static int doc_getTileMode(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/)
+{
+ SetLastExceptionMsg();
+#if ENABLE_CAIRO_RGBA || defined IOS
+ return LOK_TILEMODE_RGBA;
+#else
+ return LOK_TILEMODE_BGRA;
+#endif
+}
+
+static void doc_getDocumentSize(LibreOfficeKitDocument* pThis,
+ long* pWidth,
+ long* pHeight)
+{
+ comphelper::ProfileZone aZone("doc_getDocumentSize");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (pDoc)
+ {
+ Size aDocumentSize = pDoc->getDocumentSize();
+ *pWidth = aDocumentSize.Width();
+ *pHeight = aDocumentSize.Height();
+ }
+ else
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ }
+}
+
+static void doc_getDataArea(LibreOfficeKitDocument* pThis,
+ long nTab,
+ long* pCol,
+ long* pRow)
+{
+ comphelper::ProfileZone aZone("doc_getDataArea");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (pDoc)
+ {
+ Size aDocumentSize = pDoc->getDataArea(nTab);
+ *pCol = aDocumentSize.Width();
+ *pRow = aDocumentSize.Height();
+ }
+ else
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ }
+}
+
+static void doc_initializeForRendering(LibreOfficeKitDocument* pThis,
+ const char* pArguments)
+{
+ comphelper::ProfileZone aZone("doc_initializeForRendering");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (pDoc)
+ {
+ doc_iniUnoCommands();
+ pDoc->initializeForTiledRendering(
+ comphelper::containerToSequence(jsonToPropertyValuesVector(pArguments)));
+ }
+}
+
+static void doc_registerCallback(LibreOfficeKitDocument* pThis,
+ LibreOfficeKitCallback pCallback,
+ void* pData)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ const int nView = SfxLokHelper::getView();
+ if (nView < 0)
+ return;
+
+ const size_t nId = nView;
+ if (pCallback != nullptr)
+ {
+ for (auto& pair : pDocument->mpCallbackFlushHandlers)
+ {
+ if (pair.first == nId)
+ continue;
+
+ pair.second->addViewStates(nView);
+ }
+ }
+ else
+ {
+ for (auto& pair : pDocument->mpCallbackFlushHandlers)
+ {
+ if (pair.first == nId)
+ continue;
+
+ pair.second->removeViewStates(nView);
+ }
+ }
+
+ pDocument->mpCallbackFlushHandlers[nView] = std::make_shared<CallbackFlushHandler>(pThis, pCallback, pData);
+
+ if (pCallback != nullptr)
+ {
+ for (const auto& pair : pDocument->mpCallbackFlushHandlers)
+ {
+ if (pair.first == nId)
+ continue;
+
+ pDocument->mpCallbackFlushHandlers[nView]->addViewStates(pair.first);
+ }
+
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ {
+ pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get());
+ pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get());
+ }
+
+ if (pDocument->maFontsMissing.size() != 0)
+ {
+ OString sPayload = "{ \"fontsmissing\": [ "_ostr;
+ bool bFirst = true;
+ for (const auto &f : pDocument->maFontsMissing)
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ sPayload += ", ";
+ sPayload += "\"" + f.toUtf8() + "\"";
+ }
+ sPayload += " ] }";
+ pCallback(LOK_CALLBACK_FONTS_MISSING, sPayload.getStr(), pData);
+ pDocument->maFontsMissing.clear();
+ }
+ }
+ else
+ {
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ {
+ pViewShell->setLibreOfficeKitViewCallback(nullptr);
+ pDocument->mpCallbackFlushHandlers[nView]->setViewId(-1);
+ }
+ }
+}
+
+/// Returns the JSON representation of all the comments in the document
+static char* getPostIts(LibreOfficeKitDocument* pThis)
+{
+ SetLastExceptionMsg();
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+ tools::JsonWriter aJsonWriter;
+ pDoc->getPostIts(aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+}
+
+/// Returns the JSON representation of the positions of all the comments in the document
+static char* getPostItsPos(LibreOfficeKitDocument* pThis)
+{
+ SetLastExceptionMsg();
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+ tools::JsonWriter aJsonWriter;
+ pDoc->getPostItsPos(aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+}
+
+static char* getRulerState(LibreOfficeKitDocument* pThis)
+{
+ SetLastExceptionMsg();
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+ tools::JsonWriter aJsonWriter;
+ pDoc->getRulerState(aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+}
+
+static void doc_postKeyEvent(LibreOfficeKitDocument* pThis, int nType, int nCharCode, int nKeyCode)
+{
+ comphelper::ProfileZone aZone("doc_postKeyEvent");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ try
+ {
+ pDoc->postKeyEvent(nType, nCharCode, nKeyCode);
+ }
+ catch (const uno::Exception& exception)
+ {
+ SetLastExceptionMsg(exception.Message);
+ SAL_INFO("lok", "Failed to postKeyEvent " << exception.Message);
+ }
+}
+
+static void doc_setBlockedCommandList(LibreOfficeKitDocument* /*pThis*/, int nViewId, const char* blockedCommandList)
+{
+ SolarMutexGuard aGuard;
+ SfxLokHelper::setBlockedCommandList(nViewId, blockedCommandList);
+}
+
+static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis, unsigned nWindowId, int nType, const char* pText)
+{
+ comphelper::ProfileZone aZone("doc_postWindowExtTextInputEvent");
+
+ SolarMutexGuard aGuard;
+ VclPtr<vcl::Window> pWindow;
+ if (nWindowId == 0)
+ {
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+ pWindow = pDoc->getDocWindow();
+ }
+ else
+ {
+ pWindow = vcl::Window::FindLOKWindow(nWindowId);
+ }
+
+ if (!pWindow)
+ {
+ SetLastExceptionMsg("No window found for window id: " + OUString::number(nWindowId));
+ return;
+ }
+
+ SfxLokHelper::postExtTextEventAsync(pWindow, nType, OUString::fromUtf8(std::string_view(pText, strlen(pText))));
+}
+
+static void doc_removeTextContext(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, int nCharBefore, int nCharAfter)
+{
+ SolarMutexGuard aGuard;
+ VclPtr<vcl::Window> pWindow;
+ if (nLOKWindowId == 0)
+ {
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+ pWindow = pDoc->getDocWindow();
+ }
+ else
+ {
+ pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ }
+
+ if (!pWindow)
+ {
+ SetLastExceptionMsg("No window found for window id: " + OUString::number(nLOKWindowId));
+ return;
+ }
+
+ // Annoyingly - backspace and delete are handled in the apps via an accelerator
+ // which are PostMessage'd by SfxViewShell::ExecKey_Impl so to stay in the same
+ // order we do this synchronously here, unless we're in a dialog.
+ if (nCharBefore > 0)
+ {
+ // backspace
+ if (nLOKWindowId == 0)
+ {
+ KeyEvent aEvt(8, KEY_BACKSPACE);
+ for (int i = 0; i < nCharBefore; ++i)
+ pWindow->KeyInput(aEvt);
+ }
+ else
+ SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 8, KEY_BACKSPACE, nCharBefore - 1);
+ }
+
+ if (nCharAfter > 0)
+ {
+ // delete (forward)
+ if (nLOKWindowId == 0)
+ {
+ KeyEvent aEvt(46, KEY_DELETE);
+ for (int i = 0; i < nCharAfter; ++i)
+ pWindow->KeyInput(aEvt);
+ }
+ else
+ SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 46, KEY_DELETE, nCharAfter - 1);
+ }
+}
+
+static void doc_postWindowKeyEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nCharCode, int nKeyCode)
+{
+ comphelper::ProfileZone aZone("doc_postWindowKeyEvent");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+ KeyEvent aEvent(nCharCode, nKeyCode, 0);
+
+ switch (nType)
+ {
+ case LOK_KEYEVENT_KEYINPUT:
+ Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent);
+ break;
+ case LOK_KEYEVENT_KEYUP:
+ Application::PostKeyEvent(VclEventId::WindowKeyUp, pWindow, &aEvent);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput)
+{
+ comphelper::ProfileZone aZone("doc_renderShapeSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LokChartHelper aChartHelper(SfxViewShell::Current());
+
+ if (aChartHelper.GetWindow())
+ return 0;
+
+ try
+ {
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
+
+ SvMemoryStream aOutStream;
+ uno::Reference<io::XOutputStream> xOut = new utl::OOutputStreamWrapper(aOutStream);
+
+ utl::MediaDescriptor aMediaDescriptor;
+ switch (doc_getDocumentType(pThis))
+ {
+ case LOK_DOCTYPE_PRESENTATION:
+ aMediaDescriptor[u"FilterName"_ustr] <<= u"impress_svg_Export"_ustr;
+ break;
+ case LOK_DOCTYPE_DRAWING:
+ aMediaDescriptor[u"FilterName"_ustr] <<= u"draw_svg_Export"_ustr;
+ break;
+ case LOK_DOCTYPE_TEXT:
+ aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_svg_Export"_ustr;
+ break;
+ case LOK_DOCTYPE_SPREADSHEET:
+ aMediaDescriptor[u"FilterName"_ustr] <<= u"calc_svg_Export"_ustr;
+ break;
+ default:
+ SAL_WARN("lok", "Failed to render shape selection: Document type is not supported");
+ }
+ aMediaDescriptor[u"SelectionOnly"_ustr] <<= true;
+ aMediaDescriptor[u"OutputStream"_ustr] <<= xOut;
+ aMediaDescriptor[u"IsPreview"_ustr] <<= true; // will down-scale graphics
+
+ xStorable->storeToURL(u"private:stream"_ustr, aMediaDescriptor.getAsConstPropertyValueList());
+
+ if (pOutput)
+ {
+ const size_t nOutputSize = aOutStream.GetEndOfData();
+ *pOutput = static_cast<char*>(malloc(nOutputSize));
+ if (*pOutput)
+ {
+ std::memcpy(*pOutput, aOutStream.GetData(), nOutputSize);
+ return nOutputSize;
+ }
+ }
+ }
+ catch (const uno::Exception& exception)
+ {
+ css::uno::Any exAny( cppu::getCaughtException() );
+ SetLastExceptionMsg(exception.Message);
+ SAL_WARN("lok", "Failed to render shape selection: " << exceptionToString(exAny));
+ }
+
+ return 0;
+}
+
+namespace {
+
+/** Class to react on finishing of a dispatched command.
+
+ This will call a LOK_COMMAND_FINISHED callback when postUnoCommand was
+ called with the parameter requesting the notification.
+
+ @see LibreOfficeKitCallbackType::LOK_CALLBACK_UNO_COMMAND_RESULT.
+*/
+class DispatchResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener>
+{
+ const OString maCommand; ///< Command for which this is the result.
+ const std::shared_ptr<CallbackFlushHandler> mpCallback; ///< Callback to call.
+ const std::chrono::steady_clock::time_point mSaveTime; //< The time we started saving.
+ const bool mbWasModified; //< Whether or not the document was modified before saving.
+
+public:
+ DispatchResultListener(const char* pCommand, std::shared_ptr<CallbackFlushHandler> pCallback)
+ : maCommand(pCommand)
+ , mpCallback(std::move(pCallback))
+ , mSaveTime(std::chrono::steady_clock::now())
+ , mbWasModified(SfxObjectShell::Current()->IsModified())
+ {
+ assert(mpCallback);
+ }
+
+ virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& rEvent) override
+ {
+ tools::JsonWriter aJson;
+ aJson.put("commandName", maCommand);
+
+ if (rEvent.State != frame::DispatchResultState::DONTKNOW)
+ {
+ bool bSuccess = (rEvent.State == frame::DispatchResultState::SUCCESS);
+ aJson.put("success", bSuccess);
+ }
+
+ unoAnyToJson(aJson, "result", rEvent.Result);
+ aJson.put("wasModified", mbWasModified);
+ aJson.put("startUnixTimeMics",
+ std::chrono::time_point_cast<std::chrono::microseconds>(mSaveTime)
+ .time_since_epoch()
+ .count());
+ aJson.put("saveDurationMics", std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now() - mSaveTime)
+ .count());
+ mpCallback->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+ }
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject&) override {}
+};
+
+} // anonymous namespace
+
+
+static void lcl_sendDialogEvent(unsigned long long int nWindowId, const char* pArguments)
+{
+ SolarMutexGuard aGuard;
+
+ StringMap aMap(jsdialog::jsonToStringMap(pArguments));
+
+ if (aMap.find(u"id"_ustr) == aMap.end())
+ return;
+
+ sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(SfxViewShell::Current());
+
+ try
+ {
+ OUString sControlId = aMap[u"id"_ustr];
+ OUString sWindowId = OUString::number(nWindowId);
+ OUString sCurrentShellId = OUString::number(nCurrentShellId);
+
+ // special values for window id
+ if (nWindowId == static_cast<unsigned long long int>(-1))
+ sWindowId = sCurrentShellId + "sidebar";
+ if (nWindowId == static_cast<unsigned long long int>(-2))
+ sWindowId = sCurrentShellId + "notebookbar";
+ if (nWindowId == static_cast<unsigned long long int>(-3))
+ sWindowId = sCurrentShellId + "formulabar";
+
+ // dialogs send own id but notebookbar and sidebar controls are remembered by SfxViewShell id
+ if (jsdialog::ExecuteAction(sWindowId, sControlId, aMap))
+ return;
+
+ if (jsdialog::ExecuteAction(sCurrentShellId + "sidebar", sControlId, aMap))
+ return;
+ if (jsdialog::ExecuteAction(sCurrentShellId + "notebookbar", sControlId, aMap))
+ return;
+ if (jsdialog::ExecuteAction(sCurrentShellId + "formulabar", sControlId, aMap))
+ return;
+ // this is needed for dialogs shown before document is loaded: MacroWarning dialog, etc...
+ // these dialogs are created with WindowId "0"
+ if (!SfxViewShell::Current() && jsdialog::ExecuteAction(u"0"_ustr, sControlId, aMap))
+ return;
+
+ // force resend - used in mobile-wizard
+ jsdialog::SendFullUpdate(sCurrentShellId + "sidebar", u"Panel"_ustr);
+
+ } catch(...) {}
+}
+
+
+static void doc_sendDialogEvent(LibreOfficeKitDocument* /*pThis*/, unsigned long long int nWindowId, const char* pArguments)
+{
+ lcl_sendDialogEvent(nWindowId, pArguments);
+}
+
+static void lo_sendDialogEvent(LibreOfficeKit* /*pThis*/, unsigned long long int nWindowId, const char* pArguments)
+{
+ lcl_sendDialogEvent(nWindowId, pArguments);
+}
+
+static void lo_setOption(LibreOfficeKit* /*pThis*/, const char *pOption, const char* pValue)
+{
+ static char* pCurrentSalLogOverride = nullptr;
+
+ if (strcmp(pOption, "traceeventrecording") == 0)
+ {
+ if (strcmp(pValue, "start") == 0)
+ {
+ comphelper::TraceEvent::setBufferSizeAndCallback(100, TraceEventDumper::flushRecordings);
+ comphelper::TraceEvent::startRecording();
+ if (traceEventDumper == nullptr)
+ traceEventDumper = new TraceEventDumper();
+ }
+ else if (strcmp(pValue, "stop") == 0)
+ comphelper::TraceEvent::stopRecording();
+ }
+ else if (strcmp(pOption, "sallogoverride") == 0)
+ {
+ if (pCurrentSalLogOverride != nullptr)
+ free(pCurrentSalLogOverride);
+ if (pValue == nullptr)
+ pCurrentSalLogOverride = nullptr;
+ else
+ pCurrentSalLogOverride = strdup(pValue);
+
+ if (pCurrentSalLogOverride == nullptr || pCurrentSalLogOverride[0] == '\0')
+ sal_detail_set_log_selector(nullptr);
+ else
+ sal_detail_set_log_selector(pCurrentSalLogOverride);
+ }
+#ifdef LINUX
+ else if (strcmp(pOption, "addfont") == 0)
+ {
+ if (memcmp(pValue, "file://", 7) == 0)
+ pValue += 7;
+
+ int fd = open(pValue, O_RDONLY);
+ if (fd == -1)
+ {
+ std::cerr << "Could not open font file '" << pValue << "': " << strerror(errno) << std::endl;
+ return;
+ }
+
+ OUString sMagicFileName = "file:///:FD:/" + OUString::number(fd);
+
+ OutputDevice *pDevice = Application::GetDefaultDevice();
+ OutputDevice::ImplClearAllFontData(false);
+ pDevice->AddTempDevFont(sMagicFileName, "");
+ OutputDevice::ImplRefreshAllFontData(false);
+ }
+#endif
+}
+
+static void lo_dumpState (LibreOfficeKit* pThis, const char* /* pOptions */, char** pState)
+{
+ if (!pState)
+ return;
+
+ // NB. no SolarMutexGuard since this may be caused in some extremis / deadlock
+ SetLastExceptionMsg();
+
+ *pState = nullptr;
+ OStringBuffer aState(4096*256);
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+
+ pLib->dumpState(aState);
+
+ *pState = convertOString(aState.makeStringAndClear());
+}
+
+void LibLibreOffice_Impl::dumpState(rtl::OStringBuffer &rState)
+{
+ rState.append("LibreOfficeKit state:"
+ "\n\tLastExceptionMsg:\t");
+ rState.append(rtl::OUStringToOString(maLastExceptionMsg, RTL_TEXTENCODING_UTF8));
+ rState.append("\n\tUnipoll:\t");
+ rState.append(vcl::lok::isUnipoll() ? "yes" : "no: events on thread");
+ rState.append("\n\tOptionalFeatures:\t0x");
+ rState.append(static_cast<sal_Int64>(mOptionalFeatures), 16);
+ rState.append("\n\tCallbackData:\t0x");
+ rState.append(reinterpret_cast<sal_Int64>(mpCallback), 16);
+ // TODO: dump mInteractionMap
+ SfxLokHelper::dumpState(rState);
+ vcl::lok::dumpState(rState);
+}
+
+static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, const char* pCommand, const char* pArguments, bool bNotifyWhenFinished)
+{
+ comphelper::ProfileZone aZone("doc_postUnoCommand");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ SfxObjectShell* pDocSh = SfxObjectShell::Current();
+ OUString aCommand(pCommand, strlen(pCommand), RTL_TEXTENCODING_UTF8);
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ std::vector<beans::PropertyValue> aPropertyValuesVector(jsonToPropertyValuesVector(pArguments));
+
+ if (!vcl::lok::isUnipoll())
+ {
+ beans::PropertyValue aSynchronMode;
+ aSynchronMode.Name = u"SynchronMode"_ustr;
+ aSynchronMode.Value <<= false;
+ aPropertyValuesVector.push_back(aSynchronMode);
+ }
+
+ int nView = SfxLokHelper::getView();
+ if (nView < 0)
+ return;
+
+ if (gImpl && aCommand == ".uno:ToggleOrientation")
+ {
+ ExecuteOrientationChange();
+ return;
+ }
+
+ // handle potential interaction
+ if (gImpl && aCommand == ".uno:Save")
+ {
+ // Check if saving a PDF file
+ OUString aMimeType = lcl_getCurrentDocumentMimeType(pDocument);
+ if (pDocSh && pDocSh->IsModified() && aMimeType == "application/pdf")
+ {
+ // If we have a PDF file (for saving annotations for example), we need
+ // to run save-as to the same file as the opened document. Plain save
+ // doesn't work as the PDF is not a "native" format.
+ uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
+ OUString aURL = xStorable->getLocation();
+ OString aURLUtf8 = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
+ bool bResult = doc_saveAs(pThis, aURLUtf8.getStr(), "pdf", nullptr);
+
+ // Send the result of save
+ tools::JsonWriter aJson;
+ aJson.put("commandName", pCommand);
+ aJson.put("success", bResult);
+ pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+ return;
+ }
+
+
+ rtl::Reference<LOKInteractionHandler> const pInteraction(
+ new LOKInteractionHandler("save"_ostr, gImpl, pDocument));
+ uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
+
+ beans::PropertyValue aValue;
+ aValue.Name = u"InteractionHandler"_ustr;
+ aValue.Value <<= xInteraction;
+ aPropertyValuesVector.push_back(aValue);
+
+ bool bDontSaveIfUnmodified = false;
+ std::erase_if(aPropertyValuesVector,
+ [&bDontSaveIfUnmodified](const beans::PropertyValue& aItem){
+ if (aItem.Name == "DontSaveIfUnmodified")
+ {
+ bDontSaveIfUnmodified = aItem.Value.get<bool>();
+ return true;
+ }
+ return false;
+ });
+
+ // skip saving and tell the result via UNO_COMMAND_RESULT
+ if (bDontSaveIfUnmodified && (!pDocSh || !pDocSh->IsModified()))
+ {
+ tools::JsonWriter aJson;
+ aJson.put("commandName", pCommand);
+ aJson.put("success", false);
+ // Add the reason for not saving
+ {
+ auto resultNode = aJson.startNode("result");
+ aJson.put("type", "string");
+ aJson.put("value", "unmodified");
+ }
+ pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+ return;
+ }
+ }
+ else if (gImpl && aCommand == ".uno:TransformDialog")
+ {
+ bool bNeedConversion = false;
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ LokChartHelper aChartHelper(pViewShell);
+
+ if (aChartHelper.GetWindow() )
+ {
+ bNeedConversion = true;
+ }
+ else if (const SdrView* pView = pViewShell->GetDrawView())
+ {
+ if (OutputDevice* pOutputDevice = pView->GetFirstOutputDevice())
+ {
+ bNeedConversion = (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM);
+ }
+ }
+
+ if (bNeedConversion)
+ {
+ sal_Int32 value;
+ for (beans::PropertyValue& rPropValue: aPropertyValuesVector)
+ {
+ if (rPropValue.Name == "TransformPosX"
+ || rPropValue.Name == "TransformPosY"
+ || rPropValue.Name == "TransformWidth"
+ || rPropValue.Name == "TransformHeight"
+ || rPropValue.Name == "TransformRotationX"
+ || rPropValue.Name == "TransformRotationY")
+ {
+ rPropValue.Value >>= value;
+ value = o3tl::convert(value, o3tl::Length::twip, o3tl::Length::mm100);
+ rPropValue.Value <<= value;
+ }
+ }
+ }
+
+ if (aChartHelper.GetWindow() && aPropertyValuesVector.size() > 0)
+ {
+ if (aPropertyValuesVector[0].Name != "Action")
+ {
+ tools::Rectangle aChartBB = aChartHelper.GetChartBoundingBox();
+
+ int nLeft = o3tl::convert(aChartBB.Left(), o3tl::Length::twip, o3tl::Length::mm100);
+ int nTop = o3tl::convert(aChartBB.Top(), o3tl::Length::twip, o3tl::Length::mm100);
+
+ for (beans::PropertyValue& rPropValue: aPropertyValuesVector)
+ {
+ if (rPropValue.Name == "TransformPosX" || rPropValue.Name == "TransformRotationX")
+ {
+ auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value);
+ rPropValue.Value <<= value - nLeft;
+ }
+ else if (rPropValue.Name == "TransformPosY" || rPropValue.Name == "TransformRotationY")
+ {
+ auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value);
+ rPropValue.Value <<= value - nTop;
+ }
+ }
+ }
+ util::URL aCommandURL;
+ aCommandURL.Path = u"LOKTransform"_ustr;
+ css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher();
+ aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector));
+ return;
+ }
+ }
+ else if (gImpl && aCommand == ".uno:LOKSidebarWriterPage")
+ {
+ setupSidebar(u"WriterPageDeck");
+ return;
+ }
+ else if (gImpl && aCommand == ".uno:SidebarShow")
+ {
+ setupSidebar();
+ return;
+ }
+ else if (gImpl && aCommand == ".uno:SidebarHide")
+ {
+ hideSidebar();
+ return;
+ }
+
+ bool bResult = false;
+ LokChartHelper aChartHelper(SfxViewShell::Current());
+
+ if (aChartHelper.GetWindow() && aCommand != ".uno:Save" )
+ {
+ util::URL aCommandURL;
+ aCommandURL.Path = aCommand.copy(5);
+ css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher();
+ aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector));
+ return;
+ }
+ if (LokStarMathHelper aMathHelper(SfxViewShell::Current());
+ aMathHelper.GetGraphicWindow() && aCommand != ".uno:Save")
+ {
+ aMathHelper.Dispatch(aCommand, comphelper::containerToSequence(aPropertyValuesVector));
+ return;
+ }
+ if (bNotifyWhenFinished && pDocument->mpCallbackFlushHandlers.count(nView))
+ {
+ bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector),
+ new DispatchResultListener(pCommand, pDocument->mpCallbackFlushHandlers[nView]));
+ }
+ else
+ bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector));
+
+ if (!bResult)
+ {
+ SetLastExceptionMsg("Failed to dispatch " + aCommand);
+ }
+}
+
+static void doc_postMouseEvent(LibreOfficeKitDocument* pThis, int nType, int nX, int nY, int nCount, int nButtons, int nModifier)
+{
+ comphelper::ProfileZone aZone("doc_postMouseEvent");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+ try
+ {
+ pDoc->postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier);
+ }
+ catch (const uno::Exception& exception)
+ {
+ SetLastExceptionMsg(exception.Message);
+ SAL_INFO("lok", "Failed to postMouseEvent " << exception.Message);
+ }
+}
+
+static void doc_postWindowMouseEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nX, int nY, int nCount, int nButtons, int nModifier)
+{
+ comphelper::ProfileZone aZone("doc_postWindowMouseEvent");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+ const Point aPos(nX, nY);
+
+ MouseEvent aEvent(aPos, nCount, MouseEventModifiers::SIMPLECLICK, nButtons, nModifier);
+
+ vcl::EnableDialogInput(pWindow);
+
+ switch (nType)
+ {
+ case LOK_MOUSEEVENT_MOUSEBUTTONDOWN:
+ Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aEvent);
+ break;
+ case LOK_MOUSEEVENT_MOUSEBUTTONUP:
+ Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aEvent);
+ break;
+ case LOK_MOUSEEVENT_MOUSEMOVE:
+ Application::PostMouseEvent(VclEventId::WindowMouseMove, pWindow, &aEvent);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+static void doc_postWindowGestureEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, const char* pType, int nX, int nY, int nOffset)
+{
+ comphelper::ProfileZone aZone("doc_postWindowGestureEvent");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+ OString aType(pType);
+ GestureEventPanType eEventType = GestureEventPanType::Update;
+
+ if (aType == "panBegin")
+ eEventType = GestureEventPanType::Begin;
+ else if (aType == "panEnd")
+ eEventType = GestureEventPanType::End;
+
+ GestureEventPan aEvent {
+ sal_Int32(nX),
+ sal_Int32(nY),
+ eEventType,
+ sal_Int32(nOffset),
+ PanningOrientation::Vertical,
+ };
+
+ vcl::EnableDialogInput(pWindow);
+
+ Application::PostGestureEvent(VclEventId::WindowGestureEvent, pWindow, &aEvent);
+}
+
+static void doc_setTextSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY)
+{
+ comphelper::ProfileZone aZone("doc_setTextSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->setTextSelection(nType, nX, nY);
+}
+
+static void doc_setWindowTextSelection(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, bool swap, int nX, int nY)
+{
+ comphelper::ProfileZone aZone("doc_setWindowTextSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+
+ Size aOffset(pWindow->GetOutOffXPixel(), pWindow->GetOutOffYPixel());
+ Point aCursorPos(nX, nY);
+ aCursorPos.Move(aOffset);
+ sal_uInt16 nModifier = swap ? KEY_MOD1 + KEY_MOD2 : KEY_SHIFT;
+
+ MouseEvent aCursorEvent(aCursorPos, 1, MouseEventModifiers::SIMPLECLICK, 0, nModifier);
+ Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aCursorEvent);
+ Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aCursorEvent);
+}
+
+static bool getFromTransferable(
+ const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
+ const OString &aInMimeType, OString &aRet);
+
+static bool encodeImageAsHTML(
+ const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
+ const OString &aMimeType, OString &aRet)
+{
+ if (!getFromTransferable(xTransferable, aMimeType, aRet))
+ return false;
+
+ // Encode in base64.
+ auto aSeq = Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(aRet.getStr()),
+ aRet.getLength());
+ OStringBuffer aBase64Data;
+ comphelper::Base64::encode(aBase64Data, aSeq);
+
+ // Embed in HTML.
+ aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
+ "<html><head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta "
+ "name=\"generator\" content=\""
+ + getGenerator().toUtf8()
+ + "\"/>"
+ "</head><body><img src=\"data:" + aMimeType + ";base64,"
+ + aBase64Data + "\"/></body></html>";
+
+ return true;
+}
+
+static bool encodeTextAsHTML(
+ const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
+ const OString &aMimeType, OString &aRet)
+{
+ if (!getFromTransferable(xTransferable, aMimeType, aRet))
+ return false;
+
+ // Embed in HTML - FIXME: needs some escaping.
+ aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
+ "<html><head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta "
+ "name=\"generator\" content=\""
+ + getGenerator().toUtf8()
+ + "\"/></head><body><pre>" + aRet + "</pre></body></html>";
+
+ return true;
+}
+
+static bool getFromTransferable(
+ const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
+ const OString &aInMimeType, OString &aRet)
+{
+ OString aMimeType(aInMimeType);
+
+ // Take care of UTF-8 text here.
+ bool bConvert = false;
+ sal_Int32 nIndex = 0;
+ if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "text/plain")
+ {
+ if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "charset=utf-8")
+ {
+ aMimeType = "text/plain;charset=utf-16"_ostr;
+ bConvert = true;
+ }
+ }
+
+ datatransfer::DataFlavor aFlavor;
+ aFlavor.MimeType = OUString::fromUtf8(aMimeType);
+ if (aMimeType == "text/plain;charset=utf-16")
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ aFlavor.DataType = cppu::UnoType< uno::Sequence<sal_Int8> >::get();
+
+ if (!xTransferable->isDataFlavorSupported(aFlavor))
+ {
+ // Try harder for HTML it is our copy/paste meta-file format
+ if (aInMimeType == "text/html")
+ {
+ // Desperate measures - convert text to HTML instead.
+ if (encodeTextAsHTML(xTransferable, "text/plain;charset=utf-8"_ostr, aRet))
+ return true;
+ // If html is not supported, might be a graphic-selection,
+ if (encodeImageAsHTML(xTransferable, "image/png"_ostr, aRet))
+ return true;
+ }
+
+ SetLastExceptionMsg("Flavor " + aFlavor.MimeType + " is not supported");
+ return false;
+ }
+
+ uno::Any aAny;
+ try
+ {
+ aAny = xTransferable->getTransferData(aFlavor);
+ }
+ catch (const css::datatransfer::UnsupportedFlavorException& e)
+ {
+ SetLastExceptionMsg("Unsupported flavor " + aFlavor.MimeType + " exception " + e.Message);
+ return false;
+ }
+ catch (const css::uno::Exception& e)
+ {
+ SetLastExceptionMsg("Exception getting " + aFlavor.MimeType + " exception " + e.Message);
+ return false;
+ }
+
+ if (aFlavor.DataType == cppu::UnoType<OUString>::get())
+ {
+ OUString aString;
+ aAny >>= aString;
+ if (bConvert)
+ aRet = OUStringToOString(aString, RTL_TEXTENCODING_UTF8);
+ else
+ aRet = OString(reinterpret_cast<const char *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode));
+ }
+ else
+ {
+ uno::Sequence<sal_Int8> aSequence;
+ aAny >>= aSequence;
+ aRet = OString(reinterpret_cast<const char*>(aSequence.getConstArray()), aSequence.getLength());
+ }
+
+ return true;
+}
+
+static char* doc_getTextSelection(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pUsedMimeType)
+{
+ comphelper::ProfileZone aZone("doc_getTextSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
+ if (!xTransferable)
+ {
+ SetLastExceptionMsg(u"No selection available"_ustr);
+ return nullptr;
+ }
+
+ OString aType
+ = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr;
+
+ OString aRet;
+ bool bSuccess = getFromTransferable(xTransferable, aType, aRet);
+ if (!bSuccess)
+ return nullptr;
+
+ if (pUsedMimeType) // legacy
+ {
+ if (pMimeType)
+ *pUsedMimeType = strdup(pMimeType);
+ else
+ *pUsedMimeType = nullptr;
+ }
+
+ return convertOString(aRet);
+}
+
+static int doc_getSelectionType(LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getSelectionType");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return LOK_SELTYPE_NONE;
+ }
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
+ if (!xTransferable)
+ {
+ SetLastExceptionMsg(u"No selection available"_ustr);
+ return LOK_SELTYPE_NONE;
+ }
+
+ css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY);
+ if (xTransferable2.is() && xTransferable2->isComplex())
+ return LOK_SELTYPE_COMPLEX;
+
+ OString aRet;
+ bool bSuccess = getFromTransferable(xTransferable, "text/plain;charset=utf-8"_ostr, aRet);
+ if (!bSuccess)
+ return LOK_SELTYPE_NONE;
+
+ if (aRet.getLength() > 10000)
+ return LOK_SELTYPE_COMPLEX;
+
+ return !aRet.isEmpty() ? LOK_SELTYPE_TEXT : LOK_SELTYPE_NONE;
+}
+
+static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pText, char** pUsedMimeType)
+{
+ // The purpose of this function is to avoid double call to pDoc->getSelection(),
+ // which may be expensive.
+ comphelper::ProfileZone aZone("doc_getSelectionTypeAndText");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return LOK_SELTYPE_NONE;
+ }
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
+ if (!xTransferable)
+ {
+ SetLastExceptionMsg(u"No selection available"_ustr);
+ return LOK_SELTYPE_NONE;
+ }
+
+ css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY);
+ if (xTransferable2.is() && xTransferable2->isComplex())
+ return LOK_SELTYPE_COMPLEX;
+
+ OString aType
+ = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr;
+
+ OString aRet;
+ bool bSuccess = getFromTransferable(xTransferable, aType, aRet);
+ if (!bSuccess)
+ return LOK_SELTYPE_NONE;
+
+ if (aRet.getLength() > 10000)
+ return LOK_SELTYPE_COMPLEX;
+
+ if (aRet.isEmpty())
+ return LOK_SELTYPE_NONE;
+
+ if (pText)
+ *pText = convertOString(aRet);
+
+ if (pUsedMimeType) // legacy
+ {
+ if (pMimeType)
+ *pUsedMimeType = strdup(pMimeType);
+ else
+ *pUsedMimeType = nullptr;
+ }
+
+ return LOK_SELTYPE_TEXT;
+}
+
+static int doc_getClipboard(LibreOfficeKitDocument* pThis,
+ const char **pMimeTypes,
+ size_t *pOutCount,
+ char ***pOutMimeTypes,
+ size_t **pOutSizes,
+ char ***pOutStreams)
+{
+#ifdef IOS
+ (void) pThis;
+ (void) pMimeTypes;
+ (void) pOutCount;
+ (void) pOutMimeTypes;
+ (void) pOutSizes;
+ (void) pOutStreams;
+
+ assert(!"doc_getClipboard should not be called on iOS");
+
+ return 0;
+#else
+ comphelper::ProfileZone aZone("doc_getClipboard");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ assert (pOutCount);
+ assert (pOutMimeTypes);
+ assert (pOutSizes);
+ assert (pOutStreams);
+
+ *pOutCount = 0;
+ *pOutMimeTypes = nullptr;
+ *pOutSizes = nullptr;
+ *pOutStreams = nullptr;
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return 0;
+ }
+
+ rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView());
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable = xClip->getContents();
+ SAL_INFO("lok", "Got from clip: " << xClip.get() << " transferable: " << xTransferable);
+ if (!xTransferable)
+ {
+ SetLastExceptionMsg(u"No clipboard content available"_ustr);
+ return 0;
+ }
+
+ std::vector<OString> aMimeTypes;
+ if (!pMimeTypes) // everything
+ {
+ const uno::Sequence< css::datatransfer::DataFlavor > flavors = xTransferable->getTransferDataFlavors();
+ if (!flavors.getLength())
+ {
+ SetLastExceptionMsg(u"Flavourless selection"_ustr);
+ return 0;
+ }
+ for (const auto &it : flavors)
+ aMimeTypes.push_back(OUStringToOString(it.MimeType, RTL_TEXTENCODING_UTF8));
+ }
+ else
+ {
+ for (size_t i = 0; pMimeTypes[i]; ++i)
+ aMimeTypes.push_back(OString(pMimeTypes[i]));
+ }
+
+ *pOutCount = aMimeTypes.size();
+ *pOutSizes = static_cast<size_t *>(malloc(*pOutCount * sizeof(size_t)));
+ *pOutMimeTypes = static_cast<char **>(malloc(*pOutCount * sizeof(char *)));
+ *pOutStreams = static_cast<char **>(malloc(*pOutCount * sizeof(char *)));
+ for (size_t i = 0; i < aMimeTypes.size(); ++i)
+ {
+ if (aMimeTypes[i] == "text/plain;charset=utf-16")
+ (*pOutMimeTypes)[i] = strdup("text/plain;charset=utf-8");
+ else
+ (*pOutMimeTypes)[i] = convertOString(aMimeTypes[i]);
+
+ OString aRet;
+ bool bSuccess = getFromTransferable(xTransferable, (*pOutMimeTypes)[i], aRet);
+ if (!bSuccess || aRet.getLength() < 1)
+ {
+ (*pOutSizes)[i] = 0;
+ (*pOutStreams)[i] = nullptr;
+ }
+ else
+ {
+ (*pOutSizes)[i] = aRet.getLength();
+ (*pOutStreams)[i] = convertOString(aRet);
+ }
+ }
+
+ return 1;
+#endif
+}
+
+static int doc_setClipboard(LibreOfficeKitDocument* pThis,
+ const size_t nInCount,
+ const char **pInMimeTypes,
+ const size_t *pInSizes,
+ const char **pInStreams)
+{
+#ifdef IOS
+ (void) pThis;
+ (void) nInCount;
+ (void) pInMimeTypes;
+ (void) pInSizes;
+ (void) pInStreams;
+#else
+ comphelper::ProfileZone aZone("doc_setClipboard");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return false;
+ }
+
+ uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(nInCount, pInMimeTypes, pInSizes, pInStreams));
+
+ auto xClip = forceSetClipboardForCurrentView(pThis);
+ xClip->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>());
+
+ SAL_INFO("lok", "Set clip: " << xClip.get() << " to: " << xTransferable);
+
+ if (!pDoc->isMimeTypeSupported())
+ {
+ SetLastExceptionMsg(u"Document doesn't support this mime type"_ustr);
+ return false;
+ }
+#endif
+ return true;
+}
+
+static bool doc_paste(LibreOfficeKitDocument* pThis, const char* pMimeType, const char* pData, size_t nSize)
+{
+ comphelper::ProfileZone aZone("doc_paste");
+
+ SolarMutexGuard aGuard;
+
+ const char *pInMimeTypes[1];
+ const char *pInStreams[1];
+ size_t pInSizes[1];
+ pInMimeTypes[0] = pMimeType;
+ pInSizes[0] = nSize;
+ pInStreams[0] = pData;
+
+ if (!doc_setClipboard(pThis, 1, pInMimeTypes, pInSizes, pInStreams))
+ return false;
+
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"AnchorType", uno::Any(static_cast<sal_uInt16>(css::text::TextContentAnchorType_AS_CHARACTER))},
+ {"IgnoreComments", uno::Any(true)},
+ }));
+ if (!comphelper::dispatchCommand(u".uno:Paste"_ustr, aPropertyValues))
+ {
+ SetLastExceptionMsg(u"Failed to dispatch the .uno: command"_ustr);
+ return false;
+ }
+
+ return true;
+}
+
+static void doc_setGraphicSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY)
+{
+ comphelper::ProfileZone aZone("doc_setGraphicSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->setGraphicSelection(nType, nX, nY);
+}
+
+static void doc_resetSelection(LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_resetSelection");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->resetSelection();
+}
+
+static char* getDocReadOnly(LibreOfficeKitDocument* pThis)
+{
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ if (!pDocument)
+ return nullptr;
+
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return nullptr;
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ if (!pObjectShell)
+ return nullptr;
+
+ boost::property_tree::ptree aTree;
+ aTree.put("commandName", ".uno:ReadOnly");
+ aTree.put("success", pObjectShell->IsLoadReadonly());
+
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
+ if (!pJson)
+ return nullptr;
+
+ strcpy(pJson, aStream.str().c_str());
+ pJson[aStream.str().size()] = '\0';
+ return pJson;
+}
+
+static void addLocale(boost::property_tree::ptree& rValues, css::lang::Locale const & rLocale)
+{
+ boost::property_tree::ptree aChild;
+ const LanguageTag aLanguageTag( rLocale );
+ OUString sLanguage = SvtLanguageTable::GetLanguageString(aLanguageTag.getLanguageType());
+ if (sLanguage.endsWith("}"))
+ return;
+
+ sLanguage += ";" + aLanguageTag.getBcp47(false);
+ aChild.put("", sLanguage.toUtf8());
+ rValues.push_back(std::make_pair("", aChild));
+}
+
+static char* getLanguages(const char* pCommand)
+{
+ css::uno::Sequence< css::lang::Locale > aLocales;
+ css::uno::Sequence< css::lang::Locale > aGrammarLocales;
+
+ if (xContext.is())
+ {
+ // SpellChecker
+ css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext);
+ if (xLangSrv.is())
+ {
+ css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
+ if (xSpell.is())
+ aLocales = xSpell->getLocales();
+ }
+
+ // LanguageTool
+ if (LanguageToolCfg::IsEnabled::get())
+ {
+ uno::Reference< linguistic2::XProofreader > xGC(
+ xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext),
+ uno::UNO_QUERY_THROW );
+ uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW );
+ aGrammarLocales = xSuppLoc->getLocales();
+ }
+ }
+
+ boost::property_tree::ptree aTree;
+ aTree.put("commandName", pCommand);
+ boost::property_tree::ptree aValues;
+ for ( css::lang::Locale const & rLocale : std::as_const(aLocales) )
+ addLocale(aValues, rLocale);
+ for ( css::lang::Locale const & rLocale : std::as_const(aGrammarLocales) )
+ addLocale(aValues, rLocale);
+ aTree.add_child("commandValues", aValues);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
+ assert(pJson); // Don't handle OOM conditions
+ strcpy(pJson, aStream.str().c_str());
+ pJson[aStream.str().size()] = '\0';
+ return pJson;
+}
+
+static char* getFonts (const char* pCommand)
+{
+ SfxObjectShell* pDocSh = SfxObjectShell::Current();
+ if (!pDocSh)
+ return nullptr;
+ const SvxFontListItem* pFonts = static_cast<const SvxFontListItem*>(
+ pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST));
+ const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr;
+
+ boost::property_tree::ptree aTree;
+ aTree.put("commandName", pCommand);
+ boost::property_tree::ptree aValues;
+ if ( pList )
+ {
+ sal_uInt16 nFontCount = pList->GetFontNameCount();
+ for (sal_uInt16 i = 0; i < nFontCount; ++i)
+ {
+ boost::property_tree::ptree aChildren;
+ const FontMetric& rFontMetric = pList->GetFontName(i);
+ const int* pAry = FontList::GetStdSizeAry();
+ sal_uInt16 nSizeCount = 0;
+ while (pAry[nSizeCount])
+ {
+ boost::property_tree::ptree aChild;
+ aChild.put("", static_cast<float>(pAry[nSizeCount]) / 10);
+ aChildren.push_back(std::make_pair("", aChild));
+ nSizeCount++;
+ }
+ aValues.add_child(rFontMetric.GetFamilyName().toUtf8().getStr(), aChildren);
+ }
+ }
+ aTree.add_child("commandValues", aValues);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
+ assert(pJson); // Don't handle OOM conditions
+ strcpy(pJson, aStream.str().c_str());
+ pJson[aStream.str().size()] = '\0';
+ return pJson;
+}
+
+static char* getFontSubset (std::string_view aFontName)
+{
+ OUString aFoundFont(::rtl::Uri::decode(OStringToOUString(aFontName, RTL_TEXTENCODING_UTF8), rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8));
+
+ boost::property_tree::ptree aTree;
+ aTree.put("commandName", ".uno:FontSubset");
+ boost::property_tree::ptree aValues;
+
+ if (const vcl::Font* pFont = FindFont(aFoundFont))
+ {
+ FontCharMapRef xFontCharMap (new FontCharMap());
+ auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
+
+ aDevice->SetFont(*pFont);
+ aDevice->GetFontCharMap(xFontCharMap);
+ SubsetMap aSubMap(xFontCharMap);
+
+ for (auto const& subset : aSubMap.GetSubsetMap())
+ {
+ boost::property_tree::ptree aChild;
+ aChild.put("", static_cast<int>(ublock_getCode(subset.GetRangeMin())));
+ aValues.push_back(std::make_pair("", aChild));
+ }
+ }
+
+ aTree.add_child("commandValues", aValues);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
+ assert(pJson); // Don't handle OOM conditions
+ strcpy(pJson, aStream.str().c_str());
+ pJson[aStream.str().size()] = '\0';
+ return pJson;
+}
+
+static char* getStyles(LibreOfficeKitDocument* pThis, const char* pCommand)
+{
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ boost::property_tree::ptree aTree;
+ aTree.put("commandName", pCommand);
+ uno::Reference<css::style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(pDocument->mxComponent, uno::UNO_QUERY);
+ const uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ const uno::Sequence<OUString> aStyleFamilies = xStyleFamilies->getElementNames();
+
+ static constexpr OUString aWriterStyles[] =
+ {
+ u"Text body"_ustr,
+ u"Quotations"_ustr,
+ u"Title"_ustr,
+ u"Subtitle"_ustr,
+ u"Heading 1"_ustr,
+ u"Heading 2"_ustr,
+ u"Heading 3"_ustr,
+ };
+
+ // We need to keep a list of the default style names
+ // in order to filter these out later when processing
+ // the full list of styles.
+ std::set<OUString> aDefaultStyleNames;
+
+ boost::property_tree::ptree aValues;
+ for (OUString const & sStyleFam : aStyleFamilies)
+ {
+ boost::property_tree::ptree aChildren;
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(sStyleFam), uno::UNO_QUERY);
+
+ // Writer provides a huge number of styles, we have a list of 7 "default" styles which
+ // should be shown in the normal dropdown, which we should add to the start of the list
+ // to simplify their selection.
+ if (sStyleFam == "ParagraphStyles"
+ && doc_getDocumentType(pThis) == LOK_DOCTYPE_TEXT)
+ {
+ for (const OUString& rStyle: aWriterStyles)
+ {
+ aDefaultStyleNames.insert( rStyle );
+
+ boost::property_tree::ptree aChild;
+ aChild.put("", rStyle.toUtf8());
+ aChildren.push_back(std::make_pair("", aChild));
+ }
+ }
+
+ const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
+ for (const OUString& rStyle: aStyles )
+ {
+ // Filter out the default styles - they are already at the top
+ // of the list
+ if (aDefaultStyleNames.find(rStyle) == aDefaultStyleNames.end() ||
+ (sStyleFam != "ParagraphStyles" || doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) )
+ {
+ boost::property_tree::ptree aChild;
+ aChild.put("", rStyle.toUtf8());
+ aChildren.push_back(std::make_pair("", aChild));
+ }
+ }
+ aValues.add_child(sStyleFam.toUtf8().getStr(), aChildren);
+ }
+
+ // Header & Footer Styles
+ {
+ boost::property_tree::ptree aChild;
+ boost::property_tree::ptree aChildren;
+ static constexpr OUString sPageStyles(u"PageStyles"_ustr);
+ uno::Reference<beans::XPropertySet> xProperty;
+ uno::Reference<container::XNameContainer> xContainer;
+
+ if (xStyleFamilies->hasByName(sPageStyles) && (xStyleFamilies->getByName(sPageStyles) >>= xContainer))
+ {
+ const uno::Sequence<OUString> aSeqNames = xContainer->getElementNames();
+ for (OUString const & sName : aSeqNames)
+ {
+ bool bIsPhysical;
+ xProperty.set(xContainer->getByName(sName), uno::UNO_QUERY);
+ if (xProperty.is() && (xProperty->getPropertyValue(u"IsPhysical"_ustr) >>= bIsPhysical) && bIsPhysical)
+ {
+ OUString displayName;
+ xProperty->getPropertyValue(u"DisplayName"_ustr) >>= displayName;
+ aChild.put("", displayName.toUtf8());
+ aChildren.push_back(std::make_pair("", aChild));
+ }
+ }
+ aValues.add_child("HeaderFooter", aChildren);
+ }
+ }
+
+ {
+ boost::property_tree::ptree aCommandList;
+
+ {
+ boost::property_tree::ptree aChild;
+
+ OUString sClearFormat = SvxResId(RID_SVXSTR_CLEARFORM);
+
+ boost::property_tree::ptree aName;
+ aName.put("", sClearFormat.toUtf8());
+ aChild.push_back(std::make_pair("text", aName));
+
+ boost::property_tree::ptree aCommand;
+ aCommand.put("", ".uno:ResetAttributes");
+ aChild.push_back(std::make_pair("id", aCommand));
+
+ aCommandList.push_back(std::make_pair("", aChild));
+ }
+
+ aValues.add_child("Commands", aCommandList);
+ }
+
+ aTree.add_child("commandValues", aValues);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
+ assert(pJson); // Don't handle OOM conditions
+ strcpy(pJson, aStream.str().c_str());
+ pJson[aStream.str().size()] = '\0';
+ return pJson;
+}
+
+namespace {
+
+enum class UndoOrRedo
+{
+ UNDO,
+ REDO
+};
+
+}
+
+/// Returns the JSON representation of either an undo or a redo stack.
+static char* getUndoOrRedo(LibreOfficeKitDocument* pThis, UndoOrRedo eCommand)
+{
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ auto pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return nullptr;
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ if (!pObjectShell)
+ return nullptr;
+
+ SfxUndoManager* pUndoManager = pObjectShell->GetUndoManager();
+ if (!pUndoManager)
+ return nullptr;
+
+ OUString aString;
+ if (eCommand == UndoOrRedo::UNDO)
+ aString = pUndoManager->GetUndoActionsInfo();
+ else
+ aString = pUndoManager->GetRedoActionsInfo();
+ char* pJson = convertOUString(aString);
+ return pJson;
+}
+
+/// Returns the JSON representation of the redline stack.
+static char* getTrackedChanges(LibreOfficeKitDocument* pThis)
+{
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ uno::Reference<document::XRedlinesSupplier> xRedlinesSupplier(pDocument->mxComponent, uno::UNO_QUERY);
+ tools::JsonWriter aJson;
+ // We want positions of the track changes also which is not possible from
+ // UNO. Enable positioning information for text documents only for now, so
+ // construct the tracked changes JSON from inside the sw/, not here using UNO
+ if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT && xRedlinesSupplier.is())
+ {
+ auto redlinesNode = aJson.startArray("redlines");
+ uno::Reference<container::XEnumeration> xRedlines = xRedlinesSupplier->getRedlines()->createEnumeration();
+ for (size_t nIndex = 0; xRedlines->hasMoreElements(); ++nIndex)
+ {
+ uno::Reference<beans::XPropertySet> xRedline(xRedlines->nextElement(), uno::UNO_QUERY);
+ auto redlineNode = aJson.startStruct();
+ aJson.put("index", static_cast<sal_Int32>(nIndex));
+
+ OUString sAuthor;
+ xRedline->getPropertyValue(u"RedlineAuthor"_ustr) >>= sAuthor;
+ aJson.put("author", sAuthor);
+
+ OUString sType;
+ xRedline->getPropertyValue(u"RedlineType"_ustr) >>= sType;
+ aJson.put("type", sType);
+
+ OUString sComment;
+ xRedline->getPropertyValue(u"RedlineComment"_ustr) >>= sComment;
+ aJson.put("comment", sComment);
+
+ OUString sDescription;
+ xRedline->getPropertyValue(u"RedlineDescription"_ustr) >>= sDescription;
+ aJson.put("description", sDescription);
+
+ util::DateTime aDateTime;
+ xRedline->getPropertyValue(u"RedlineDateTime"_ustr) >>= aDateTime;
+ OUString sDateTime = utl::toISO8601(aDateTime);
+ aJson.put("dateTime", sDateTime);
+ }
+ }
+ else
+ {
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+ pDoc->getTrackedChanges(aJson);
+ }
+
+ return convertOString(aJson.finishAndGetAsOString());
+}
+
+
+/// Returns the JSON representation of the redline author table.
+static char* getTrackedChangeAuthors(LibreOfficeKitDocument* pThis)
+{
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+ tools::JsonWriter aJsonWriter;
+ pDoc->getTrackedChangeAuthors(aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+}
+
+static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand)
+{
+ comphelper::ProfileZone aZone("doc_getCommandValues");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ const std::string_view aCommand(pCommand);
+ static constexpr std::string_view aViewRowColumnHeaders(".uno:ViewRowColumnHeaders");
+ static constexpr std::string_view aSheetGeometryData(".uno:SheetGeometryData");
+ static constexpr std::string_view aFontSubset(".uno:FontSubset&name=");
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return nullptr;
+ }
+
+ if (aCommand == ".uno:ReadOnly")
+ {
+ return getDocReadOnly(pThis);
+ }
+ else if (aCommand == ".uno:LanguageStatus")
+ {
+ return getLanguages(pCommand);
+ }
+ else if (aCommand == ".uno:CharFontName")
+ {
+ return getFonts(pCommand);
+ }
+ else if (aCommand == ".uno:StyleApply")
+ {
+ return getStyles(pThis, pCommand);
+ }
+ else if (aCommand == ".uno:Undo")
+ {
+ return getUndoOrRedo(pThis, UndoOrRedo::UNDO);
+ }
+ else if (aCommand == ".uno:Redo")
+ {
+ return getUndoOrRedo(pThis, UndoOrRedo::REDO);
+ }
+ else if (aCommand == ".uno:AcceptTrackedChanges")
+ {
+ return getTrackedChanges(pThis);
+ }
+ else if (aCommand == ".uno:TrackedChangeAuthors")
+ {
+ return getTrackedChangeAuthors(pThis);
+ }
+ else if (aCommand == ".uno:ViewAnnotations")
+ {
+ return getPostIts(pThis);
+ }
+ else if (aCommand == ".uno:ViewAnnotationsPosition")
+ {
+ return getPostItsPos(pThis);
+ }
+ else if (aCommand == ".uno:RulerState")
+ {
+ return getRulerState(pThis);
+ }
+ else if (aCommand.starts_with(aViewRowColumnHeaders))
+ {
+ tools::Rectangle aRectangle;
+ if (aCommand.size() > aViewRowColumnHeaders.size())
+ {
+ // Command has parameters.
+ int nX = 0;
+ int nY = 0;
+ int nWidth = 0;
+ int nHeight = 0;
+ std::string_view aArguments = aCommand.substr(aViewRowColumnHeaders.size() + 1);
+ sal_Int32 nParamIndex = 0;
+ do
+ {
+ std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex);
+ sal_Int32 nIndex = 0;
+ std::string_view aKey;
+ std::string_view aValue;
+ do
+ {
+ std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex);
+ if (aKey.empty())
+ aKey = aToken;
+ else
+ aValue = aToken;
+ }
+ while (nIndex >= 0);
+ if (aKey == "x")
+ nX = o3tl::toInt32(aValue);
+ else if (aKey == "y")
+ nY = o3tl::toInt32(aValue);
+ else if (aKey == "width")
+ nWidth = o3tl::toInt32(aValue);
+ else if (aKey == "height")
+ nHeight = o3tl::toInt32(aValue);
+ }
+ while (nParamIndex >= 0);
+
+ aRectangle = tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
+ }
+
+ tools::JsonWriter aJsonWriter;
+ pDoc->getRowColumnHeaders(aRectangle, aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+ }
+ else if (aCommand.starts_with(aSheetGeometryData))
+ {
+ bool bColumns = true;
+ bool bRows = true;
+ bool bSizes = true;
+ bool bHidden = true;
+ bool bFiltered = true;
+ bool bGroups = true;
+ if (aCommand.size() > aSheetGeometryData.size())
+ {
+ bColumns = bRows = bSizes = bHidden = bFiltered = bGroups = false;
+
+ std::string_view aArguments = aCommand.substr(aSheetGeometryData.size() + 1);
+ sal_Int32 nParamIndex = 0;
+ do
+ {
+ std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex);
+ sal_Int32 nIndex = 0;
+ std::string_view aKey;
+ std::string_view aValue;
+ do
+ {
+ std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex);
+ if (aKey.empty())
+ aKey = aToken;
+ else
+ aValue = aToken;
+
+ } while (nIndex >= 0);
+
+ bool bEnableFlag = aValue.empty() ||
+ o3tl::equalsIgnoreAsciiCase(aValue, "true") || o3tl::toInt32(aValue) > 0;
+ if (!bEnableFlag)
+ continue;
+
+ if (aKey == "columns")
+ bColumns = true;
+ else if (aKey == "rows")
+ bRows = true;
+ else if (aKey == "sizes")
+ bSizes = true;
+ else if (aKey == "hidden")
+ bHidden = true;
+ else if (aKey == "filtered")
+ bFiltered = true;
+ else if (aKey == "groups")
+ bGroups = true;
+
+ } while (nParamIndex >= 0);
+ }
+
+ OString aGeomDataStr
+ = pDoc->getSheetGeometryData(bColumns, bRows, bSizes, bHidden, bFiltered, bGroups);
+
+ if (aGeomDataStr.isEmpty())
+ return nullptr;
+
+ return convertOString(aGeomDataStr);
+ }
+ else if (aCommand.starts_with(".uno:CellCursor"))
+ {
+ // Ignore command's deprecated parameters.
+ tools::JsonWriter aJsonWriter;
+ pDoc->getCellCursor(aJsonWriter);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+ }
+ else if (aCommand.starts_with(aFontSubset))
+ {
+ return getFontSubset(aCommand.substr(aFontSubset.size()));
+ }
+ else if (pDoc->supportsCommand(INetURLObject(OUString::fromUtf8(aCommand)).GetURLPath()))
+ {
+ tools::JsonWriter aJsonWriter;
+ pDoc->getCommandValues(aJsonWriter, aCommand);
+ return convertOString(aJsonWriter.finishAndGetAsOString());
+ }
+ else
+ {
+ SetLastExceptionMsg(u"Unknown command, no values returned"_ustr);
+ return nullptr;
+ }
+}
+
+static void doc_setClientZoom(LibreOfficeKitDocument* pThis, int nTilePixelWidth, int nTilePixelHeight,
+ int nTileTwipWidth, int nTileTwipHeight)
+{
+ comphelper::ProfileZone aZone("doc_setClientZoom");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->setClientZoom(nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight);
+}
+
+static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight)
+{
+ comphelper::ProfileZone aZone("doc_setClientVisibleArea");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ tools::Rectangle aRectangle(Point(nX, nY), Size(nWidth, nHeight));
+ pDoc->setClientVisibleArea(aRectangle);
+}
+
+static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden)
+{
+ comphelper::ProfileZone aZone("doc_setOutlineState");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->setOutlineState(bColumn, nLevel, nIndex, bHidden);
+}
+
+static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis,
+ const char* pOptions)
+{
+ comphelper::ProfileZone aZone("doc_createView");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ OUString aOptions = getUString(pOptions);
+ const OUString aLanguage = extractParameter(aOptions, u"Language");
+
+ if (!aLanguage.isEmpty())
+ {
+ // Set the LOK language tag, used for dialog tunneling.
+ comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage));
+ comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage));
+ }
+
+ const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor");
+ SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor);
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ const int nId = SfxLokHelper::createView(pDocument->mnDocumentId);
+
+ vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId));
+
+#ifdef IOS
+ (void) pThis;
+#else
+ forceSetClipboardForCurrentView(pThis);
+#endif
+
+ return nId;
+}
+
+static int doc_createView(LibreOfficeKitDocument* pThis)
+{
+ return doc_createViewWithOptions(pThis, nullptr); // No options.
+}
+
+static void doc_destroyView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId)
+{
+ comphelper::ProfileZone aZone("doc_destroyView");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+#ifndef IOS
+ LOKClipboardFactory::releaseClipboardForView(nId);
+#endif
+
+ SfxLokHelper::destroyView(nId);
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId));
+}
+
+static void doc_setView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId)
+{
+ comphelper::ProfileZone aZone("doc_setView");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ SfxLokHelper::setView(nId);
+}
+
+static int doc_getView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/)
+{
+ comphelper::ProfileZone aZone("doc_getView");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ return SfxLokHelper::getView();
+}
+
+static int doc_getViewsCount(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getViewsCount");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ return SfxLokHelper::getViewsCount(pDocument->mnDocumentId);
+}
+
+static bool doc_getViewIds(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int* pArray, size_t nSize)
+{
+ comphelper::ProfileZone aZone("doc_getViewsIds");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+ return SfxLokHelper::getViewIds(pDocument->mnDocumentId, pArray, nSize);
+}
+
+static void doc_setViewLanguage(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, const char* language)
+{
+ comphelper::ProfileZone aZone("doc_setViewLanguage");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ OUString sLanguage = OStringToOUString(language, RTL_TEXTENCODING_UTF8);
+ SfxLokHelper::setViewLanguage(nId, sLanguage);
+ SfxLokHelper::setViewLocale(nId, sLanguage);
+}
+
+unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis,
+ const char* pFontName,
+ const char* pChar,
+ int* pFontWidth,
+ int* pFontHeight)
+{
+ return doc_renderFontOrientation(pThis, pFontName, pChar, pFontWidth, pFontHeight, 0);
+}
+
+unsigned char* doc_renderFontOrientation(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/,
+ const char* pFontName,
+ const char* pChar,
+ int* pFontWidth,
+ int* pFontHeight,
+ int pOrientation)
+{
+ comphelper::ProfileZone aZone("doc_renderFont");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ const int nDefaultFontSize = 25;
+
+ auto aFont = FindFont_FallbackToDefault(OStringToOUString(pFontName, RTL_TEXTENCODING_UTF8));
+
+ OUString aText(OStringToOUString(pChar, RTL_TEXTENCODING_UTF8));
+ if (aText.isEmpty())
+ aText = aFont.GetFamilyName();
+
+ auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
+ ::tools::Rectangle aRect;
+ aFont.SetFontSize(Size(0, nDefaultFontSize));
+ aFont.SetOrientation(Degree10(pOrientation));
+ aDevice->SetFont(aFont);
+ aDevice->GetTextBoundRect(aRect, aText);
+ if (aRect.IsEmpty())
+ return nullptr;
+
+ int nFontWidth = aRect.Right() + 1;
+ int nFontHeight = aRect.Bottom() + 1;
+
+ if (nFontWidth <= 0 || nFontHeight <= 0)
+ return nullptr;
+
+ if (*pFontWidth > 0 && *pFontHeight > 0)
+ {
+ double fScaleX = *pFontWidth / static_cast<double>(nFontWidth) / 1.5;
+ double fScaleY = *pFontHeight / static_cast<double>(nFontHeight) / 1.5;
+
+ double fScale = std::min(fScaleX, fScaleY);
+
+ if (fScale >= 1.0)
+ {
+ int nFontSize = fScale * nDefaultFontSize;
+ aFont.SetFontSize(Size(0, nFontSize));
+ aDevice->SetFont(aFont);
+ }
+
+ aRect = tools::Rectangle(0, 0, *pFontWidth, *pFontHeight);
+
+ nFontWidth = *pFontWidth;
+ nFontHeight = *pFontHeight;
+
+ }
+
+ unsigned char* pBuffer = static_cast<unsigned char*>(malloc(4 * nFontWidth * nFontHeight));
+ if (!pBuffer)
+ return nullptr;
+
+ memset(pBuffer, 0, nFontWidth * nFontHeight * 4);
+ aDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ aDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(
+ Size(nFontWidth, nFontHeight), Fraction(1.0), Point(),
+ pBuffer);
+
+ if (*pFontWidth > 0 && *pFontHeight > 0)
+ {
+ DrawTextFlags const nStyle =
+ DrawTextFlags::Center
+ | DrawTextFlags::VCenter
+ | DrawTextFlags::MultiLine
+ | DrawTextFlags::WordBreak;// | DrawTextFlags::WordBreakHyphenation ;
+
+ aDevice->DrawText(aRect, aText, nStyle);
+ }
+ else
+ {
+ *pFontWidth = nFontWidth;
+ *pFontHeight = nFontHeight;
+
+ aDevice->DrawText(Point(0,0), aText);
+ }
+
+
+ return pBuffer;
+}
+
+
+static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
+ unsigned char* pBuffer,
+ const int nX, const int nY,
+ const int nWidth, const int nHeight)
+{
+ doc_paintWindowDPI(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, 1.0);
+}
+
+static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
+ unsigned char* pBuffer,
+ const int nX, const int nY,
+ const int nWidth, const int nHeight,
+ const double fDPIScale)
+{
+ doc_paintWindowForView(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, fDPIScale, -1);
+}
+
+static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
+ unsigned char* pBuffer, const int nX, const int nY,
+ const int nWidth, const int nHeight,
+ const double fDPIScale, int viewId)
+{
+ comphelper::ProfileZone aZone("doc_paintWindowDPI");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+ // Used to avoid work in setView if set.
+ comphelper::LibreOfficeKit::setDialogPainting(true);
+
+ if (viewId >= 0)
+ doc_setView(pThis, viewId);
+
+ // Setup cairo (or CoreGraphics, in the iOS case) to draw with the changed DPI scale (and return
+ // back to 1.0 when the painting finishes)
+ comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); });
+ comphelper::LibreOfficeKit::setDPIScale(fDPIScale);
+
+#if defined(IOS)
+ // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags
+ // to kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big
+ CGContextRef cgc = CGBitmapContextCreate(pBuffer, nWidth, nHeight, 8, nWidth*4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big);
+
+ CGContextTranslateCTM(cgc, 0, nHeight);
+ CGContextScaleCTM(cgc, fDPIScale, -fDPIScale);
+
+ SystemGraphicsData aData;
+ aData.rCGContext = cgc;
+
+ ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+
+ pDevice->SetOutputSizePixel(Size(nWidth, nHeight));
+
+ MapMode aMapMode(pDevice->GetMapMode());
+ aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale)));
+ pDevice->SetMapMode(aMapMode);
+
+ pWindow->PaintToDevice(pDevice.get(), Point(0, 0));
+
+ CGContextRelease(cgc);
+
+#else
+
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+
+ pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nWidth, nHeight), Fraction(1.0), Point(), pBuffer);
+
+ MapMode aMapMode(pDevice->GetMapMode());
+ aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale)));
+ pDevice->SetMapMode(aMapMode);
+
+ pWindow->PaintToDevice(pDevice.get(), Point(0, 0));
+#endif
+
+ comphelper::LibreOfficeKit::setDialogPainting(false);
+}
+
+static void doc_postWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nAction, const char* pData)
+{
+ comphelper::ProfileZone aZone("doc_postWindow");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
+ return;
+ }
+
+ if (nAction == LOK_WINDOW_CLOSE)
+ {
+ vcl::CloseTopLevel(pWindow);
+ }
+ else if (nAction == LOK_WINDOW_PASTE)
+ {
+#ifndef IOS
+ OUString aMimeType;
+ css::uno::Sequence<sal_Int8> aData;
+ std::vector<beans::PropertyValue> aArgs(jsonToPropertyValuesVector(pData));
+ {
+ aArgs.size() == 2 &&
+ aArgs[0].Name == "MimeType" && (aArgs[0].Value >>= aMimeType) &&
+ aArgs[1].Name == "Data" && (aArgs[1].Value >>= aData);
+ }
+
+ if (!aMimeType.isEmpty() && aData.hasElements())
+ {
+ uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(aMimeType, aData));
+ uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(new LOKClipboard);
+ xClipboard->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>());
+ pWindow->SetClipboard(xClipboard);
+
+ KeyEvent aEvent(0, KEY_PASTE, 0);
+ Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent);
+ }
+ else
+ SetLastExceptionMsg(u"Window command 'paste': wrong parameters."_ustr);
+#else
+ (void) pData;
+ assert(!"doc_postWindow() with LOK_WINDOW_PASTE should not be called on iOS");
+#endif
+ }
+}
+
+// CERTIFICATE AND DOCUMENT SIGNING
+static bool doc_insertCertificate(LibreOfficeKitDocument* pThis,
+ const unsigned char* pCertificateBinary, const int nCertificateBinarySize,
+ const unsigned char* pPrivateKeyBinary, const int nPrivateKeySize)
+{
+ comphelper::ProfileZone aZone("doc_insertCertificate");
+
+ if (!xContext.is())
+ return false;
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ if (!pDocument->mxComponent.is())
+ return false;
+
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return false;
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+
+ if (!pObjectShell)
+ return false;
+
+ uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
+ uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
+ if (!xSecurityContext.is())
+ return false;
+
+ uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
+ uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
+
+ if (!xCertificateCreator.is())
+ return false;
+
+ uno::Sequence<sal_Int8> aCertificateSequence;
+
+ std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
+ std::string aCertificateBase64String = extractCertificate(aCertificateString);
+ if (!aCertificateBase64String.empty())
+ {
+ OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
+ comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
+ }
+ else
+ {
+ aCertificateSequence.realloc(nCertificateBinarySize);
+ std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
+ }
+
+ uno::Sequence<sal_Int8> aPrivateKeySequence;
+ std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeySize);
+ std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString);
+ if (!aPrivateKeyBase64String.empty())
+ {
+ OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
+ comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
+ }
+ else
+ {
+ aPrivateKeySequence.realloc(nPrivateKeySize);
+ std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeySize, aPrivateKeySequence.getArray());
+ }
+
+ uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
+
+ if (!xCertificate.is())
+ return false;
+
+ SolarMutexGuard aGuard;
+
+ return pObjectShell->SignDocumentContentUsingCertificate(xCertificate);
+}
+
+static bool doc_addCertificate(LibreOfficeKitDocument* pThis,
+ const unsigned char* pCertificateBinary, const int nCertificateBinarySize)
+{
+ comphelper::ProfileZone aZone("doc_addCertificate");
+
+ if (!xContext.is())
+ return false;
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ if (!pDocument->mxComponent.is())
+ return false;
+
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return false;
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+
+ if (!pObjectShell)
+ return false;
+
+ uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
+ uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
+ if (!xSecurityContext.is())
+ return false;
+
+ uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
+ uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
+
+ if (!xCertificateCreator.is())
+ return false;
+
+ uno::Sequence<sal_Int8> aCertificateSequence;
+
+ std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
+ std::string aCertificateBase64String = extractCertificate(aCertificateString);
+ if (!aCertificateBase64String.empty())
+ {
+ OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
+ comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
+ }
+ else
+ {
+ aCertificateSequence.realloc(nCertificateBinarySize);
+ std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
+ }
+
+ uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->addDERCertificateToTheDatabase(aCertificateSequence, u"TCu,Cu,Tu"_ustr);
+
+ if (!xCertificate.is())
+ return false;
+
+ SAL_INFO("lok", "Certificate Added = IssuerName: " << xCertificate->getIssuerName() << " SubjectName: " << xCertificate->getSubjectName());
+
+ return true;
+}
+
+static int doc_getSignatureState(LibreOfficeKitDocument* pThis)
+{
+ comphelper::ProfileZone aZone("doc_getSignatureState");
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ if (!pDocument->mxComponent.is())
+ return int(SignatureState::UNKNOWN);
+
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
+ if (!pBaseModel)
+ return int(SignatureState::UNKNOWN);
+
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ if (!pObjectShell)
+ return int(SignatureState::UNKNOWN);
+
+ SolarMutexGuard aGuard;
+
+ pObjectShell->RecheckSignature(false);
+
+ return int(pObjectShell->GetDocumentSignatureState());
+}
+
+static void doc_resizeWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId,
+ const int nWidth, const int nHeight)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
+ if (!pWindow)
+ {
+ SetLastExceptionMsg(u"Document doesn't support dialog resizing, or window not found."_ustr);
+ return;
+ }
+
+ pWindow->SetSizePixel(Size(nWidth, nHeight));
+}
+
+static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char* pFunctionName)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ pDoc->completeFunction(OUString::fromUtf8(pFunctionName));
+}
+
+
+static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis, const char* pArguments)
+{
+ SolarMutexGuard aGuard;
+
+ // Supported in Writer only
+ if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
+ return;
+
+ StringMap aMap(jsdialog::jsonToStringMap(pArguments));
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering!"_ustr);
+ return;
+ }
+
+ // Sanity check
+ if (aMap.find(u"type"_ustr) == aMap.end() || aMap.find(u"cmd"_ustr) == aMap.end())
+ {
+ SetLastExceptionMsg(u"Wrong arguments for sendFormFieldEvent!"_ustr);
+ return;
+ }
+
+ pDoc->executeFromFieldEvent(aMap);
+}
+
+static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis,
+ const char* pSearchResult, unsigned char** pBitmapBuffer,
+ int* pWidth, int* pHeight, size_t* pByteSize)
+{
+ if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
+ return false;
+
+ if (pBitmapBuffer == nullptr)
+ return false;
+
+ if (!pSearchResult || pSearchResult[0] == '\0')
+ return false;
+
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return false;
+ }
+
+ auto aRectangleVector = pDoc->getSearchResultRectangles(pSearchResult);
+
+ // combine into a rectangle union
+ basegfx::B2DRange aRangeUnion;
+ for (basegfx::B2DRange const & rRange : aRectangleVector)
+ {
+ aRangeUnion.expand(rRange);
+ }
+
+ int aPixelWidth = o3tl::convert(aRangeUnion.getWidth(), o3tl::Length::twip, o3tl::Length::px);
+ int aPixelHeight = o3tl::convert(aRangeUnion.getHeight(), o3tl::Length::twip, o3tl::Length::px);
+
+ size_t nByteSize = aPixelWidth * aPixelHeight * 4;
+
+ *pWidth = aPixelWidth;
+ *pHeight = aPixelHeight;
+ *pByteSize = nByteSize;
+
+ auto* pBuffer = static_cast<unsigned char*>(std::malloc(nByteSize));
+
+ doc_paintTile(pThis, pBuffer,
+ aPixelWidth, aPixelHeight,
+ aRangeUnion.getMinX(), aRangeUnion.getMinY(),
+ aRangeUnion.getWidth(), aRangeUnion.getHeight());
+
+ *pBitmapBuffer = pBuffer;
+
+ return true;
+}
+
+static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments)
+{
+ SolarMutexGuard aGuard;
+
+ // Supported in Writer only
+ if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
+ {
+ return;
+ }
+
+ StringMap aMap(jsdialog::jsonToStringMap(pArguments));
+ ITiledRenderable* pDoc = getTiledRenderable(pThis);
+ if (!pDoc)
+ {
+ SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
+ return;
+ }
+
+ // Sanity check
+ if (aMap.find(u"type"_ustr) == aMap.end())
+ {
+ SetLastExceptionMsg(u"Missing 'type' argument for sendContentControlEvent"_ustr);
+ return;
+ }
+
+ pDoc->executeContentControlEvent(aMap);
+}
+
+static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId,
+ const char* pTimezone)
+{
+ comphelper::ProfileZone aZone("doc_setViewTimezone");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ // Leave the default if we get a null timezone.
+ if (pTimezone)
+ {
+ OUString sTimezone = OStringToOUString(pTimezone, RTL_TEXTENCODING_UTF8);
+ SfxLokHelper::setViewTimezone(nId, true, sTimezone);
+ }
+}
+
+static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId, bool nEnabled)
+{
+ SolarMutexGuard aGuard;
+
+ int nDocType = getDocumentType(pThis);
+ if (!(nDocType == LOK_DOCTYPE_TEXT || nDocType == LOK_DOCTYPE_PRESENTATION || nDocType == LOK_DOCTYPE_SPREADSHEET))
+ return;
+
+ SfxLokHelper::setAccessibilityState(nId, nEnabled);
+}
+
+static char* lo_getError (LibreOfficeKit *pThis)
+{
+ comphelper::ProfileZone aZone("lo_getError");
+
+ SolarMutexGuard aGuard;
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ return convertOUString(pLib->maLastExceptionMsg);
+}
+
+static void lo_freeError(char* pFree)
+{
+ free(pFree);
+}
+
+static char* lo_getFilterTypes(LibreOfficeKit* pThis)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLibreOffice_Impl* pImpl = static_cast<LibLibreOffice_Impl*>(pThis);
+
+ if (!xSFactory.is())
+ xSFactory = comphelper::getProcessServiceFactory();
+
+ if (!xSFactory.is())
+ {
+ pImpl->maLastExceptionMsg = u"Service factory is not available"_ustr;
+ return nullptr;
+ }
+
+ uno::Reference<container::XNameAccess> xTypeDetection(xSFactory->createInstance(u"com.sun.star.document.TypeDetection"_ustr), uno::UNO_QUERY);
+ const uno::Sequence<OUString> aTypes = xTypeDetection->getElementNames();
+ tools::JsonWriter aJson;
+ for (const OUString& rType : aTypes)
+ {
+ uno::Sequence<beans::PropertyValue> aValues;
+ if (xTypeDetection->getByName(rType) >>= aValues)
+ {
+ auto it = std::find_if(std::cbegin(aValues), std::cend(aValues), [](const beans::PropertyValue& rValue) { return rValue.Name == "MediaType"; });
+ OUString aValue;
+ if (it != std::cend(aValues) && (it->Value >>= aValue) && !aValue.isEmpty())
+ {
+ auto typeNode = aJson.startNode(rType.toUtf8());
+ aJson.put("MediaType", aValue.toUtf8());
+ }
+ }
+ }
+
+ return convertOString(aJson.finishAndGetAsOString());
+}
+
+static void lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long const features)
+{
+ comphelper::ProfileZone aZone("lo_setOptionalFeatures");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ pLib->mOptionalFeatures = features;
+ if (features & LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK)
+ comphelper::LibreOfficeKit::setPartInInvalidation(true);
+ if (features & LOK_FEATURE_NO_TILED_ANNOTATIONS)
+ comphelper::LibreOfficeKit::setTiledAnnotations(false);
+ if (features & LOK_FEATURE_RANGE_HEADERS)
+ comphelper::LibreOfficeKit::setRangeHeaders(true);
+ if (features & LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK)
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
+}
+
+static void lo_setDocumentPassword(LibreOfficeKit* pThis,
+ const char* pURL, const char* pPassword)
+{
+ comphelper::ProfileZone aZone("lo_setDocumentPassword");
+
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ assert(pThis);
+ assert(pURL);
+ LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ assert(pLib->mInteractionMap.find(OString(pURL)) != pLib->mInteractionMap.end());
+ pLib->mInteractionMap.find(OString(pURL))->second->SetPassword(pPassword);
+}
+
+static char* lo_getVersionInfo(SAL_UNUSED_PARAMETER LibreOfficeKit* /*pThis*/)
+{
+ SetLastExceptionMsg();
+ return convertOUString(ReplaceStringHookProc(
+ u"{ "
+ "\"ProductName\": \"%PRODUCTNAME\", "
+ "\"ProductVersion\": \"%PRODUCTVERSION\", "
+ "\"ProductExtension\": \"%PRODUCTEXTENSION\", "
+ "\"BuildId\": \"%BUILDID\""
+#if BUILDCONFIG_RECORDED
+ ", \"BuildConfig\": \"" BUILDCONFIG "\""
+#endif
+ " }"_ustr));
+}
+
+static void aBasicErrorFunc(const OUString& rError, const OUString& rAction)
+{
+ OString aBuffer = "Unexpected dialog: " +
+ OUStringToOString(rAction, RTL_TEXTENCODING_ASCII_US) +
+ " Error: " +
+ OUStringToOString(rError, RTL_TEXTENCODING_ASCII_US);
+
+ fprintf(stderr, "Unexpected basic error dialog '%s'\n", aBuffer.getStr());
+}
+
+static bool initialize_uno(const OUString& aAppProgramURL)
+{
+#ifdef IOS
+ // For iOS we already hardcode the inifile as "rc" in the .app directory.
+ rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("fundamental"));
+ xContext = cppu::defaultBootstrap_InitialComponentContext(aAppProgramURL + "/rc");
+#elif defined MACOSX
+ rtl::Bootstrap::setIniFilename(aAppProgramURL + "/../Resources/" SAL_CONFIGFILE("soffice"));
+ xContext = cppu::defaultBootstrap_InitialComponentContext();
+#else
+ rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("soffice"));
+ xContext = cppu::defaultBootstrap_InitialComponentContext();
+#endif
+
+ if (!xContext.is())
+ {
+ SetLastExceptionMsg(u"XComponentContext could not be created"_ustr);
+ SAL_INFO("lok", "XComponentContext could not be created");
+ return false;
+ }
+
+ xFactory = xContext->getServiceManager();
+ if (!xFactory.is())
+ {
+ SetLastExceptionMsg(u"XMultiComponentFactory could not be created"_ustr);
+ SAL_INFO("lok", "XMultiComponentFactory could not be created");
+ return false;
+ }
+
+ xSFactory.set(xFactory, uno::UNO_QUERY_THROW);
+ comphelper::setProcessServiceFactory(xSFactory);
+
+ SAL_INFO("lok", "Uno initialized - " << xContext.is());
+
+ // set UserInstallation to user profile dir in test/user-template
+// rtl::Bootstrap aDefaultVars;
+// aDefaultVars.set(OUString("UserInstallation"), aAppProgramURL + "../registry" );
+ // configmgr setup ?
+
+ return true;
+}
+
+// pre-unipoll version.
+static void lo_startmain(void*)
+{
+ osl_setThreadName("lo_startmain");
+
+ if (comphelper::SolarMutex::get())
+ Application::GetSolarMutex().tryToAcquire();
+
+ Application::UpdateMainThread();
+
+ soffice_main();
+
+ Application::ReleaseSolarMutex();
+}
+
+// unipoll version.
+static void lo_runLoop(LibreOfficeKit* /*pThis*/,
+ LibreOfficeKitPollCallback pPollCallback,
+ LibreOfficeKitWakeCallback pWakeCallback,
+ void* pData)
+{
+#if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__)
+ Application::GetSolarMutex().acquire();
+#endif
+
+ {
+ SolarMutexGuard aGuard;
+
+ vcl::lok::registerPollCallbacks(pPollCallback, pWakeCallback, pData);
+ Application::UpdateMainThread();
+ soffice_main();
+ }
+#if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__)
+ vcl::lok::unregisterPollCallbacks();
+ Application::ReleaseSolarMutex();
+#endif
+}
+
+static bool bInitialized = false;
+
+static void lo_status_indicator_callback(void *data, comphelper::LibreOfficeKit::statusIndicatorCallbackType type, int percent, const char* pText)
+{
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(data);
+
+ if (!pLib->mpCallback)
+ return;
+
+ switch (type)
+ {
+ case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Start:
+ pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_START, pText, pLib->mpCallbackData);
+ break;
+ case comphelper::LibreOfficeKit::statusIndicatorCallbackType::SetValue:
+ pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE,
+ OUString(OUString::number(percent)).toUtf8().getStr(), pLib->mpCallbackData);
+ break;
+ case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Finish:
+ pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_FINISH, nullptr, pLib->mpCallbackData);
+ break;
+ }
+}
+
+/// Used by preloadData (LibreOfficeKit) for providing different shortcuts for different languages.
+static void preLoadShortCutAccelerators()
+{
+ std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& acceleratorConfs = SfxLokHelper::getAcceleratorConfs();
+ css::uno::Sequence<OUString> installedLocales(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ OUString actualLang = officecfg::Setup::L10N::ooLocale::get();
+
+ for (sal_Int32 i = 0; i < installedLocales.getLength(); i++)
+ {
+ // Set the UI language to current one, before creating the accelerator.
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Setup::L10N::ooLocale::set(installedLocales[i], batch);
+ batch->commit();
+
+ // Supported module names: Writer, Calc, Draw, Impress
+ static constexpr OUString supportedModuleNames[] = {
+ u"com.sun.star.text.TextDocument"_ustr,
+ u"com.sun.star.sheet.SpreadsheetDocument"_ustr,
+ u"com.sun.star.drawing.DrawingDocument"_ustr,
+ u"com.sun.star.presentation.PresentationDocument"_ustr,
+ };
+ // Create the accelerators.
+ for (const OUString& supportedModuleName : supportedModuleNames)
+ {
+ OUString key = supportedModuleName + installedLocales[i];
+ acceleratorConfs[key] = svt::AcceleratorExecute::lok_createNewAcceleratorConfiguration(::comphelper::getProcessComponentContext(), supportedModuleName);
+ }
+ }
+
+ // Set the UI language back to default one.
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Setup::L10N::ooLocale::set(actualLang, batch);
+ batch->commit();
+}
+
+void setLanguageToolConfig();
+
+/// Used only by LibreOfficeKit when used by Online to pre-initialize
+static void preloadData()
+{
+ comphelper::ProfileZone aZone("preload data");
+
+ // Create user profile in the temp directory for loading the dictionaries
+ OUString sUserPath;
+ rtl::Bootstrap::get(u"UserInstallation"_ustr, sUserPath);
+ utl::TempFileNamed aTempDir(nullptr, true);
+ aTempDir.EnableKillingFile();
+ rtl::Bootstrap::set(u"UserInstallation"_ustr, aTempDir.GetURL());
+
+ // Register the bundled extensions
+ desktop::Desktop::SynchronizeExtensionRepositories(true);
+ bool bAbort = desktop::Desktop::CheckExtensionDependencies();
+ if(bAbort)
+ std::cerr << "CheckExtensionDependencies failed" << std::endl;
+
+ // setup LanguageTool config before spell checking init
+ setLanguageToolConfig();
+
+ // preload all available dictionaries
+ linguistic2::DictionaryList::create(comphelper::getProcessComponentContext());
+ css::uno::Reference<css::linguistic2::XLinguServiceManager> xLngSvcMgr =
+ css::linguistic2::LinguServiceManager::create(comphelper::getProcessComponentContext());
+ css::uno::Reference<linguistic2::XSpellChecker> xSpellChecker(xLngSvcMgr->getSpellChecker());
+
+ std::cerr << "Preloading dictionaries: ";
+ css::uno::Reference<linguistic2::XSupportedLocales> xSpellLocales(xSpellChecker, css::uno::UNO_QUERY_THROW);
+ uno::Sequence< css::lang::Locale > aLocales = xSpellLocales->getLocales();
+ for (auto &it : std::as_const(aLocales))
+ {
+ std::cerr << LanguageTag::convertToBcp47(it) << " ";
+ css::beans::PropertyValues aNone;
+ xSpellChecker->isValid(u"forcefed"_ustr, it, aNone);
+ }
+ std::cerr << "\n";
+
+ // Hack to load and cache the module liblocaledata_others.so which is not loaded normally
+ // (when loading dictionaries of just non-Asian locales). Creating a XCalendar4 of one Asian locale
+ // will cheaply load this missing "others" locale library. Appending an Asian locale in
+ // LOK_ALLOWLIST_LANGUAGES env-var also works but at the cost of loading that dictionary.
+ css::uno::Reference< css::i18n::XCalendar4 > xCal = css::i18n::LocaleCalendar2::create(comphelper::getProcessComponentContext());
+ css::lang::Locale aAsianLocale = { u"hi"_ustr, u"IN"_ustr, {} };
+ xCal->loadDefaultCalendar(aAsianLocale);
+
+ // preload all available thesauri
+ css::uno::Reference<linguistic2::XThesaurus> xThesaurus(xLngSvcMgr->getThesaurus());
+ css::uno::Reference<linguistic2::XSupportedLocales> xThesLocales(xSpellChecker, css::uno::UNO_QUERY_THROW);
+ aLocales = xThesLocales->getLocales();
+ std::cerr << "Preloading thesauri: ";
+ for (auto &it : std::as_const(aLocales))
+ {
+ std::cerr << LanguageTag::convertToBcp47(it) << " ";
+ css::beans::PropertyValues aNone;
+ xThesaurus->queryMeanings(u"forcefed"_ustr, it, aNone);
+ }
+ std::cerr << "\n";
+
+ css::uno::Reference< css::ui::XAcceleratorConfiguration > xGlobalCfg = css::ui::GlobalAcceleratorConfiguration::create(
+ comphelper::getProcessComponentContext());
+ xGlobalCfg->getAllKeyEvents();
+
+ std::cerr << "Preload icons\n";
+ ImageTree &images = ImageTree::get();
+ images.getImageUrl(u"forcefed.png"_ustr, u"style"_ustr, u"FO_oo"_ustr);
+
+ std::cerr << "Preload short cut accelerators\n";
+ preLoadShortCutAccelerators();
+
+ std::cerr << "Preload languages\n";
+
+ // force load language singleton
+ SvtLanguageTable::HasLanguageType(LANGUAGE_SYSTEM);
+ (void)LanguageTag::isValidBcp47(u"foo"_ustr, nullptr);
+
+ std::cerr << "Preload fonts\n";
+
+ // Initialize fonts.
+ css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext);
+ if (xLangSrv.is())
+ {
+ css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
+ if (xSpell.is())
+ aLocales = xSpell->getLocales();
+ }
+
+ for (const auto& aLocale : std::as_const(aLocales))
+ {
+ //TODO: Add more types and cache more aggressively. For now this initializes the fontcache.
+ using namespace ::com::sun::star::i18n::ScriptType;
+ LanguageType nLang;
+ nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), LATIN);
+ OutputDevice::GetDefaultFont(DefaultFontType::LATIN_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
+ nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), ASIAN);
+ OutputDevice::GetDefaultFont(DefaultFontType::CJK_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
+ nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), COMPLEX);
+ OutputDevice::GetDefaultFont(DefaultFontType::CTL_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
+ }
+
+ std::cerr << "Preload config\n";
+#if defined __GNUC__ || defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#endif
+ static SvtOptionsDialogOptions aDialogOptions;
+ static SvtCTLOptions aSvtCTLOptions;
+ static SvtAccessibilityOptions aSvtAccessibilityOptions;
+ static svtools::ColorConfig aColorConfig;
+ static SvtMiscOptions aSvtMiscOptions;
+ static SvtSlideSorterBarOptions aSvtSlideSorterBarOptions;
+ static SvtCommandOptions aSvtCommandOptions;
+ static SvtCompatibilityOptions aSvtCompatibilityOptions;
+ static SvtFilterOptions aSvtFilterOptions;
+ static SvtLinguConfig aSvtLinguConfig;
+ static SvtModuleOptions aSvtModuleOptions;
+ static SvtPathOptions aSvtPathOptions;
+ static SvtSearchOptions aSvtSearchOptions;
+ static SvtSysLocaleOptions aSvtSysLocaleOptions;
+ static SvtUserOptions aSvtUserOptions;
+ //static SvtViewOptions aSvtViewOptions;
+ static MouseSettings aMouseSettings;
+ static StyleSettings aStyleSettings;
+ static MiscSettings aMiscSettings;
+ static HelpSettings aHelpSettings;
+ static AllSettings aAllSettings;
+#if defined __GNUC__ || defined __clang__
+#pragma GCC diagnostic pop
+#endif
+
+ // Set user profile's path back to the original one
+ rtl::Bootstrap::set(u"UserInstallation"_ustr, sUserPath);
+}
+
+namespace {
+
+static void activateNotebookbar(std::u16string_view rApp)
+{
+ OUString aPath = OUString::Concat("org.openoffice.Office.UI.ToolbarMode/Applications/") + rApp;
+
+ const utl::OConfigurationTreeRoot aAppNode(xContext, aPath, true);
+
+ if (aAppNode.isValid())
+ {
+ static constexpr OUString sNoteBookbarName(u"notebookbar_online.ui"_ustr);
+ aAppNode.setNodeValue(u"Active"_ustr, Any(sNoteBookbarName));
+
+ const utl::OConfigurationNode aImplsNode = aAppNode.openNode(u"Modes"_ustr);
+ const Sequence<OUString> aModeNodeNames( aImplsNode.getNodeNames() );
+
+ for (const auto& rModeNodeName : aModeNodeNames)
+ {
+ const utl::OConfigurationNode aImplNode(aImplsNode.openNode(rModeNodeName));
+ if (!aImplNode.isValid())
+ continue;
+
+ OUString aCommandArg = comphelper::getString(aImplNode.getNodeValue(u"CommandArg"_ustr));
+ if (aCommandArg == "notebookbar.ui")
+ aImplNode.setNodeValue(u"CommandArg"_ustr, Any(sNoteBookbarName));
+ }
+
+ aAppNode.commit();
+ }
+}
+
+void setHelpRootURL()
+{
+ const char* pHelpRootURL = ::getenv("LOK_HELP_URL");
+ if (pHelpRootURL)
+ {
+ OUString aHelpRootURL = OStringToOUString(pHelpRootURL, RTL_TEXTENCODING_UTF8);
+ try
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Help::HelpRootURL::set(aHelpRootURL, batch);
+ batch->commit();
+ }
+ catch (uno::Exception const& rException)
+ {
+ SAL_WARN("lok", "Failed to set the help root URL: " << rException.Message);
+ }
+ }
+}
+
+void setCertificateDir()
+{
+ const char* pEnvVarString = ::getenv("LO_CERTIFICATE_DATABASE_PATH");
+ if (pEnvVarString)
+ {
+ OUString aCertificateDatabasePath = OStringToOUString(pEnvVarString, RTL_TEXTENCODING_UTF8);
+ try
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Security::Scripting::CertDir::set(aCertificateDatabasePath, pBatch);
+ officecfg::Office::Common::Security::Scripting::ManualCertDir::set(aCertificateDatabasePath, pBatch);
+ pBatch->commit();
+ }
+ catch (uno::Exception const& rException)
+ {
+ SAL_WARN("lok", "Failed to set the NSS certificate database directory: " << rException.Message);
+ }
+ }
+}
+
+void setDeeplConfig()
+{
+ const char* pAPIUrlString = ::getenv("DEEPL_API_URL");
+ const char* pAuthKeyString = ::getenv("DEEPL_AUTH_KEY");
+ if (pAPIUrlString && pAuthKeyString)
+ {
+ OUString aAPIUrl = OStringToOUString(pAPIUrlString, RTL_TEXTENCODING_UTF8);
+ OUString aAuthKey = OStringToOUString(pAuthKeyString, RTL_TEXTENCODING_UTF8);
+ try
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Linguistic::Translation::Deepl::ApiURL::set(aAPIUrl, batch);
+ officecfg::Office::Linguistic::Translation::Deepl::AuthKey::set(aAuthKey, batch);
+ batch->commit();
+ }
+ catch(uno::Exception const& rException)
+ {
+ SAL_WARN("lok", "Failed to set Deepl API settings: " << rException.Message);
+ }
+ }
+}
+
+void setLanguageToolConfig()
+{
+ const char* pEnabled = ::getenv("LANGUAGETOOL_ENABLED");
+ const char* pBaseUrlString = ::getenv("LANGUAGETOOL_BASEURL");
+
+ if (pEnabled && pBaseUrlString)
+ {
+ const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME");
+ const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY");
+ const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION");
+ const char* pRestProtocol = ::getenv("LANGUAGETOOL_RESTPROTOCOL");
+
+ OUString aEnabled = OStringToOUString(pEnabled, RTL_TEXTENCODING_UTF8);
+ if (aEnabled != "true")
+ return;
+ OUString aBaseUrl = OStringToOUString(pBaseUrlString, RTL_TEXTENCODING_UTF8);
+ try
+ {
+ using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool;
+ auto batch(comphelper::ConfigurationChanges::create());
+
+ LanguageToolCfg::BaseURL::set(aBaseUrl, batch);
+ LanguageToolCfg::IsEnabled::set(true, batch);
+ if (pSSLVerification)
+ {
+ OUString aSSLVerification = OStringToOUString(pSSLVerification, RTL_TEXTENCODING_UTF8);
+ LanguageToolCfg::SSLCertVerify::set(aSSLVerification == "true", batch);
+ }
+ if (pRestProtocol)
+ {
+ OUString aRestProtocol = OStringToOUString(pRestProtocol, RTL_TEXTENCODING_UTF8);
+ LanguageToolCfg::RestProtocol::set(aRestProtocol, batch);
+ }
+ if (pUsername && pApikey)
+ {
+ OUString aUsername = OStringToOUString(pUsername, RTL_TEXTENCODING_UTF8);
+ OUString aApiKey = OStringToOUString(pApikey, RTL_TEXTENCODING_UTF8);
+ LanguageToolCfg::Username::set(aUsername, batch);
+ LanguageToolCfg::ApiKey::set(aApiKey, batch);
+ }
+ batch->commit();
+
+ css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv =
+ css::linguistic2::LinguServiceManager::create(xContext);
+ if (xLangSrv.is())
+ {
+ css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
+ if (xSpell.is())
+ {
+ Sequence<OUString> aEmpty;
+ Sequence<css::lang::Locale> aLocales = xSpell->getLocales();
+
+ uno::Reference<linguistic2::XProofreader> xGC(
+ xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext),
+ uno::UNO_QUERY_THROW);
+ uno::Reference<linguistic2::XSupportedLocales> xSuppLoc(xGC, uno::UNO_QUERY_THROW);
+
+ for (int itLocale = 0; itLocale < aLocales.getLength(); itLocale++)
+ {
+ // turn off spell checker if LanguageTool supports the locale already
+ if (xSuppLoc->hasLocale(aLocales[itLocale]))
+ xLangSrv->setConfiguredServices(
+ SN_SPELLCHECKER, aLocales[itLocale], aEmpty);
+ }
+ }
+ }
+ }
+ catch(uno::Exception const& rException)
+ {
+ SAL_WARN("lok", "Failed to set LanguageTool API settings: " << rException.Message);
+ }
+ }
+}
+
+}
+
+static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char* pUserProfileUrl)
+{
+ enum {
+ PRE_INIT, // setup shared data in master process
+ SECOND_INIT, // complete init. after fork
+ FULL_INIT // do a standard complete init.
+ } eStage;
+
+ // Did we do a pre-initialize
+ static bool bPreInited = false;
+ static bool bUnipoll = false;
+ static bool bProfileZones = false;
+ static bool bNotebookbar = false;
+
+ { // cf. string lifetime for preinit
+ std::vector<OUString> aOpts;
+
+ // ':' delimited options - avoiding ABI change for new parameters
+ const char *pOptions = getenv("SAL_LOK_OPTIONS");
+ if (pOptions)
+ aOpts = comphelper::string::split(OUString(pOptions, strlen(pOptions), RTL_TEXTENCODING_UTF8), ':');
+ for (const auto &it : aOpts)
+ {
+ if (it == "unipoll")
+ bUnipoll = true;
+ else if (it == "profile_events")
+ bProfileZones = true;
+ else if (it == "sc_no_grid_bg")
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scNoGridBackground);
+ else if (it == "sc_print_twips_msgs")
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
+ else if (it == "notebookbar")
+ bNotebookbar = true;
+ }
+ }
+
+ // What stage are we at ?
+ if (pThis == nullptr)
+ {
+ eStage = PRE_INIT;
+ if (lok_preinit_2_called)
+ {
+ SAL_INFO("lok", "Create libreoffice object");
+ gImpl = new LibLibreOffice_Impl();
+ }
+ }
+ else if (bPreInited)
+ eStage = SECOND_INIT;
+ else
+ eStage = FULL_INIT;
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+
+ if (bInitialized)
+ return 1;
+
+ // Turn profile zones on early
+ if (bProfileZones && eStage == SECOND_INIT)
+ {
+ comphelper::TraceEvent::startRecording();
+ traceEventDumper = new TraceEventDumper();
+ }
+
+ comphelper::ProfileZone aZone("lok-init");
+
+ if (eStage == PRE_INIT)
+ {
+ rtl_alloc_preInit(true);
+
+ // Set the default timezone to the TZ envar, if set.
+ const char* tz = ::getenv("TZ");
+ SfxLokHelper::setDefaultTimezone(!!tz, tz ? OStringToOUString(tz, RTL_TEXTENCODING_UTF8)
+ : OUString());
+ }
+
+ if (eStage != SECOND_INIT)
+ comphelper::LibreOfficeKit::setActive();
+
+ if (eStage != PRE_INIT)
+ comphelper::LibreOfficeKit::setStatusIndicatorCallback(lo_status_indicator_callback, pLib);
+
+ if (pUserProfileUrl && eStage != PRE_INIT)
+ {
+ OUString url(
+ pUserProfileUrl, strlen(pUserProfileUrl), RTL_TEXTENCODING_UTF8);
+ OUString path;
+ if (url.startsWithIgnoreAsciiCase("vnd.sun.star.pathname:", &path))
+ {
+ OUString url2;
+ osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(
+ path, url2);
+ if (e == osl::FileBase::E_None)
+ url = url2;
+ else
+ SAL_WARN("lok", "resolving <" << url << "> failed with " << +e);
+ }
+ rtl::Bootstrap::set(u"UserInstallation"_ustr, url);
+ if (eStage == SECOND_INIT)
+ utl::Bootstrap::reloadData();
+ }
+
+ OUString aAppPath;
+ if (pAppPath)
+ {
+ aAppPath = OUString(pAppPath, strlen(pAppPath), RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+#if defined ANDROID || defined EMSCRIPTEN
+ aAppPath = OUString::fromUtf8(lo_get_app_data_dir()) + "/program";
+#else
+ // Fun conversion dance back and forth between URLs and system paths...
+ OUString aAppURL;
+ ::osl::Module::getUrlFromAddress( reinterpret_cast< oslGenericFunction >(lo_initialize),
+ aAppURL);
+ osl::FileBase::getSystemPathFromFileURL( aAppURL, aAppPath );
+#endif
+
+#ifdef IOS
+ // The above gives something like
+ // "/private/var/containers/Bundle/Application/953AA851-CC15-4C60-A2CB-C2C6F24E6F71/Foo.app/Foo",
+ // and we want to drop the final component (the binary name).
+ sal_Int32 lastSlash = aAppPath.lastIndexOf('/');
+ assert(lastSlash > 0);
+ aAppPath = aAppPath.copy(0, lastSlash);
+#endif
+ }
+
+ OUString aAppURL;
+ if (osl::FileBase::getFileURLFromSystemPath(aAppPath, aAppURL) != osl::FileBase::E_None)
+ return 0;
+
+#ifdef IOS
+ // A LibreOffice-using iOS app should have the ICU data file in the app bundle. Initialize ICU
+ // to use that.
+ NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
+
+ int fd = open([[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String], O_RDONLY);
+ if (fd == -1)
+ NSLog(@"Could not open ICU data file %s", [[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String]);
+ else
+ {
+ struct stat st;
+ if (fstat(fd, &st) == -1)
+ NSLog(@"fstat on ICU data file failed: %s", strerror(errno));
+ else
+ {
+ void *icudata = mmap(0, (size_t) st.st_size, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
+ if (icudata == MAP_FAILED)
+ NSLog(@"mmap failed: %s", strerror(errno));
+ else
+ {
+ UErrorCode icuStatus = U_ZERO_ERROR;
+ udata_setCommonData(icudata, &icuStatus);
+ if (U_FAILURE(icuStatus))
+ NSLog(@"udata_setCommonData failed");
+ else
+ {
+ // Quick test that ICU works...
+ UConverter *cnv = ucnv_open("iso-8859-3", &icuStatus);
+ if (U_SUCCESS(icuStatus))
+ ucnv_close(cnv);
+ else
+ NSLog(@"ucnv_open() failed: %s", u_errorName(icuStatus));
+ }
+ }
+ }
+ close(fd);
+ }
+#endif
+
+ try
+ {
+ if (eStage != SECOND_INIT)
+ {
+ SAL_INFO("lok", "Attempting to initialize UNO");
+
+ if (!initialize_uno(aAppURL))
+ return false;
+
+ // Force headless -- this is only for bitmap rendering.
+ rtl::Bootstrap::set(u"SAL_USE_VCLPLUGIN"_ustr, u"svp"_ustr);
+
+ // We specifically need to make sure we have the "headless"
+ // command arg set (various code specifically checks via
+ // CommandLineArgs):
+ desktop::Desktop::GetCommandLineArgs().setHeadless();
+
+#ifdef IOS
+ if (InitVCL() && [NSThread isMainThread])
+ {
+ static bool bFirstTime = true;
+ if (bFirstTime)
+ {
+ Application::GetSolarMutex().release();
+ bFirstTime = false;
+ }
+ }
+ SfxApplication::GetOrCreate();
+#endif
+
+#if HAVE_FEATURE_ANDROID_LOK
+ // Register the bundled extensions - so that the dictionaries work
+ desktop::Desktop::SynchronizeExtensionRepositories(false);
+ bool bFailed = desktop::Desktop::CheckExtensionDependencies();
+ if (bFailed)
+ SAL_INFO("lok", "CheckExtensionDependencies failed");
+#endif
+
+ if (eStage == PRE_INIT)
+ {
+ {
+ comphelper::ProfileZone aInit("Init vcl");
+ std::cerr << "Init vcl\n";
+ InitVCL();
+ }
+
+ // pre-load all component libraries.
+ if (!xContext.is())
+ throw css::uno::DeploymentException(u"preInit: XComponentContext is not created"_ustr);
+
+ css::uno::Reference< css::uno::XInterface > xService;
+ xContext->getValueByName(u"/singletons/com.sun.star.lang.theServiceManager"_ustr) >>= xService;
+ if (!xService.is())
+ throw css::uno::DeploymentException(u"preInit: XMultiComponentFactory is not created"_ustr);
+
+ css::uno::Reference<css::lang::XInitialization> aService(
+ xService, css::uno::UNO_QUERY_THROW);
+
+ // pre-requisites:
+ // In order to load implementations and invoke
+ // component factory it is required:
+ // 1) defaultBootstrap_InitialComponentContext()
+ // 2) comphelper::setProcessServiceFactory(xSFactory);
+ // 3) InitVCL()
+ {
+ comphelper::ProfileZone aInit("preload");
+ aService->initialize({css::uno::Any(u"preload"_ustr)});
+ }
+ { // Force load some modules
+ comphelper::ProfileZone aInit("preload modules");
+ VclBuilderPreload();
+ VclAbstractDialogFactory::Create();
+ }
+
+ preloadData();
+
+ // Release Solar Mutex, lo_startmain thread should acquire it.
+ Application::ReleaseSolarMutex();
+ }
+
+ setLanguageAndLocale(u"en-US"_ustr);
+ }
+
+ if (eStage != PRE_INIT)
+ {
+ SAL_INFO("lok", "Re-initialize temp paths");
+ SvtPathOptions aOptions;
+ OUString aNewTemp;
+ osl::FileBase::getTempDirURL(aNewTemp);
+ aOptions.SetTempPath(aNewTemp);
+ desktop::Desktop::CreateTemporaryDirectory();
+
+ // The RequestHandler is specifically set to be ready when all the other
+ // init in Desktop::Main (run from soffice_main) is done. We can enable
+ // the RequestHandler here (without starting any IPC thread;
+ // shortcutting the invocation in Desktop::Main that would start the IPC
+ // thread), and can then use it to wait until we're definitely ready to
+ // continue.
+
+ SAL_INFO("lok", "Enabling RequestHandler");
+ RequestHandler::Enable(false);
+ SAL_INFO("lok", "Starting soffice_main");
+ RequestHandler::SetReady(false);
+ if (!bUnipoll)
+ {
+ // Start the main thread only in non-unipoll mode (i.e. multithreaded).
+ pLib->maThread = osl_createThread(lo_startmain, nullptr);
+ SAL_INFO("lok", "Waiting for RequestHandler");
+ RequestHandler::WaitForReady();
+ SAL_INFO("lok", "RequestHandler ready -- continuing");
+ }
+ else
+ InitVCL();
+ }
+
+ if (eStage != SECOND_INIT)
+ ErrorRegistry::RegisterDisplay(aBasicErrorFunc);
+
+ SAL_INFO("lok", "LOK Initialized");
+ if (eStage == PRE_INIT)
+ bPreInited = true;
+ else
+ bInitialized = true;
+ }
+ catch (css::uno::Exception& exception)
+ {
+ fprintf(stderr, "Bootstrapping exception '%s'\n",
+ OUStringToOString(exception.Message, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ if (eStage == PRE_INIT)
+ {
+ comphelper::ThreadPool::getSharedOptimalPool().shutdown();
+ }
+
+// Turn off quick editing on iOS, Android and Emscripten
+#if defined IOS || defined ANDROID || defined __EMSCRIPTEN__
+ if (officecfg::Office::Impress::Misc::TextObject::QuickEditing::get())
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Impress::Misc::TextObject::QuickEditing::set(false, batch);
+ batch->commit();
+ }
+#endif
+
+
+ setHelpRootURL();
+ setCertificateDir();
+ setDeeplConfig();
+ setLanguageToolConfig();
+
+ if (bNotebookbar)
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::UI::ToolbarMode::ActiveWriter::set(u"notebookbar_online.ui"_ustr, batch);
+ officecfg::Office::UI::ToolbarMode::ActiveCalc::set(u"notebookbar_online.ui"_ustr, batch);
+ officecfg::Office::UI::ToolbarMode::ActiveImpress::set(u"notebookbar_online.ui"_ustr, batch);
+ officecfg::Office::UI::ToolbarMode::ActiveDraw::set(u"notebookbar_online.ui"_ustr, batch);
+ batch->commit();
+
+ activateNotebookbar(u"Writer");
+ activateNotebookbar(u"Calc");
+ activateNotebookbar(u"Impress");
+ activateNotebookbar(u"Draw");
+ }
+
+ // staticize all strings.
+ if (eStage == PRE_INIT)
+ rtl_alloc_preInit(false);
+
+ return bInitialized;
+}
+
+SAL_JNI_EXPORT
+LibreOfficeKit *libreofficekit_hook_2(const char* install_path, const char* user_profile_url)
+{
+ static bool alreadyCalled = false;
+
+ if ((!lok_preinit_2_called && !gImpl) || (lok_preinit_2_called && !alreadyCalled))
+ {
+ alreadyCalled = true;
+
+ if (!lok_preinit_2_called)
+ {
+ SAL_INFO("lok", "Create libreoffice object");
+ gImpl = new LibLibreOffice_Impl();
+ }
+
+ if (!lo_initialize(gImpl, install_path, user_profile_url))
+ {
+ lo_destroy(gImpl);
+ }
+ }
+ return static_cast<LibreOfficeKit*>(gImpl);
+}
+
+SAL_JNI_EXPORT
+LibreOfficeKit *libreofficekit_hook(const char* install_path)
+{
+ return libreofficekit_hook_2(install_path, nullptr);
+}
+
+SAL_JNI_EXPORT
+int lok_preinit(const char* install_path, const char* user_profile_url)
+{
+ return lo_initialize(nullptr, install_path, user_profile_url);
+}
+
+SAL_JNI_EXPORT
+int lok_preinit_2(const char* install_path, const char* user_profile_url, LibreOfficeKit** kit)
+{
+ lok_preinit_2_called = true;
+ int result = lo_initialize(nullptr, install_path, user_profile_url);
+ if (kit != nullptr)
+ *kit = gImpl;
+ return result;
+}
+
+static void lo_destroy(LibreOfficeKit* pThis)
+{
+ SolarMutexClearableGuard aGuard;
+
+ LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
+ gImpl = nullptr;
+
+ SAL_INFO("lok", "LO Destroy");
+
+ comphelper::LibreOfficeKit::setStatusIndicatorCallback(nullptr, nullptr);
+ uno::Reference <frame::XDesktop2> xDesktop = frame::Desktop::create ( ::comphelper::getProcessComponentContext() );
+ // FIXME: the terminate() call here is a no-op because it detects
+ // that LibreOfficeKit::isActive() and then returns early!
+ bool bSuccess = xDesktop.is() && xDesktop->terminate();
+
+ if (!bSuccess)
+ {
+ bSuccess = GetpApp() && GetpApp()->QueryExit();
+ }
+
+ if (!bSuccess)
+ {
+ Application::Quit();
+ }
+
+ aGuard.clear();
+
+ osl_joinWithThread(pLib->maThread);
+ osl_destroyThread(pLib->maThread);
+
+ delete pLib;
+ bInitialized = false;
+ SAL_INFO("lok", "LO Destroy Done");
+}
+
+} // extern "C"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/lokandroid.cxx b/desktop/source/lib/lokandroid.cxx
new file mode 100644
index 0000000000..e800c82b07
--- /dev/null
+++ b/desktop/source/lib/lokandroid.cxx
@@ -0,0 +1,422 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <unistd.h>
+#include <jni.h>
+
+#include <sal/types.h>
+#include <vcl/event.hxx>
+#include <android/log.h>
+
+#include <osl/detail/android-bootstrap.h>
+
+#include <LibreOfficeKit/LibreOfficeKit.h>
+
+/* LibreOfficeKit */
+
+namespace
+{
+
+jfieldID getHandleField(JNIEnv* pEnv, jobject aObject)
+{
+ jclass clazz = pEnv->GetObjectClass(aObject);
+ return pEnv->GetFieldID(clazz, "handle", "Ljava/nio/ByteBuffer;");
+}
+
+template <typename T>
+T* getHandle(JNIEnv* pEnv, jobject aObject)
+{
+ jobject aHandle = pEnv->GetObjectField(aObject, getHandleField(pEnv, aObject));
+ return reinterpret_cast<T*>(pEnv->GetDirectBufferAddress(aHandle));
+}
+
+const char* copyJavaString(JNIEnv* pEnv, jstring aJavaString)
+{
+ const char* pTemp = pEnv->GetStringUTFChars(aJavaString, NULL);
+ const char* pClone = strdup(pTemp);
+ pEnv->ReleaseStringUTFChars(aJavaString, pTemp);
+
+ return pClone;
+}
+
+} // anonymous namespace
+
+extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Office_getError
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+ char* pError = pLibreOfficeKit->pClass->getError(pLibreOfficeKit);
+ return pEnv->NewStringUTF(pError);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_destroy
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+ pLibreOfficeKit->pClass->destroy(pLibreOfficeKit);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_destroyAndExit(JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+ pLibreOfficeKit->pClass->destroy(pLibreOfficeKit);
+ // Stopgap fix: _exit() to force the OS to restart the LO activity.
+ // Better than to hang.
+ _exit(0);
+}
+
+namespace
+{
+
+struct CallbackData
+{
+ jmethodID aJavaCallbackMethod;
+ jclass aClass;
+ jobject aObject;
+};
+
+static CallbackData gCallbackData;
+static CallbackData gCallbackDataLOKit;
+
+/**
+ * Handle retrieved callback
+ */
+void messageCallback(int nType, const char* pPayload, void* pData)
+{
+ CallbackData* pCallbackData = (CallbackData*) pData;
+
+ JavaVM* pJavaVM = lo_get_javavm();
+ JNIEnv* pEnv;
+ bool bIsAttached = false;
+
+ int status = pJavaVM->GetEnv((void **) &pEnv, JNI_VERSION_1_6);
+
+ if(status < 0)
+ {
+ status = pJavaVM->AttachCurrentThread(&pEnv, NULL);
+ if(status < 0)
+ {
+ return;
+ }
+ bIsAttached = true;
+ }
+
+ jstring sPayload = pEnv->NewStringUTF(pPayload);
+
+ jvalue aParameter[2];
+ aParameter[0].i = nType;
+ aParameter[1].l = sPayload;
+
+ pEnv->CallVoidMethodA(pCallbackData->aObject, pCallbackData->aJavaCallbackMethod, aParameter);
+
+ pEnv->DeleteLocalRef(sPayload);
+
+ if (bIsAttached)
+ {
+ pJavaVM->DetachCurrentThread();
+ }
+}
+
+} // anonymous namespace
+
+extern "C" SAL_JNI_EXPORT jobject JNICALL Java_org_libreoffice_kit_Office_documentLoadNative
+ (JNIEnv* pEnv, jobject aObject, jstring documentPath)
+{
+ const char* aCloneDocumentPath = copyJavaString(pEnv, documentPath);
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+
+ LibreOfficeKitDocument* pDocument = pLibreOfficeKit->pClass->documentLoad(pLibreOfficeKit, aCloneDocumentPath);
+
+ if (pDocument == NULL)
+ return NULL;
+
+ jobject aHandle = pEnv->NewDirectByteBuffer((void*) pDocument, sizeof(LibreOfficeKitDocument));
+
+ return aHandle;
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_setDocumentPassword
+ (JNIEnv* pEnv, jobject aObject, jstring sUrl, jstring sPassword)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+
+ char const* pUrl = copyJavaString(pEnv, sUrl);
+ if (sPassword == NULL) {
+ pLibreOfficeKit->pClass->setDocumentPassword(pLibreOfficeKit, pUrl, nullptr);
+ } else {
+ char const* pPassword = copyJavaString(pEnv, sPassword);
+ pLibreOfficeKit->pClass->setDocumentPassword(pLibreOfficeKit, pUrl, pPassword);
+ }
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_setOptionalFeatures
+ (JNIEnv* pEnv, jobject aObject, jlong options)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+
+ unsigned long long pOptions = (unsigned long long)options;
+
+ pLibreOfficeKit->pClass->setOptionalFeatures(pLibreOfficeKit, pOptions);
+}
+
+/** Implementation of org.libreoffice.kit.Office.bindMessageCallback method */
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_bindMessageCallback
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject);
+
+ gCallbackDataLOKit.aObject = (jobject) pEnv->NewGlobalRef(aObject);
+ jclass aClass = pEnv->GetObjectClass(aObject);
+ gCallbackDataLOKit.aClass = (jclass) pEnv->NewGlobalRef(aClass);
+
+ gCallbackDataLOKit.aJavaCallbackMethod = pEnv->GetMethodID(aClass, "messageRetrievedLOKit", "(ILjava/lang/String;)V");
+
+ pLibreOfficeKit->pClass->registerCallback(pLibreOfficeKit, messageCallback, (void*) &gCallbackDataLOKit);
+}
+
+/* Document */
+
+/** Implementation of org.libreoffice.kit.Document.bindMessageCallback method */
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_bindMessageCallback
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ gCallbackData.aObject = (jobject) pEnv->NewGlobalRef(aObject);
+ jclass aClass = pEnv->GetObjectClass(aObject);
+ gCallbackData.aClass = (jclass) pEnv->NewGlobalRef(aClass);
+
+ gCallbackData.aJavaCallbackMethod = pEnv->GetMethodID(aClass, "messageRetrieved", "(ILjava/lang/String;)V");
+
+ pDocument->pClass->registerCallback(pDocument, messageCallback, (void*) &gCallbackData);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_destroy
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->destroy(pDocument);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setPart
+ (JNIEnv* pEnv, jobject aObject, jint aPart)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->setPart(pDocument, aPart);
+}
+
+extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getPart
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ return (jint) pDocument->pClass->getPart(pDocument);
+}
+
+extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getPartPageRectangles
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument);
+ return pEnv->NewStringUTF(pRectangles);
+}
+
+extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getParts
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ return (jint) pDocument->pClass->getParts(pDocument);
+}
+
+extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getPartName
+ (JNIEnv* pEnv, jobject aObject, jint nPartIndex)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ char* pPartName = pDocument->pClass->getPartName(pDocument, nPartIndex);
+ return pEnv->NewStringUTF(pPartName);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setPartMode
+ (JNIEnv* pEnv, jobject aObject, jint nPartMode)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ pDocument->pClass->setPartMode(pDocument, nPartMode);
+}
+
+extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getDocumentTypeNative
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ return (jint) pDocument->pClass->getDocumentType(pDocument);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_paintTileNative
+ (JNIEnv* pEnv, jobject aObject, jobject aByteBuffer,
+ jint nCanvasWidth, jint nCanvasHeight, jint nTilePosX, jint nTilePosY,
+ jint nTileWidth, jint nTileHeight)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ unsigned char* buffer = (unsigned char*) pEnv->GetDirectBufferAddress(aByteBuffer);
+ pDocument->pClass->paintTile(pDocument, buffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
+}
+
+extern "C" SAL_JNI_EXPORT jlong JNICALL Java_org_libreoffice_kit_Document_getDocumentHeight
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ long nWidth;
+ long nHeight;
+ pDocument->pClass->getDocumentSize(pDocument, &nWidth, &nHeight);
+ return nHeight;
+}
+
+extern "C" SAL_JNI_EXPORT jlong JNICALL Java_org_libreoffice_kit_Document_getDocumentWidth
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ long nWidth;
+ long nHeight;
+ pDocument->pClass->getDocumentSize(pDocument, &nWidth, &nHeight);
+ return nWidth;
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_initializeForRendering
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->initializeForRendering(pDocument, NULL);
+}
+
+extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_saveAs
+ (JNIEnv* pEnv, jobject aObject, jstring sUrl, jstring sFormat, jstring sOptions)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ const char* pUrl = pEnv->GetStringUTFChars(sUrl, NULL);
+ const char* pFormat = pEnv->GetStringUTFChars(sFormat, NULL);
+ const char* pOptions = pEnv->GetStringUTFChars(sOptions, NULL);
+
+ int result = pDocument->pClass->saveAs(pDocument, pUrl, pFormat, pOptions);
+
+ pEnv->ReleaseStringUTFChars(sUrl, pUrl);
+ pEnv->ReleaseStringUTFChars(sFormat, pFormat);
+ pEnv->ReleaseStringUTFChars(sOptions, pOptions);
+
+ return result;
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postKeyEvent
+ (JNIEnv* pEnv, jobject aObject, jint nType, jint nCharCode, jint nKeyCode)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->postKeyEvent(pDocument, nType, nCharCode, nKeyCode);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postMouseEvent
+ (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y, jint count, jint button, jint modifier)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->postMouseEvent(pDocument, type, x, y, count, button, modifier);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postUnoCommand
+ (JNIEnv* pEnv, jobject aObject, jstring command, jstring arguments, jboolean bNotifyWhenFinished)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ const char* pCommand = pEnv->GetStringUTFChars(command, NULL);
+ const char* pArguments = nullptr;
+ if (arguments != NULL)
+ pArguments = pEnv->GetStringUTFChars(arguments, NULL);
+
+ pDocument->pClass->postUnoCommand(pDocument, pCommand, pArguments, bNotifyWhenFinished);
+
+ pEnv->ReleaseStringUTFChars(command, pCommand);
+ if (arguments != NULL)
+ pEnv->ReleaseStringUTFChars(arguments, pArguments);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setTextSelection
+ (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->setTextSelection(pDocument, type, x, y);
+}
+
+extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getTextSelection
+ (JNIEnv* pEnv, jobject aObject, jstring mimeType)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ const char* pMimeType = pEnv->GetStringUTFChars(mimeType, NULL);
+
+ char* pUsedMimeType = 0;
+ LibreOfficeKitDocumentClass* pcls = pDocument->pClass;
+ char* pSelection = pcls->getTextSelection(pDocument, pMimeType, &pUsedMimeType);
+ free(pUsedMimeType);
+
+ pEnv->ReleaseStringUTFChars(mimeType, pMimeType);
+
+ return pEnv->NewStringUTF(pSelection);
+}
+
+extern "C" SAL_JNI_EXPORT jboolean JNICALL Java_org_libreoffice_kit_Document_paste
+ (JNIEnv* pEnv, jobject aObject, jstring mimeType, jstring data)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ const char* pMimeType = pEnv->GetStringUTFChars(mimeType, NULL);
+ const char* pData = pEnv->GetStringUTFChars(data, NULL);
+ const size_t nSize = pEnv->GetStringLength(data);
+
+ LibreOfficeKitDocumentClass* pcls = pDocument->pClass;
+ bool result = pcls->paste(pDocument, pMimeType, pData, nSize);
+ pEnv->ReleaseStringUTFChars(mimeType, pMimeType);
+ pEnv->ReleaseStringUTFChars(data, pData);
+
+ return result;
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setGraphicSelection
+ (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->setGraphicSelection(pDocument, type, x, y);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_resetSelection
+ (JNIEnv* pEnv, jobject aObject)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->resetSelection(pDocument);
+}
+
+extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getCommandValues
+ (JNIEnv* pEnv, jobject aObject, jstring command)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+
+ const char* pCommand = pEnv->GetStringUTFChars(command, NULL);
+
+ char* pValue = pDocument->pClass->getCommandValues(pDocument, pCommand);
+
+ pEnv->ReleaseStringUTFChars(command, pCommand);
+
+ return pEnv->NewStringUTF(pValue);
+}
+
+extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setClientZoom
+ (JNIEnv* pEnv, jobject aObject, jint nTilePixelWidth, jint nTilePixelHeight, jint nTileTwipWidth, jint nTileTwipHeight)
+{
+ LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject);
+ pDocument->pClass->setClientZoom(pDocument, nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight);
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/lokclipboard.cxx b/desktop/source/lib/lokclipboard.cxx
new file mode 100644
index 0000000000..f7d52ba466
--- /dev/null
+++ b/desktop/source/lib/lokclipboard.cxx
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "lokclipboard.hxx"
+#include <unordered_map>
+#include <vcl/lazydelete.hxx>
+#include <vcl/svapp.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <sal/log.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+/* static */ osl::Mutex LOKClipboardFactory::gMutex;
+static vcl::DeleteOnDeinit<std::unordered_map<int, rtl::Reference<LOKClipboard>>> gClipboards{};
+
+rtl::Reference<LOKClipboard> LOKClipboardFactory::getClipboardForCurView()
+{
+ int nViewId = SfxLokHelper::getView(); // currently active.
+
+ osl::MutexGuard aGuard(gMutex);
+
+ auto it = gClipboards.get()->find(nViewId);
+ if (it != gClipboards.get()->end())
+ {
+ SAL_INFO("lok", "Got clip: " << it->second.get() << " from " << nViewId);
+ return it->second;
+ }
+ rtl::Reference<LOKClipboard> xClip(new LOKClipboard());
+ (*gClipboards.get())[nViewId] = xClip;
+ SAL_INFO("lok", "Created clip: " << xClip.get() << " for viewId " << nViewId);
+ return xClip;
+}
+
+void LOKClipboardFactory::releaseClipboardForView(int nViewId)
+{
+ osl::MutexGuard aGuard(gMutex);
+
+ if (nViewId < 0) // clear all
+ {
+ gClipboards.get()->clear();
+ SAL_INFO("lok", "Released all clipboards on doc destroy\n");
+ }
+ else if (gClipboards.get())
+ {
+ auto it = gClipboards.get()->find(nViewId);
+ if (it != gClipboards.get()->end())
+ {
+ SAL_INFO("lok", "Releasing clip: " << it->second.get() << " for destroyed " << nViewId);
+ gClipboards.get()->erase(it);
+ }
+ }
+}
+
+uno::Reference<uno::XInterface>
+ SAL_CALL LOKClipboardFactory::createInstanceWithArguments(const Sequence<Any>& /* rArgs */)
+{
+ return { static_cast<cppu::OWeakObject*>(getClipboardForCurView().get()) };
+}
+
+LOKClipboard::LOKClipboard()
+ : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo>(m_aMutex)
+{
+ // Encourage 'paste' menu items to always show up.
+ uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable());
+ setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>());
+}
+
+Sequence<OUString> LOKClipboard::getSupportedServiceNames_static()
+{
+ Sequence<OUString> aRet{ "com.sun.star.datatransfer.clipboard.LokClipboard" };
+ return aRet;
+}
+
+OUString LOKClipboard::getImplementationName() { return "com.sun.star.datatransfer.LOKClipboard"; }
+
+Sequence<OUString> LOKClipboard::getSupportedServiceNames()
+{
+ return getSupportedServiceNames_static();
+}
+
+sal_Bool LOKClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Reference<css::datatransfer::XTransferable> LOKClipboard::getContents() { return m_xTransferable; }
+
+void LOKClipboard::setContents(
+ const Reference<css::datatransfer::XTransferable>& xTrans,
+ const Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+ Reference<datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
+ Reference<datatransfer::XTransferable> xOldContents(m_xTransferable);
+ m_xTransferable = xTrans;
+ m_aOwner = xClipboardOwner;
+
+ std::vector<Reference<datatransfer::clipboard::XClipboardListener>> aListeners(m_aListeners);
+ datatransfer::clipboard::ClipboardEvent aEv;
+ aEv.Contents = m_xTransferable;
+ SAL_INFO("lok", "Clip: " << this << " set contents to " << m_xTransferable);
+
+ aGuard.clear();
+
+ if (xOldOwner.is() && xOldOwner != xClipboardOwner)
+ xOldOwner->lostOwnership(this, xOldContents);
+ for (auto const& listener : aListeners)
+ {
+ listener->changedContents(aEv);
+ }
+}
+
+void LOKClipboard::addClipboardListener(
+ const Reference<datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+ m_aListeners.push_back(listener);
+}
+
+void LOKClipboard::removeClipboardListener(
+ const Reference<datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+ std::erase(m_aListeners, listener);
+}
+LOKTransferable::LOKTransferable(const OUString& sMimeType,
+ const css::uno::Sequence<sal_Int8>& aSequence)
+{
+ m_aContent.reserve(1);
+ m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(1);
+ initFlavourFromMime(m_aFlavors.getArray()[0], sMimeType);
+
+ uno::Any aContent;
+ if (m_aFlavors[0].DataType == cppu::UnoType<OUString>::get())
+ {
+ auto pText = reinterpret_cast<const char*>(aSequence.getConstArray());
+ aContent <<= OUString(pText, aSequence.getLength(), RTL_TEXTENCODING_UTF8);
+ }
+ else
+ aContent <<= aSequence;
+ m_aContent.push_back(aContent);
+}
+
+/// Use to ensure we have some dummy content on the clipboard to allow a 1st 'paste'
+LOKTransferable::LOKTransferable()
+{
+ m_aContent.reserve(1);
+ m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(1);
+ initFlavourFromMime(m_aFlavors.getArray()[0], "text/plain");
+ uno::Any aContent;
+ aContent <<= OUString();
+ m_aContent.push_back(aContent);
+}
+
+// cf. sot/source/base/exchange.cxx for these two exceptional types.
+void LOKTransferable::initFlavourFromMime(css::datatransfer::DataFlavor& rFlavor,
+ OUString aMimeType)
+{
+ if (aMimeType.startsWith("text/plain"))
+ {
+ aMimeType = "text/plain;charset=utf-16";
+ rFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ else if (aMimeType == "application/x-libreoffice-tsvc")
+ rFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ rFlavor.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
+ rFlavor.MimeType = aMimeType;
+ rFlavor.HumanPresentableName = aMimeType;
+}
+
+LOKTransferable::LOKTransferable(const size_t nInCount, const char** pInMimeTypes,
+ const size_t* pInSizes, const char** pInStreams)
+{
+ m_aContent.reserve(nInCount);
+ m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(nInCount);
+ auto p_aFlavors = m_aFlavors.getArray();
+ for (size_t i = 0; i < nInCount; ++i)
+ {
+ initFlavourFromMime(p_aFlavors[i], OUString::fromUtf8(pInMimeTypes[i]));
+
+ uno::Any aContent;
+ if (m_aFlavors[i].DataType == cppu::UnoType<OUString>::get())
+ aContent <<= OUString(pInStreams[i], pInSizes[i], RTL_TEXTENCODING_UTF8);
+ else
+ aContent <<= css::uno::Sequence<sal_Int8>(
+ reinterpret_cast<const sal_Int8*>(pInStreams[i]), pInSizes[i]);
+ m_aContent.push_back(aContent);
+ }
+}
+
+uno::Any SAL_CALL LOKTransferable::getTransferData(const datatransfer::DataFlavor& rFlavor)
+{
+ assert(m_aContent.size() == static_cast<size_t>(m_aFlavors.getLength()));
+ for (size_t i = 0; i < m_aContent.size(); ++i)
+ {
+ if (m_aFlavors[i].MimeType == rFlavor.MimeType)
+ {
+ if (m_aFlavors[i].DataType != rFlavor.DataType)
+ SAL_WARN("lok", "Horror type mismatch!");
+ return m_aContent[i];
+ }
+ }
+ return {};
+}
+
+uno::Sequence<datatransfer::DataFlavor> SAL_CALL LOKTransferable::getTransferDataFlavors()
+{
+ return m_aFlavors;
+}
+
+sal_Bool SAL_CALL LOKTransferable::isDataFlavorSupported(const datatransfer::DataFlavor& rFlavor)
+{
+ return std::find_if(std::cbegin(m_aFlavors), std::cend(m_aFlavors),
+ [&rFlavor](const datatransfer::DataFlavor& i) {
+ return i.MimeType == rFlavor.MimeType && i.DataType == rFlavor.DataType;
+ })
+ != std::cend(m_aFlavors);
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_LOKClipboard_get_implementation(css::uno::XComponentContext*,
+ css::uno::Sequence<css::uno::Any> const& /*args*/)
+{
+ SolarMutexGuard aGuard;
+
+ cppu::OWeakObject* pClipboard = LOKClipboardFactory::getClipboardForCurView().get();
+
+ pClipboard->acquire();
+ return pClipboard;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/lokclipboard.hxx b/desktop/source/lib/lokclipboard.hxx
new file mode 100644
index 0000000000..699830756a
--- /dev/null
+++ b/desktop/source/lib/lokclipboard.hxx
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <rtl/ref.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+
+using namespace css::uno;
+
+/// A clipboard implementation for LibreOfficeKit.
+class LOKClipboard final
+ : public cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ css::uno::Reference<css::datatransfer::XTransferable> m_xTransferable;
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
+ std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> m_aListeners;
+
+public:
+ LOKClipboard();
+
+ /// get an XInterface easily.
+ css::uno::Reference<css::uno::XInterface> getXI()
+ {
+ return { static_cast<cppu::OWeakObject*>(this) };
+ }
+
+ // XServiceInfo
+ OUString SAL_CALL getImplementationName() override;
+ sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+ static Sequence<OUString> getSupportedServiceNames_static();
+
+ // XClipboard
+ css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+ void SAL_CALL setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+ override;
+ OUString SAL_CALL getName() override { return "CLIPBOARD"; }
+
+ // XClipboardEx
+ sal_Int8 SAL_CALL getRenderingCapabilities() override { return 0; }
+
+ // XClipboardNotifier
+ void SAL_CALL addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+ void SAL_CALL removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+};
+
+/// Represents the contents of LOKClipboard.
+class LOKTransferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> m_aFlavors;
+ std::vector<css::uno::Any> m_aContent;
+
+ static void initFlavourFromMime(css::datatransfer::DataFlavor& rFlavor, OUString aMimeType);
+
+public:
+ LOKTransferable();
+ LOKTransferable(size_t nInCount, const char** pInMimeTypes, const size_t* pInSizes,
+ const char** pInStreams);
+ LOKTransferable(const OUString& sMimeType, const css::uno::Sequence<sal_Int8>& aSequence);
+
+ css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
+
+ css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+
+ sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
+};
+
+/// Theoretically to hook into the (horrible) vcl dtranscomp.cxx code.
+class LOKClipboardFactory : public ::cppu::WeakComponentImplHelper<css::lang::XSingleServiceFactory>
+{
+ static osl::Mutex gMutex;
+
+public:
+ LOKClipboardFactory()
+ : cppu::WeakComponentImplHelper<css::lang::XSingleServiceFactory>(gMutex)
+ {
+ }
+
+ css::uno::Reference<css::uno::XInterface> SAL_CALL createInstance() override
+ {
+ return createInstanceWithArguments(css::uno::Sequence<css::uno::Any>());
+ }
+ css::uno::Reference<css::uno::XInterface> SAL_CALL
+ createInstanceWithArguments(const css::uno::Sequence<css::uno::Any>& /* rArgs */) override;
+
+ /// Fetch clipboard from the global pool.
+ static rtl::Reference<LOKClipboard> getClipboardForCurView();
+
+ /// Release a clipboard before its document dies, nViewId of -1 clears all.
+ static void releaseClipboardForView(int nViewId);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/lokinteractionhandler.cxx b/desktop/source/lib/lokinteractionhandler.cxx
new file mode 100644
index 0000000000..a05091cedf
--- /dev/null
+++ b/desktop/source/lib/lokinteractionhandler.cxx
@@ -0,0 +1,457 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "lokinteractionhandler.hxx"
+
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/document/BrokenPackageRequest.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/task/XInteractionPassword2.hpp>
+#include <com/sun/star/task/DocumentMacroConfirmationRequest.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkOffLineException.hpp>
+
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
+
+#include <com/sun/star/task/DocumentPasswordRequest2.hpp>
+#include <com/sun/star/task/DocumentMSPasswordRequest2.hpp>
+
+#include <com/sun/star/document/FilterOptionsRequest.hpp>
+
+#include "../../inc/lib/init.hxx"
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <sfx2/lokhelper.hxx>
+#include <sfx2/viewsh.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <tools/json_writer.hxx>
+
+using namespace com::sun::star;
+
+LOKInteractionHandler::LOKInteractionHandler(
+ OString command,
+ desktop::LibLibreOffice_Impl *const pLOKit,
+ desktop::LibLODocument_Impl *const pLOKDocument)
+ : m_pLOKit(pLOKit)
+ , m_pLOKDocument(pLOKDocument)
+ , m_command(std::move(command))
+ , m_usePassword(false)
+{
+ assert(m_pLOKit);
+}
+
+LOKInteractionHandler::~LOKInteractionHandler()
+{
+}
+
+OUString SAL_CALL LOKInteractionHandler::getImplementationName()
+{
+ return "com.sun.star.comp.uui.LOKInteractionHandler";
+}
+
+sal_Bool SAL_CALL LOKInteractionHandler::supportsService(OUString const & rServiceName)
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL LOKInteractionHandler::getSupportedServiceNames()
+{
+ return { "com.sun.star.task.InteractionHandler",
+ // added to indicate support for configuration.backend.MergeRecoveryRequest
+ "com.sun.star.configuration.backend.InteractionHandler",
+ // for backwards compatibility
+ "com.sun.star.uui.InteractionHandler" };
+}
+
+void SAL_CALL LOKInteractionHandler::initialize(uno::Sequence<uno::Any> const & /*rArguments*/)
+{
+}
+
+void SAL_CALL LOKInteractionHandler::handle(
+ uno::Reference<task::XInteractionRequest> const & xRequest)
+{
+ // just do the same thing in both cases
+ handleInteractionRequest(xRequest);
+}
+
+void LOKInteractionHandler::postError(css::task::InteractionClassification classif, const char* kind, ErrCode code, const OUString &message)
+{
+ std::string classification = "error";
+ switch (classif)
+ {
+ case task::InteractionClassification_ERROR: break;
+ case task::InteractionClassification_WARNING: classification = "warning"; break;
+ case task::InteractionClassification_INFO: classification = "info"; break;
+ case task::InteractionClassification_QUERY: classification = "query"; break;
+ default: assert(false); break;
+ }
+
+ // create the JSON representation
+ tools::JsonWriter aJson;
+ aJson.put("classification", classification);
+ aJson.put("cmd", m_command.getStr());
+ aJson.put("kind", kind);
+ aJson.put("code", static_cast<sal_uInt32>(code));
+ aJson.put("message", message.toUtf8());
+
+ std::size_t nView = SfxViewShell::Current() ? SfxLokHelper::getView() : 0;
+ if (m_pLOKDocument && m_pLOKDocument->mpCallbackFlushHandlers.count(nView))
+ m_pLOKDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_ERROR, aJson.finishAndGetAsOString());
+ else if (m_pLOKit->mpCallback)
+ m_pLOKit->mpCallback(LOK_CALLBACK_ERROR, aJson.finishAndGetAsOString().getStr(), m_pLOKit->mpCallbackData);
+}
+
+namespace {
+
+/// Just approve the interaction.
+void selectApproved(uno::Sequence<uno::Reference<task::XInteractionContinuation>> const &rContinuations)
+{
+ for (auto const & c : rContinuations)
+ {
+ uno::Reference<task::XInteractionApprove> xApprove(c, uno::UNO_QUERY);
+ if (xApprove.is())
+ xApprove->select();
+ }
+}
+
+}
+
+bool LOKInteractionHandler::handleIOException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest)
+{
+ ucb::InteractiveIOException aIoException;
+ if (!(rRequest >>= aIoException))
+ return false;
+
+ static ErrCode const aErrorCode[int(ucb::IOErrorCode_WRONG_VERSION) + 1] =
+ {
+ ERRCODE_IO_ABORT,
+ ERRCODE_IO_ACCESSDENIED,
+ ERRCODE_IO_ALREADYEXISTS,
+ ERRCODE_IO_BADCRC,
+ ERRCODE_IO_CANTCREATE,
+ ERRCODE_IO_CANTREAD,
+ ERRCODE_IO_CANTSEEK,
+ ERRCODE_IO_CANTTELL,
+ ERRCODE_IO_CANTWRITE,
+ ERRCODE_IO_CURRENTDIR,
+ ERRCODE_IO_DEVICENOTREADY,
+ ERRCODE_IO_NOTSAMEDEVICE,
+ ERRCODE_IO_GENERAL,
+ ERRCODE_IO_INVALIDACCESS,
+ ERRCODE_IO_INVALIDCHAR,
+ ERRCODE_IO_INVALIDDEVICE,
+ ERRCODE_IO_INVALIDLENGTH,
+ ERRCODE_IO_INVALIDPARAMETER,
+ ERRCODE_IO_ISWILDCARD,
+ ERRCODE_IO_LOCKVIOLATION,
+ ERRCODE_IO_MISPLACEDCHAR,
+ ERRCODE_IO_NAMETOOLONG,
+ ERRCODE_IO_NOTEXISTS,
+ ERRCODE_IO_NOTEXISTSPATH,
+ ERRCODE_IO_NOTSUPPORTED,
+ ERRCODE_IO_NOTADIRECTORY,
+ ERRCODE_IO_NOTAFILE,
+ ERRCODE_IO_OUTOFSPACE,
+ ERRCODE_IO_TOOMANYOPENFILES,
+ ERRCODE_IO_OUTOFMEMORY,
+ ERRCODE_IO_PENDING,
+ ERRCODE_IO_RECURSIVE,
+ ERRCODE_IO_UNKNOWN,
+ ERRCODE_IO_WRITEPROTECTED,
+ ERRCODE_IO_WRONGFORMAT,
+ ERRCODE_IO_WRONGVERSION,
+ };
+
+ postError(aIoException.Classification, "io", aErrorCode[static_cast<int>(aIoException.Code)], "");
+ selectApproved(rContinuations);
+
+ return true;
+}
+
+bool LOKInteractionHandler::handleNetworkException(const uno::Sequence<uno::Reference<task::XInteractionContinuation>> &rContinuations, const uno::Any &rRequest)
+{
+ ucb::InteractiveNetworkException aNetworkException;
+ if (!(rRequest >>= aNetworkException))
+ return false;
+
+ ErrCode nErrorCode;
+ OUString aMessage;
+
+ ucb::InteractiveNetworkOffLineException aOffLineException;
+ ucb::InteractiveNetworkResolveNameException aResolveNameException;
+ ucb::InteractiveNetworkConnectException aConnectException;
+ ucb::InteractiveNetworkReadException aReadException;
+ ucb::InteractiveNetworkWriteException aWriteException;
+ if (rRequest >>= aOffLineException)
+ {
+ nErrorCode = ERRCODE_INET_OFFLINE;
+ }
+ else if (rRequest >>= aResolveNameException)
+ {
+ nErrorCode = ERRCODE_INET_NAME_RESOLVE;
+ aMessage = aResolveNameException.Server;
+ }
+ else if (rRequest >>= aConnectException)
+ {
+ nErrorCode = ERRCODE_INET_CONNECT;
+ aMessage = aConnectException.Server;
+ }
+ else if (rRequest >>= aReadException)
+ {
+ nErrorCode = ERRCODE_INET_READ;
+ aMessage = aReadException.Diagnostic;
+ }
+ else if (rRequest >>= aWriteException)
+ {
+ nErrorCode = ERRCODE_INET_WRITE;
+ aMessage = aWriteException.Diagnostic;
+ }
+ else
+ {
+ nErrorCode = ERRCODE_INET_GENERAL;
+ }
+
+ postError(aNetworkException.Classification, "network", nErrorCode, aMessage);
+ selectApproved(rContinuations);
+
+ return true;
+}
+
+bool LOKInteractionHandler::handlePasswordRequest(const uno::Sequence<uno::Reference<task::XInteractionContinuation>> &rContinuations, const uno::Any &rRequest)
+{
+ bool bPasswordRequestFound = false;
+ bool bIsRequestPasswordToModify = false;
+
+ OString sUrl;
+
+ task::DocumentPasswordRequest passwordRequest;
+ if (rRequest >>= passwordRequest)
+ {
+ bIsRequestPasswordToModify = false;
+ sUrl = passwordRequest.Name.toUtf8();
+ bPasswordRequestFound = true;
+ }
+
+ task::DocumentPasswordRequest2 passwordRequest2;
+ if (rRequest >>= passwordRequest2)
+ {
+ bIsRequestPasswordToModify = passwordRequest2.IsRequestPasswordToModify;
+ sUrl = passwordRequest2.Name.toUtf8();
+ bPasswordRequestFound = true;
+ }
+
+ task::DocumentMSPasswordRequest2 passwordMSRequest;
+ if (rRequest >>= passwordMSRequest)
+ {
+ bIsRequestPasswordToModify = passwordMSRequest.IsRequestPasswordToModify;
+ sUrl = passwordMSRequest.Name.toUtf8();
+ bPasswordRequestFound = true;
+ }
+
+ if (!bPasswordRequestFound)
+ return false;
+
+ if (m_pLOKit->mpCallback &&
+ m_pLOKit->hasOptionalFeature(bIsRequestPasswordToModify ? LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY
+ : LOK_FEATURE_DOCUMENT_PASSWORD))
+ {
+ // release SolarMutex, so the callback handler, which may run in another thread,
+ // can acquire it in 'lo_setDocumentPassword'
+ SolarMutexReleaser aReleaser;
+ m_pLOKit->mpCallback(bIsRequestPasswordToModify ? LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY
+ : LOK_CALLBACK_DOCUMENT_PASSWORD,
+ sUrl.getStr(),
+ m_pLOKit->mpCallbackData);
+
+ // block until SetPassword is called
+ m_havePassword.wait();
+ m_havePassword.reset();
+ }
+
+ for (auto const & cont : rContinuations)
+ {
+ if (m_usePassword)
+ {
+ if (bIsRequestPasswordToModify)
+ {
+ uno::Reference<task::XInteractionPassword2> const xIPW2(cont, uno::UNO_QUERY);
+ xIPW2->setPasswordToModify(m_Password);
+ xIPW2->select();
+ }
+ else
+ {
+ uno::Reference<task::XInteractionPassword> const xIPW(cont, uno::UNO_QUERY);
+ if (xIPW.is())
+ {
+ xIPW->setPassword(m_Password);
+ xIPW->select();
+ }
+ }
+ }
+ else
+ {
+ if (bIsRequestPasswordToModify)
+ {
+ uno::Reference<task::XInteractionPassword2> const xIPW2(cont, uno::UNO_QUERY);
+ xIPW2->setRecommendReadOnly(true);
+ xIPW2->select();
+ }
+ else
+ {
+ uno::Reference<task::XInteractionAbort> const xAbort(cont, uno::UNO_QUERY);
+ if (xAbort.is())
+ {
+ xAbort->select();
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool LOKInteractionHandler::handleMacroConfirmationRequest(const uno::Reference<task::XInteractionRequest>& xRequest)
+{
+ uno::Any const request(xRequest->getRequest());
+
+ task::DocumentMacroConfirmationRequest aConfirmRequest;
+ if (request >>= aConfirmRequest)
+ {
+ auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr));
+
+ if (xInteraction.is())
+ xInteraction->handleInteractionRequest(xRequest);
+
+ return true;
+ }
+ return false;
+}
+
+bool LOKInteractionHandler::handlePackageReparationRequest(const uno::Reference<task::XInteractionRequest>& xRequest)
+{
+ uno::Any const request(xRequest->getRequest());
+
+ document::BrokenPackageRequest aBrokenPackageRequest;
+ if (request >>= aBrokenPackageRequest)
+ {
+ auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr));
+
+ if (xInteraction.is())
+ xInteraction->handleInteractionRequest(xRequest);
+
+ return true;
+ }
+ return false;
+}
+
+bool LOKInteractionHandler::handleLoadReadOnlyRequest(const uno::Reference<task::XInteractionRequest>& xRequest)
+{
+ uno::Any const request(xRequest->getRequest());
+
+ OUString aFileName;
+ beans::NamedValue aLoadReadOnlyRequest;
+ if ((request >>= aLoadReadOnlyRequest) &&
+ aLoadReadOnlyRequest.Name == "LoadReadOnlyRequest" &&
+ (aLoadReadOnlyRequest.Value >>= aFileName))
+ {
+ auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr));
+
+ if (xInteraction.is())
+ xInteraction->handleInteractionRequest(xRequest);
+
+ return true;
+ }
+ return false;
+}
+
+bool LOKInteractionHandler::handleFilterOptionsRequest(const uno::Reference<task::XInteractionRequest>& xRequest)
+{
+ document::FilterOptionsRequest aFilterOptionsRequest;
+ uno::Any const request(xRequest->getRequest());
+ if (request >>= aFilterOptionsRequest)
+ {
+ uno::Reference< task::XInteractionHandler2 > xInteraction(
+ task::InteractionHandler::createWithParent(
+ ::comphelper::getProcessComponentContext(), nullptr));
+
+ if (xInteraction.is())
+ xInteraction->handleInteractionRequest(xRequest);
+
+ return true;
+ }
+ return false;
+}
+
+sal_Bool SAL_CALL LOKInteractionHandler::handleInteractionRequest(
+ const uno::Reference<task::XInteractionRequest>& xRequest)
+{
+ uno::Sequence<uno::Reference<task::XInteractionContinuation>> const &rContinuations = xRequest->getContinuations();
+ uno::Any const request(xRequest->getRequest());
+
+ if (handleIOException(rContinuations, request))
+ return true;
+
+ if (handleNetworkException(rContinuations, request))
+ return true;
+
+ if (handlePasswordRequest(rContinuations, request))
+ return true;
+
+ if (handleFilterOptionsRequest(xRequest))
+ return true;
+
+ if (handleMacroConfirmationRequest(xRequest))
+ return true;
+
+ if (handlePackageReparationRequest(xRequest))
+ return true;
+
+ if (handleLoadReadOnlyRequest(xRequest))
+ return true;
+
+ // TODO: perform more interactions 'for real' like the above
+ selectApproved(rContinuations);
+
+ return true;
+}
+
+void LOKInteractionHandler::SetPassword(char const*const pPassword)
+{
+ if (pPassword)
+ {
+ m_Password = OUString(pPassword, strlen(pPassword), RTL_TEXTENCODING_UTF8);
+ m_usePassword = true;
+ }
+ else
+ {
+ m_usePassword = false;
+ }
+ m_havePassword.set();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/lib/lokinteractionhandler.hxx b/desktop/source/lib/lokinteractionhandler.hxx
new file mode 100644
index 0000000000..c3641db076
--- /dev/null
+++ b/desktop/source/lib/lokinteractionhandler.hxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osl/conditn.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/errcode.hxx>
+
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/task/InteractionClassification.hpp>
+#include <com/sun/star/task/XInteractionHandler2.hpp>
+
+namespace desktop {
+ struct LibLibreOffice_Impl;
+ struct LibLODocument_Impl;
+}
+
+/** InteractionHandler is an interface that provides the user with various dialogs / error messages.
+
+We need an own implementation for the LibreOfficeKit so that we can route the
+information easily via callbacks.
+
+TODO: the callbacks are not implemented yet, we just approve any interaction
+that we get.
+*/
+class LOKInteractionHandler: public cppu::WeakImplHelper<com::sun::star::lang::XServiceInfo,
+ com::sun::star::lang::XInitialization,
+ com::sun::star::task::XInteractionHandler2>
+{
+private:
+ desktop::LibLibreOffice_Impl * m_pLOKit;
+ desktop::LibLODocument_Impl * m_pLOKDocument;
+
+ /// Command for which we use this interaction handler (like "load", "save", "saveas", ...)
+ OString m_command;
+
+ OUString m_Password;
+ bool m_usePassword;
+ osl::Condition m_havePassword;
+
+ LOKInteractionHandler(const LOKInteractionHandler&) = delete;
+ LOKInteractionHandler& operator=(const LOKInteractionHandler&) = delete;
+
+ /** Call the LOK_CALLBACK_ERROR on the LOK document (if available) or LOK lib.
+
+ The error itself is a JSON message, like:
+ {
+ "classification": "error" | "warning" | "info"
+ "kind": "network" etc.
+ "code": 403 | 404 | ...
+ "message": freeform description
+ }
+ */
+ void postError(css::task::InteractionClassification classif, const char* kind, ErrCode code, const OUString &message);
+
+ bool handleIOException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest);
+ bool handleNetworkException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest);
+ bool handlePasswordRequest(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest);
+ static bool handleMacroConfirmationRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest);
+
+ static bool handleFilterOptionsRequest(const ::com::sun::star::uno::Reference<::com::sun::star::task::XInteractionRequest>& Request);
+ static bool handlePackageReparationRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest);
+
+ static bool handleLoadReadOnlyRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest);
+
+public:
+ void SetPassword(char const* pPassword);
+
+ explicit LOKInteractionHandler(
+ OString command,
+ desktop::LibLibreOffice_Impl *,
+ desktop::LibLODocument_Impl *pLOKDocumt = nullptr);
+
+ virtual ~LOKInteractionHandler() override;
+
+ virtual OUString SAL_CALL getImplementationName() override;
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & rServiceName) override;
+
+ virtual com::sun::star::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ virtual void SAL_CALL initialize(com::sun::star::uno::Sequence<com::sun::star::uno::Any > const & rArguments) override;
+
+ virtual void SAL_CALL handle(com::sun::star::uno::Reference<com::sun::star::task::XInteractionRequest> const & rRequest) override;
+
+ virtual sal_Bool SAL_CALL handleInteractionRequest(const ::com::sun::star::uno::Reference<::com::sun::star::task::XInteractionRequest>& Request) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/migration.cxx b/desktop/source/migration/migration.cxx
new file mode 100644
index 0000000000..b0728de61a
--- /dev/null
+++ b/desktop/source/migration/migration.cxx
@@ -0,0 +1,1217 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <set>
+
+#include <migration.hxx>
+#include "migration_impl.hxx"
+
+#include <sal/log.hxx>
+#include <unotools/textsearch.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <unotools/bootstrap.hxx>
+#include <rtl/uri.hxx>
+#include <i18nlangtag/lang.h>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <officecfg/Office/UI.hxx>
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <com/sun/star/configuration/Update.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/util/XRefreshable.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/FileSystemStorageFactory.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
+#include <com/sun/star/ui/UIConfigurationManager.hpp>
+#include <com/sun/star/ui/XUIConfigurationPersistence.hpp>
+#include <vcl/commandinfoprovider.hxx>
+
+using namespace osl;
+using namespace com::sun::star::task;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::util;
+using namespace com::sun::star::container;
+using com::sun::star::uno::Exception;
+using namespace com::sun::star;
+
+
+namespace desktop
+{
+
+constexpr OUString ITEM_DESCRIPTOR_COMMANDURL = u"CommandURL"_ustr;
+constexpr OUString ITEM_DESCRIPTOR_CONTAINER = u"ItemDescriptorContainer"_ustr;
+constexpr OUString ITEM_DESCRIPTOR_LABEL = u"Label"_ustr;
+
+static OUString mapModuleShortNameToIdentifier(std::u16string_view sShortName)
+{
+ OUString sIdentifier;
+
+ if ( sShortName == u"StartModule" )
+ sIdentifier = "com.sun.star.frame.StartModule";
+
+ else if ( sShortName == u"swriter" )
+ sIdentifier = "com.sun.star.text.TextDocument";
+
+ else if ( sShortName == u"scalc" )
+ sIdentifier = "com.sun.star.sheet.SpreadsheetDocument";
+
+ else if ( sShortName == u"sdraw" )
+ sIdentifier = "com.sun.star.drawing.DrawingDocument";
+
+ else if ( sShortName == u"simpress" )
+ sIdentifier = "com.sun.star.presentation.PresentationDocument";
+
+ else if ( sShortName == u"smath" )
+ sIdentifier = "com.sun.star.formula.FormulaProperties";
+
+ else if ( sShortName == u"schart" )
+ sIdentifier = "com.sun.star.chart2.ChartDocument";
+
+ else if ( sShortName == u"BasicIDE" )
+ sIdentifier = "com.sun.star.script.BasicIDE";
+
+ else if ( sShortName == u"dbapp" )
+ sIdentifier = "com.sun.star.sdb.OfficeDatabaseDocument";
+
+ else if ( sShortName == u"sglobal" )
+ sIdentifier = "com.sun.star.text.GlobalDocument";
+
+ else if ( sShortName == u"sweb" )
+ sIdentifier = "com.sun.star.text.WebDocument";
+
+ else if ( sShortName == u"swxform" )
+ sIdentifier = "com.sun.star.xforms.XMLFormDocument";
+
+ else if ( sShortName == u"sbibliography" )
+ sIdentifier = "com.sun.star.frame.Bibliography";
+
+ return sIdentifier;
+}
+
+bool MigrationImpl::alreadyMigrated()
+{
+ OUString aStr = m_aInfo.userdata + "/MIGRATED4";
+ File aFile(aStr);
+ // create migration stamp, and/or check its existence
+ bool bRet = aFile.open (osl_File_OpenFlag_Write | osl_File_OpenFlag_Create | osl_File_OpenFlag_NoLock) == FileBase::E_EXIST;
+ SAL_INFO( "desktop.migration", "File '" << aStr << "' exists? " << bRet );
+ return bRet;
+}
+
+bool MigrationImpl::initializeMigration()
+{
+ bool bRet = false;
+
+ if (!checkMigrationCompleted()) {
+ readAvailableMigrations(m_vMigrationsAvailable);
+ sal_Int32 nIndex = findPreferredMigrationProcess(m_vMigrationsAvailable);
+ // m_aInfo is now set to the preferred migration source
+ if ( nIndex >= 0 ) {
+ if (alreadyMigrated())
+ return false;
+ m_vrMigrations = readMigrationSteps(m_vMigrationsAvailable[nIndex].name);
+ }
+
+ bRet = !m_aInfo.userdata.isEmpty();
+ }
+
+ SAL_INFO( "desktop.migration", "Migration " << ( bRet ? "needed" : "not required" ) );
+
+ return bRet;
+}
+
+void Migration::migrateSettingsIfNecessary()
+{
+ MigrationImpl aImpl;
+
+ if (! aImpl.initializeMigration() )
+ return;
+
+ bool bResult = false;
+ try {
+ bResult = aImpl.doMigration();
+ } catch (const Exception&) {
+ TOOLS_WARN_EXCEPTION( "desktop", "doMigration()");
+ }
+ OSL_ENSURE(bResult, "Migration has not been successful");
+}
+
+MigrationImpl::MigrationImpl()
+{
+}
+
+MigrationImpl::~MigrationImpl()
+{
+}
+
+// The main entry point for migrating settings
+bool MigrationImpl::doMigration()
+{
+ // compile file list for migration
+ m_vrFileList = compileFileList();
+
+ bool result = false;
+ try {
+ NewVersionUIInfo aNewVersionUIInfo;
+ std::vector< MigrationModuleInfo > vModulesInfo = detectUIChangesForAllModules();
+ aNewVersionUIInfo.init(vModulesInfo);
+
+ copyFiles();
+
+ static constexpr OUString sMenubarResourceURL(u"private:resource/menubar/menubar"_ustr);
+ static constexpr OUStringLiteral sToolbarResourcePre(u"private:resource/toolbar/");
+ for (MigrationModuleInfo & i : vModulesInfo) {
+ OUString sModuleIdentifier = mapModuleShortNameToIdentifier(i.sModuleShortName);
+ if (sModuleIdentifier.isEmpty())
+ continue;
+
+
+ OUString aOldCfgDataPath = m_aInfo.userdata + "/user/config/soffice.cfg/modules/" + i.sModuleShortName;
+ uno::Sequence< uno::Any > lArgs {uno::Any(aOldCfgDataPath), uno::Any(embed::ElementModes::READ)};
+
+ uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext());
+ uno::Reference< lang::XSingleServiceFactory > xStorageFactory(embed::FileSystemStorageFactory::create(xContext));
+ uno::Reference< embed::XStorage > xModules(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY);
+ uno::Reference< ui::XUIConfigurationManager2 > xOldCfgManager = ui::UIConfigurationManager::create(xContext);
+
+ if ( xModules.is() ) {
+ xOldCfgManager->setStorage( xModules );
+ xOldCfgManager->reload();
+ }
+
+ uno::Reference< ui::XUIConfigurationManager > xCfgManager = aNewVersionUIInfo.getConfigManager(i.sModuleShortName);
+
+ if (i.bHasMenubar) {
+ uno::Reference< container::XIndexContainer > xOldVersionMenuSettings(xOldCfgManager->getSettings(sMenubarResourceURL, true), uno::UNO_QUERY);
+ uno::Reference< container::XIndexContainer > xNewVersionMenuSettings = aNewVersionUIInfo.getNewMenubarSettings(i.sModuleShortName);
+ compareOldAndNewConfig(OUString(), xOldVersionMenuSettings, xNewVersionMenuSettings, sMenubarResourceURL);
+ mergeOldToNewVersion(xCfgManager, xNewVersionMenuSettings, sModuleIdentifier, sMenubarResourceURL);
+ }
+
+ sal_Int32 nToolbars = i.m_vToolbars.size();
+ if (nToolbars >0) {
+ for (sal_Int32 j=0; j<nToolbars; ++j) {
+ OUString sToolbarName = i.m_vToolbars[j];
+ OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName;
+
+ uno::Reference< container::XIndexContainer > xOldVersionToolbarSettings(xOldCfgManager->getSettings(sToolbarResourceURL, true), uno::UNO_QUERY);
+ uno::Reference< container::XIndexContainer > xNewVersionToolbarSettings = aNewVersionUIInfo.getNewToolbarSettings(i.sModuleShortName, sToolbarName);
+ compareOldAndNewConfig(OUString(), xOldVersionToolbarSettings, xNewVersionToolbarSettings, sToolbarResourceURL);
+ mergeOldToNewVersion(xCfgManager, xNewVersionToolbarSettings, sModuleIdentifier, sToolbarResourceURL);
+ }
+ }
+
+ m_aOldVersionItemsHashMap.clear();
+ }
+
+ // execute the migration items from Setup.xcu
+ copyConfig();
+
+ // execute custom migration services from Setup.xcu
+ // and refresh the cache
+ runServices();
+ uno::Reference< XRefreshable >(
+ configuration::theDefaultProvider::get(comphelper::getProcessComponentContext()),
+ uno::UNO_QUERY_THROW)->refresh();
+
+ result = true;
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.migration",
+ "ignored Exception while migrating from version \"" << m_aInfo.productname
+ << "\" data \"" << m_aInfo.userdata << "\"");
+ }
+
+ // prevent running the migration multiple times
+ setMigrationCompleted();
+ return result;
+}
+
+void MigrationImpl::setMigrationCompleted()
+{
+ try {
+ uno::Reference< XPropertySet > aPropertySet(getConfigAccess("org.openoffice.Setup/Office", true), uno::UNO_QUERY_THROW);
+ aPropertySet->setPropertyValue("MigrationCompleted", uno::Any(true));
+ uno::Reference< XChangesBatch >(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges();
+ } catch (...) {
+ // fail silently
+ }
+}
+
+bool MigrationImpl::checkMigrationCompleted()
+{
+ bool bMigrationCompleted = false;
+ try {
+ uno::Reference< XPropertySet > aPropertySet(
+ getConfigAccess("org.openoffice.Setup/Office"), uno::UNO_QUERY_THROW);
+ aPropertySet->getPropertyValue("MigrationCompleted") >>= bMigrationCompleted;
+
+ if( !bMigrationCompleted && getenv("SAL_DISABLE_USERMIGRATION" ) ) {
+ // migration prevented - fake its success
+ setMigrationCompleted();
+ bMigrationCompleted = true;
+ }
+ } catch (const Exception&) {
+ // just return false...
+ }
+ SAL_INFO( "desktop.migration", "Migration " << ( bMigrationCompleted ? "already completed" : "not done" ) );
+
+ return bMigrationCompleted;
+}
+
+static void insertSorted(migrations_available& rAvailableMigrations, supported_migration const & aSupportedMigration)
+{
+ migrations_available::iterator pIter = std::find_if(rAvailableMigrations.begin(), rAvailableMigrations.end(),
+ [&aSupportedMigration](const supported_migration& rMigration) { return rMigration.nPriority < aSupportedMigration.nPriority; });
+ if (pIter != rAvailableMigrations.end())
+ rAvailableMigrations.insert(pIter, aSupportedMigration );
+ else
+ rAvailableMigrations.push_back( aSupportedMigration );
+}
+
+void MigrationImpl::readAvailableMigrations(migrations_available& rAvailableMigrations)
+{
+ // get supported version names
+ uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_SET_THROW);
+ const uno::Sequence< OUString > seqSupportedVersions = aMigrationAccess->getElementNames();
+
+ static constexpr OUStringLiteral aVersionIdentifiers( u"VersionIdentifiers" );
+ static constexpr OUStringLiteral aPriorityIdentifier( u"Priority" );
+
+ for (OUString const & supportedVersion :seqSupportedVersions) {
+ sal_Int32 nPriority( 0 );
+ uno::Sequence< OUString > seqVersions;
+ uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(supportedVersion), uno::UNO_QUERY_THROW );
+ xMigrationData->getByName( aVersionIdentifiers ) >>= seqVersions;
+ xMigrationData->getByName( aPriorityIdentifier ) >>= nPriority;
+
+ supported_migration aSupportedMigration;
+ aSupportedMigration.name = supportedVersion;
+ aSupportedMigration.nPriority = nPriority;
+ for (OUString const & s : std::as_const(seqVersions))
+ aSupportedMigration.supported_versions.push_back(s.trim());
+ insertSorted( rAvailableMigrations, aSupportedMigration );
+ SAL_INFO( "desktop.migration", " available migration '" << aSupportedMigration.name << "'" );
+ }
+}
+
+migrations_vr MigrationImpl::readMigrationSteps(const OUString& rMigrationName)
+{
+ // get migration access
+ uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_SET_THROW);
+ uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(rMigrationName), uno::UNO_QUERY_THROW );
+
+ // get migration description from org.openoffice.Setup/Migration
+ // and build vector of migration steps
+ uno::Reference< XNameAccess > theNameAccess(xMigrationData->getByName("MigrationSteps"), uno::UNO_QUERY_THROW);
+ uno::Reference< XNameAccess > tmpAccess;
+ uno::Sequence< OUString > tmpSeq;
+ migrations_vr vrMigrations(new migrations_v);
+ const css::uno::Sequence<OUString> aMigrationSteps = theNameAccess->getElementNames();
+ for (const OUString& rMigrationStep : aMigrationSteps) {
+ // get current migration step
+ theNameAccess->getByName(rMigrationStep) >>= tmpAccess;
+ migration_step tmpStep;
+
+ // read included files from current step description
+ if (tmpAccess->getByName("IncludedFiles") >>= tmpSeq) {
+ for (const OUString& rSeqEntry : std::as_const(tmpSeq))
+ tmpStep.includeFiles.push_back(rSeqEntry);
+ }
+
+ // excluded files...
+ if (tmpAccess->getByName("ExcludedFiles") >>= tmpSeq) {
+ for (const OUString& rSeqEntry : std::as_const(tmpSeq))
+ tmpStep.excludeFiles.push_back(rSeqEntry);
+ }
+
+ // included nodes...
+ if (tmpAccess->getByName("IncludedNodes") >>= tmpSeq) {
+ for (const OUString& rSeqEntry : std::as_const(tmpSeq))
+ tmpStep.includeConfig.push_back(rSeqEntry);
+ }
+
+ // excluded nodes...
+ if (tmpAccess->getByName("ExcludedNodes") >>= tmpSeq) {
+ for (const OUString& rSeqEntry : std::as_const(tmpSeq))
+ tmpStep.excludeConfig.push_back(rSeqEntry);
+ }
+
+ // excluded extensions...
+ if (tmpAccess->getByName("ExcludedExtensions") >>= tmpSeq) {
+ for (const OUString& rSeqEntry : std::as_const(tmpSeq))
+ tmpStep.excludeExtensions.push_back(rSeqEntry);
+ }
+
+ // generic service
+ tmpAccess->getByName("MigrationService") >>= tmpStep.service;
+
+ vrMigrations->push_back(tmpStep);
+ }
+ return vrMigrations;
+}
+
+static FileBase::RC _checkAndCreateDirectory(INetURLObject const & dirURL)
+{
+ FileBase::RC result = Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri));
+ if (result == FileBase::E_NOENT) {
+ INetURLObject baseURL(dirURL);
+ baseURL.removeSegment();
+ _checkAndCreateDirectory(baseURL);
+ return Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri));
+ } else
+ return result;
+}
+
+#if defined UNX && ! defined MACOSX
+
+const char XDG_CONFIG_PART[] = "/.config/";
+
+OUString MigrationImpl::preXDGConfigDir(const OUString& rConfigDir)
+{
+ OUString aPreXDGConfigPath;
+ const char* pXDGCfgHome = getenv("XDG_CONFIG_HOME");
+
+ // cater for XDG_CONFIG_HOME change
+ // If XDG_CONFIG_HOME is set then we;
+ // assume the user knows what they are doing ( room for improvement here, we could
+ // of course search the default config dir etc. also - but this is more complex,
+ // we would need to weigh results from the current config dir against matches in
+ // the 'old' config dir etc. ) - currently we just use the returned config dir.
+ // If XDG_CONFIG_HOME is NOT set;
+ // assume then we should now using the default $HOME/.config config location for
+ // our user profiles, however *all* previous libreoffice and openoffice.org
+ // configurations will be in the 'old' config directory and that's where we need
+ // to search - we convert the returned config dir to the 'old' dir
+ if ( !pXDGCfgHome && rConfigDir.endsWith( XDG_CONFIG_PART ) )
+ // remove trailing '.config/' but leave the terminating '/'
+ aPreXDGConfigPath = rConfigDir.copy( 0, rConfigDir.getLength() - sizeof( XDG_CONFIG_PART ) + 2 );
+ else
+ aPreXDGConfigPath = rConfigDir;
+
+ // the application-specific config dir is no longer prefixed by '.' because it is hidden under ".config"
+ // we have to add the '.' for the pre-XDG directory names
+ aPreXDGConfigPath += ".";
+
+ return aPreXDGConfigPath;
+}
+#endif
+
+void MigrationImpl::setInstallInfoIfExist(
+ install_info& aInfo,
+ std::u16string_view rConfigDir,
+ const OUString& rVersion)
+{
+ OUString url(INetURLObject(rConfigDir).GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ osl::DirectoryItem item;
+ osl::FileStatus stat(osl_FileStatus_Mask_Type);
+
+ if (osl::DirectoryItem::get(url, item) == osl::FileBase::E_None
+ && item.getFileStatus(stat) == osl::FileBase::E_None
+ && stat.getFileType() == osl::FileStatus::Directory) {
+ aInfo.userdata = url;
+ aInfo.productname = rVersion;
+ }
+}
+
+install_info MigrationImpl::findInstallation(const strings_v& rVersions)
+{
+
+ OUString aTopConfigDir;
+ osl::Security().getConfigDir( aTopConfigDir );
+ if ( !aTopConfigDir.isEmpty() && aTopConfigDir[ aTopConfigDir.getLength()-1 ] != '/' )
+ aTopConfigDir += "/";
+
+#if defined UNX && ! defined MACOSX
+ OUString aPreXDGTopConfigDir = preXDGConfigDir(aTopConfigDir);
+#endif
+
+ install_info aInfo;
+ for (auto const& elem : rVersions)
+ {
+ OUString aVersion, aProfileName;
+ sal_Int32 nSeparatorIndex = elem.indexOf('=');
+ if ( nSeparatorIndex != -1 ) {
+ aVersion = elem.copy( 0, nSeparatorIndex );
+ aProfileName = elem.copy( nSeparatorIndex+1 );
+ }
+
+ if ( !aVersion.isEmpty() && !aProfileName.isEmpty() &&
+ ( aInfo.userdata.isEmpty() ||
+ aProfileName.equalsIgnoreAsciiCase(
+ utl::ConfigManager::getProductName() ) ) ) {
+ setInstallInfoIfExist(aInfo, Concat2View(aTopConfigDir + aProfileName), aVersion);
+#if defined UNX && ! defined MACOSX
+ //try preXDG path if the new one does not exist
+ if ( aInfo.userdata.isEmpty())
+ setInstallInfoIfExist(aInfo, Concat2View(aPreXDGTopConfigDir + aProfileName), aVersion);
+#endif
+ }
+ }
+
+ return aInfo;
+}
+
+sal_Int32 MigrationImpl::findPreferredMigrationProcess(const migrations_available& rAvailableMigrations)
+{
+ sal_Int32 nIndex( -1 );
+ sal_Int32 i( 0 );
+
+ for (auto const& availableMigration : rAvailableMigrations)
+ {
+ install_info aInstallInfo = findInstallation(availableMigration.supported_versions);
+ if (!aInstallInfo.productname.isEmpty() ) {
+ m_aInfo = aInstallInfo;
+ nIndex = i;
+ break;
+ }
+ ++i;
+ }
+
+ SAL_INFO( "desktop.migration", " preferred migration is from product '" << m_aInfo.productname << "'");
+ SAL_INFO( "desktop.migration", " and settings directory '" << m_aInfo.userdata << "'");
+
+ return nIndex;
+}
+
+strings_vr MigrationImpl::applyPatterns(const strings_v& vSet, const strings_v& vPatterns)
+{
+ using namespace utl;
+ strings_vr vrResult(new strings_v);
+ for (auto const& pattern : vPatterns)
+ {
+ // find matches for this pattern in input set
+ // and copy them to the result
+ SearchParam param(pattern, SearchParam::SearchType::Regexp);
+ TextSearch ts(param, LANGUAGE_DONTKNOW);
+ sal_Int32 start = 0;
+ sal_Int32 end = 0;
+ for (auto const& elem : vSet)
+ {
+ end = elem.getLength();
+ if (ts.SearchForward(elem, &start, &end))
+ vrResult->push_back(elem);
+ }
+ }
+ return vrResult;
+}
+
+strings_vr MigrationImpl::getAllFiles(const OUString& baseURL) const
+{
+ strings_vr vrResult(new strings_v);
+
+ // get sub dirs
+ Directory dir(baseURL);
+ if (dir.open() == FileBase::E_None) {
+ strings_v vSubDirs;
+ strings_vr vrSubResult;
+
+ // work through directory contents...
+ DirectoryItem item;
+ FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL);
+ while (dir.getNextItem(item) == FileBase::E_None) {
+ if (item.getFileStatus(fs) == FileBase::E_None) {
+ if (fs.getFileType() == FileStatus::Directory)
+ vSubDirs.push_back(fs.getFileURL());
+ else
+ vrResult->push_back(fs.getFileURL());
+ }
+ }
+
+ // recurse subfolders
+ for (auto const& subDir : vSubDirs)
+ {
+ vrSubResult = getAllFiles(subDir);
+ vrResult->insert(vrResult->end(), vrSubResult->begin(), vrSubResult->end());
+ }
+ }
+ return vrResult;
+}
+
+namespace
+{
+
+// removes elements of vector 2 in vector 1
+strings_v subtract(strings_v && a, strings_v && b)
+{
+ std::sort(a.begin(), a.end());
+ strings_v::iterator ae(std::unique(a.begin(), a.end()));
+ std::sort(b.begin(), b.end());
+ strings_v::iterator be(std::unique(b.begin(), b.end()));
+ strings_v c;
+ std::set_difference(a.begin(), ae, b.begin(), be, std::back_inserter(c));
+ return c;
+}
+
+}
+
+strings_vr MigrationImpl::compileFileList()
+{
+
+ strings_vr vrResult(new strings_v);
+
+ // get a list of all files:
+ strings_vr vrFiles = getAllFiles(m_aInfo.userdata);
+
+ // get a file list result for each migration step
+ for (auto const& rMigration : *m_vrMigrations)
+ {
+ strings_vr vrInclude = applyPatterns(*vrFiles, rMigration.includeFiles);
+ strings_vr vrExclude = applyPatterns(*vrFiles, rMigration.excludeFiles);
+ strings_v sub(subtract(std::move(*vrInclude), std::move(*vrExclude)));
+ vrResult->insert(vrResult->end(), sub.begin(), sub.end());
+ }
+ return vrResult;
+}
+
+namespace
+{
+
+struct componentParts {
+ std::set< OUString > includedPaths;
+ std::set< OUString > excludedPaths;
+};
+
+typedef std::map< OUString, componentParts > Components;
+
+bool getComponent(OUString const & path, OUString * component)
+{
+ OSL_ASSERT(component != nullptr);
+ if (path.isEmpty() || path[0] != '/') {
+ SAL_INFO( "desktop.migration", "configuration migration in/exclude path " << path << " ignored (does not start with slash)" );
+ return false;
+ }
+ sal_Int32 i = path.indexOf('/', 1);
+ *component = i < 0 ? path.copy(1) : path.copy(1, i - 1);
+ return true;
+}
+
+void renameMigratedSetElementTo(
+ css::uno::Reference<css::container::XNameContainer> const & set, OUString const & currentName,
+ OUString const & migratedName)
+{
+ // To avoid unexpected data loss, the code is careful to only rename from currentName to
+ // migratedName in the expected case where the currentName element exists and the migratedName
+ // element doesn't exist:
+ bool const hasCurrent = set->hasByName(currentName);
+ bool const hasMigrated = set->hasByName(migratedName);
+ if (hasCurrent && !hasMigrated) {
+ auto const elem = set->getByName(currentName);
+ set->removeByName(currentName);
+ set->insertByName(migratedName, elem);
+ } else {
+ SAL_INFO_IF(!hasCurrent, "desktop.migration", "unexpectedly missing " << currentName);
+ SAL_INFO_IF(hasMigrated, "desktop.migration", "unexpectedly present " << migratedName);
+ }
+}
+
+void renameMigratedSetElementBack(
+ css::uno::Reference<css::container::XNameContainer> const & set, OUString const & currentName,
+ OUString const & migratedName)
+{
+ // To avoid unexpected data loss, the code is careful to ensure that in the end a currentName
+ // element exists, creating it from a template if the migratedName element had unexpectedly gone
+ // missing:
+ bool const hasMigrated = set->hasByName(migratedName);
+ css::uno::Any elem;
+ if (hasMigrated) {
+ elem = set->getByName(migratedName);
+ set->removeByName(migratedName);
+ } else {
+ SAL_INFO("desktop.migration", "unexpected loss of " << migratedName);
+ elem <<= css::uno::Reference<css::lang::XSingleServiceFactory>(
+ set, css::uno::UNO_QUERY_THROW)->createInstance();
+ }
+ if (set->hasByName(currentName)) {
+ SAL_INFO("desktop.migration", "unexpected reappearance of " << currentName);
+ if (hasMigrated) {
+ SAL_INFO(
+ "desktop.migration",
+ "reappeared " << currentName << " overwritten with " << migratedName);
+ set->replaceByName(currentName, elem);
+ }
+ } else {
+ set->insertByName(currentName, elem);
+ }
+}
+
+}
+
+void MigrationImpl::copyConfig()
+{
+ Components comps;
+ for (auto const& rMigrationStep : *m_vrMigrations) {
+ for (const OUString& rIncludePath : rMigrationStep.includeConfig) {
+ OUString comp;
+ if (getComponent(rIncludePath, &comp)) {
+ comps[comp].includedPaths.insert(rIncludePath);
+ }
+ }
+ for (const OUString& rExcludePath : rMigrationStep.excludeConfig) {
+ OUString comp;
+ if (getComponent(rExcludePath, &comp)) {
+ comps[comp].excludedPaths.insert(rExcludePath);
+ }
+ }
+ }
+
+ // check if the shared registrymodifications.xcu file exists
+ bool bRegistryModificationsXcuExists = false;
+ OUString regFilePath = m_aInfo.userdata + "/user/registrymodifications.xcu";
+ File regFile(regFilePath);
+ ::osl::FileBase::RC nError = regFile.open(osl_File_OpenFlag_Read);
+ if ( nError == ::osl::FileBase::E_None ) {
+ bRegistryModificationsXcuExists = true;
+ regFile.close();
+ }
+
+ // If the to-be-migrated data contains modifications of
+ // /org.openoffice.Office.UI/ColorScheme/ColorSchemes set elements named after the migrated
+ // product name, those modifications must instead be made to the corresponding set elements
+ // named after the current product name. However, if the current configuration data does not
+ // contain those old-named set elements at all, their modification data would silently be
+ // ignored by css.configuration.XUpdate::insertModificationXcuFile. So temporarily rename any
+ // new-named set elements to their old-named counterparts here, and rename them back again down
+ // below after importing the migrated data:
+ OUString sProductName = utl::ConfigManager::getProductName();
+ OUString sProductNameDark = sProductName + " Dark";
+ OUString sMigratedProductName = m_aInfo.productname;
+ // remove version number from the end of product name if there’s one
+ if (isdigit(sMigratedProductName[sMigratedProductName.getLength() - 1]))
+ sMigratedProductName = (sMigratedProductName.copy(0, m_aInfo.productname.getLength() - 1)).trim();
+ OUString sMigratedProductNameDark = sMigratedProductName + " Dark";
+ auto const tempRename = sMigratedProductName != sProductName;
+ if (tempRename) {
+ auto const batch = comphelper::ConfigurationChanges::create();
+ auto const schemes = officecfg::Office::UI::ColorScheme::ColorSchemes::get(batch);
+ renameMigratedSetElementTo(schemes, sProductName, sMigratedProductName);
+ renameMigratedSetElementTo(schemes, sProductNameDark, sMigratedProductNameDark);
+ batch->commit();
+ }
+
+ for (auto const& comp : comps)
+ {
+ if (!comp.second.includedPaths.empty()) {
+ if (!bRegistryModificationsXcuExists) {
+ // shared registrymodifications.xcu does not exists
+ // the configuration is split in many registry files
+ // determine the file names from the first element in included paths
+ OUStringBuffer buf(m_aInfo.userdata
+ + "/user/registry/data");
+ sal_Int32 n = 0;
+ do {
+ OUString seg(comp.first.getToken(0, '.', n));
+ OUString enc(
+ rtl::Uri::encode(
+ seg, rtl_UriCharClassPchar, rtl_UriEncodeStrict,
+ RTL_TEXTENCODING_UTF8));
+ if (enc.isEmpty() && !seg.isEmpty()) {
+ SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (cannot be encoded as file path)" );
+ goto next;
+ }
+ buf.append("/" + enc);
+ } while (n >= 0);
+ buf.append(".xcu");
+ regFilePath = buf.makeStringAndClear();
+ }
+ configuration::Update::get(
+ comphelper::getProcessComponentContext())->
+ insertModificationXcuFile(
+ regFilePath,
+ comphelper::containerToSequence(comp.second.includedPaths),
+ comphelper::containerToSequence(comp.second.excludedPaths));
+
+ } else {
+ SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (only excludes, no includes)" );
+ }
+next:
+ ;
+ }
+ if (tempRename) {
+ auto const batch = comphelper::ConfigurationChanges::create();
+ auto const schemes = officecfg::Office::UI::ColorScheme::ColorSchemes::get(batch);
+ renameMigratedSetElementBack(schemes, sProductName, sMigratedProductName);
+ renameMigratedSetElementBack(schemes, sProductNameDark, sMigratedProductNameDark);
+ batch->commit();
+ }
+ // checking the migrated (product name related) color scheme name, and replace it to the current version scheme name
+ try
+ {
+ OUString sMigratedColorScheme;
+ uno::Reference<XPropertySet> aPropertySet(
+ getConfigAccess("org.openoffice.Office.UI/ColorScheme", true), uno::UNO_QUERY_THROW);
+ if (aPropertySet->getPropertyValue("CurrentColorScheme") >>= sMigratedColorScheme)
+ {
+ if (sMigratedColorScheme.equals(sMigratedProductName))
+ {
+ aPropertySet->setPropertyValue("CurrentColorScheme",
+ uno::Any(sProductName));
+ uno::Reference<XChangesBatch>(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges();
+ }
+ else if (sMigratedColorScheme.equals(sMigratedProductNameDark))
+ {
+ aPropertySet->setPropertyValue("CurrentColorScheme",
+ uno::Any(sProductNameDark));
+ uno::Reference<XChangesBatch>(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges();
+ }
+ }
+ } catch (const Exception&) {
+ // fail silently...
+ }
+}
+
+uno::Reference< XNameAccess > MigrationImpl::getConfigAccess(const char* pPath, bool bUpdate)
+{
+ uno::Reference< XNameAccess > xNameAccess;
+ try {
+ OUString sAccessSrvc;
+ if (bUpdate)
+ sAccessSrvc = "com.sun.star.configuration.ConfigurationUpdateAccess";
+ else
+ sAccessSrvc = "com.sun.star.configuration.ConfigurationAccess";
+
+ OUString sConfigURL = OUString::createFromAscii(pPath);
+
+ uno::Reference< XMultiServiceFactory > theConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()));
+
+ // access the provider
+ uno::Sequence< uno::Any > theArgs {uno::Any(sConfigURL)};
+ xNameAccess.set(
+ theConfigProvider->createInstanceWithArguments(
+ sAccessSrvc, theArgs ), uno::UNO_QUERY_THROW );
+ } catch (const css::uno::Exception&) {
+ TOOLS_WARN_EXCEPTION("desktop.migration", "ignoring");
+ }
+ return xNameAccess;
+}
+
+void MigrationImpl::copyFiles()
+{
+ OUString localName;
+ OUString destName;
+ OUString userInstall;
+ utl::Bootstrap::PathStatus aStatus;
+ aStatus = utl::Bootstrap::locateUserInstallation(userInstall);
+ if (aStatus == utl::Bootstrap::PATH_EXISTS) {
+ for (auto const& rFile : *m_vrFileList)
+ {
+ // remove installation prefix from file
+ localName = rFile.copy(m_aInfo.userdata.getLength());
+ if (localName.endsWith( "/autocorr/acor_.dat")) {
+ // Previous versions used an empty language tag for
+ // LANGUAGE_DONTKNOW with the "[All]" autocorrection entry.
+ // As of LibreOffice 4.0 it is 'und' for LANGUAGE_UNDETERMINED
+ // so the file name is "acor_und.dat".
+ localName = OUString::Concat(localName.subView( 0, localName.getLength() - 4)) + "und.dat";
+ }
+ destName = userInstall + localName;
+ INetURLObject aURL(destName);
+ // check whether destination directory exists
+ aURL.removeSegment();
+ _checkAndCreateDirectory(aURL);
+ FileBase::RC copyResult = File::copy(rFile, destName);
+ if (copyResult != FileBase::E_None) {
+ SAL_WARN( "desktop", "Cannot copy " << rFile << " to " << destName);
+ }
+ }
+ } else {
+ OSL_FAIL("copyFiles: UserInstall does not exist");
+ }
+}
+
+void MigrationImpl::runServices()
+{
+ // Build argument array
+ uno::Sequence< uno::Any > seqArguments(3);
+ auto pseqArguments = seqArguments.getArray();
+ pseqArguments[0] <<= NamedValue("Productname",
+ uno::Any(m_aInfo.productname));
+ pseqArguments[1] <<= NamedValue("UserData",
+ uno::Any(m_aInfo.userdata));
+
+
+ // create an instance of every migration service
+ // and execute the migration job
+ uno::Reference< XJob > xMigrationJob;
+
+ uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext());
+ for (auto const& rMigration : *m_vrMigrations)
+ {
+ if( !rMigration.service.isEmpty()) {
+
+ try {
+ // set black list for extension migration
+ uno::Sequence< OUString > seqExtDenyList;
+ sal_uInt32 nSize = rMigration.excludeExtensions.size();
+ if ( nSize > 0 )
+ seqExtDenyList = comphelper::arrayToSequence< OUString >(
+ rMigration.excludeExtensions.data(), nSize );
+ pseqArguments[2] <<= NamedValue("ExtensionDenyList",
+ uno::Any( seqExtDenyList ));
+
+ xMigrationJob.set(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext(rMigration.service, seqArguments, xContext),
+ uno::UNO_QUERY_THROW);
+
+ xMigrationJob->execute(uno::Sequence< NamedValue >());
+
+
+ } catch (const Exception&) {
+ TOOLS_WARN_EXCEPTION( "desktop", "Execution of migration service failed. Service: "
+ << rMigration.service);
+ } catch (...) {
+ SAL_WARN( "desktop", "Execution of migration service failed (Exception caught).\nService: "
+ << rMigration.service << "\nNo message available");
+ }
+
+ }
+ }
+}
+
+std::vector< MigrationModuleInfo > MigrationImpl::detectUIChangesForAllModules() const
+{
+ std::vector< MigrationModuleInfo > vModulesInfo;
+ static constexpr OUStringLiteral MENUBAR(u"menubar");
+ static constexpr OUStringLiteral TOOLBAR(u"toolbar");
+
+ uno::Sequence< uno::Any > lArgs {uno::Any(m_aInfo.userdata + "/user/config/soffice.cfg/modules"),
+ uno::Any(embed::ElementModes::READ)};
+
+ uno::Reference< lang::XSingleServiceFactory > xStorageFactory(
+ embed::FileSystemStorageFactory::create(comphelper::getProcessComponentContext()));
+ uno::Reference< embed::XStorage > xModules;
+
+ xModules.set(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY);
+ if (!xModules.is())
+ return vModulesInfo;
+
+ uno::Sequence< OUString > lNames = xModules->getElementNames();
+ sal_Int32 nLength = lNames.getLength();
+ for (sal_Int32 i=0; i<nLength; ++i) {
+ OUString sModuleShortName = lNames[i];
+ uno::Reference< embed::XStorage > xModule = xModules->openStorageElement(sModuleShortName, embed::ElementModes::READ);
+ if (xModule.is()) {
+ MigrationModuleInfo aModuleInfo;
+
+ uno::Reference< embed::XStorage > xMenubar = xModule->openStorageElement(MENUBAR, embed::ElementModes::READ);
+ if (xMenubar.is()) {
+ if (xMenubar->getElementNames().hasElements()) {
+ aModuleInfo.sModuleShortName = sModuleShortName;
+ aModuleInfo.bHasMenubar = true;
+ }
+ }
+
+ uno::Reference< embed::XStorage > xToolbar = xModule->openStorageElement(TOOLBAR, embed::ElementModes::READ);
+ if (xToolbar.is()) {
+ const ::uno::Sequence< OUString > lToolbars = xToolbar->getElementNames();
+ for (OUString const & sToolbarName : lToolbars) {
+ if (sToolbarName.startsWith("custom_"))
+ continue;
+
+ aModuleInfo.sModuleShortName = sModuleShortName;
+ sal_Int32 nIndex = sToolbarName.lastIndexOf('.');
+ if (nIndex > 0) {
+ std::u16string_view sExtension(sToolbarName.subView(nIndex));
+ OUString sToolbarResourceName(sToolbarName.copy(0, nIndex));
+ if (!sToolbarResourceName.isEmpty() && sExtension == u".xml")
+ aModuleInfo.m_vToolbars.push_back(sToolbarResourceName);
+ }
+ }
+ }
+
+ if (!aModuleInfo.sModuleShortName.isEmpty())
+ vModulesInfo.push_back(aModuleInfo);
+ }
+ }
+
+ return vModulesInfo;
+}
+
+void MigrationImpl::compareOldAndNewConfig(const OUString& sParent,
+ const uno::Reference< container::XIndexContainer >& xIndexOld,
+ const uno::Reference< container::XIndexContainer >& xIndexNew,
+ const OUString& sResourceURL)
+{
+ static constexpr OUStringLiteral MENU_SEPARATOR(u" | ");
+
+ std::vector< MigrationItem > vOldItems;
+ std::vector< MigrationItem > vNewItems;
+ uno::Sequence< beans::PropertyValue > aProps;
+ sal_Int32 nOldCount = xIndexOld->getCount();
+ sal_Int32 nNewCount = xIndexNew->getCount();
+
+ for (int n=0; n<nOldCount; ++n) {
+ MigrationItem aMigrationItem;
+ if (xIndexOld->getByIndex(n) >>= aProps) {
+ for(beans::PropertyValue const & prop : std::as_const(aProps)) {
+ if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL )
+ prop.Value >>= aMigrationItem.m_sCommandURL;
+ else if ( prop.Name == ITEM_DESCRIPTOR_CONTAINER )
+ prop.Value >>= aMigrationItem.m_xPopupMenu;
+ }
+
+ if (!aMigrationItem.m_sCommandURL.isEmpty())
+ vOldItems.push_back(aMigrationItem);
+ }
+ }
+
+ for (int n=0; n<nNewCount; ++n) {
+ MigrationItem aMigrationItem;
+ if (xIndexNew->getByIndex(n) >>= aProps) {
+ for(beans::PropertyValue const & prop : std::as_const(aProps)) {
+ if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL )
+ prop.Value >>= aMigrationItem.m_sCommandURL;
+ else if ( prop.Name == ITEM_DESCRIPTOR_CONTAINER )
+ prop.Value >>= aMigrationItem.m_xPopupMenu;
+ }
+
+ if (!aMigrationItem.m_sCommandURL.isEmpty())
+ vNewItems.push_back(aMigrationItem);
+ }
+ }
+
+ OUString sSibling;
+ for (auto const& oldItem : vOldItems)
+ {
+ std::vector< MigrationItem >::iterator pFound = std::find(vNewItems.begin(), vNewItems.end(), oldItem);
+ if (pFound != vNewItems.end() && oldItem.m_xPopupMenu.is()) {
+ OUString sName;
+ if (!sParent.isEmpty())
+ sName = sParent + MENU_SEPARATOR + oldItem.m_sCommandURL;
+ else
+ sName = oldItem.m_sCommandURL;
+ compareOldAndNewConfig(sName, oldItem.m_xPopupMenu, pFound->m_xPopupMenu, sResourceURL);
+ } else if (pFound == vNewItems.end()) {
+ MigrationItem aMigrationItem(sParent, sSibling, oldItem.m_sCommandURL, oldItem.m_xPopupMenu);
+ if (m_aOldVersionItemsHashMap.find(sResourceURL)==m_aOldVersionItemsHashMap.end()) {
+ std::vector< MigrationItem > vMigrationItems;
+ m_aOldVersionItemsHashMap.emplace(sResourceURL, vMigrationItems);
+ m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem);
+ } else {
+ if (std::find(m_aOldVersionItemsHashMap[sResourceURL].begin(), m_aOldVersionItemsHashMap[sResourceURL].end(), aMigrationItem)==m_aOldVersionItemsHashMap[sResourceURL].end())
+ m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem);
+ }
+ }
+
+ sSibling = oldItem.m_sCommandURL;
+ }
+}
+
+void MigrationImpl::mergeOldToNewVersion(const uno::Reference< ui::XUIConfigurationManager >& xCfgManager,
+ const uno::Reference< container::XIndexContainer>& xIndexContainer,
+ const OUString& sModuleIdentifier,
+ const OUString& sResourceURL)
+{
+ MigrationHashMap::iterator pFound = m_aOldVersionItemsHashMap.find(sResourceURL);
+ if (pFound==m_aOldVersionItemsHashMap.end())
+ return;
+
+ for (auto const& elem : pFound->second)
+ {
+ uno::Reference< container::XIndexContainer > xTemp = xIndexContainer;
+
+ OUString sParentNodeName = elem.m_sParentNodeName;
+ sal_Int32 nIndex = 0;
+ do {
+ std::u16string_view sToken( o3tl::trim(o3tl::getToken(sParentNodeName, 0, '|', nIndex)) );
+ if (sToken.empty())
+ break;
+
+ sal_Int32 nCount = xTemp->getCount();
+ for (sal_Int32 i=0; i<nCount; ++i) {
+ OUString sCommandURL;
+ OUString sLabel;
+ uno::Reference< container::XIndexContainer > xChild;
+
+ uno::Sequence< beans::PropertyValue > aPropSeq;
+ xTemp->getByIndex(i) >>= aPropSeq;
+ for (beans::PropertyValue const & prop : std::as_const(aPropSeq)) {
+ OUString sPropName = prop.Name;
+ if ( sPropName == ITEM_DESCRIPTOR_COMMANDURL )
+ prop.Value >>= sCommandURL;
+ else if ( sPropName == ITEM_DESCRIPTOR_LABEL )
+ prop.Value >>= sLabel;
+ else if ( sPropName == ITEM_DESCRIPTOR_CONTAINER )
+ prop.Value >>= xChild;
+ }
+
+ if (sCommandURL == sToken) {
+ xTemp = xChild;
+ break;
+ }
+ }
+
+ } while (nIndex >= 0);
+
+ if (nIndex == -1) {
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(elem.m_sCommandURL, sModuleIdentifier);
+ uno::Sequence< beans::PropertyValue > aPropSeq {
+ beans::PropertyValue(ITEM_DESCRIPTOR_COMMANDURL, 0, uno::Any(elem.m_sCommandURL), beans::PropertyState_DIRECT_VALUE),
+ beans::PropertyValue(ITEM_DESCRIPTOR_LABEL, 0, uno::Any(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)), beans::PropertyState_DIRECT_VALUE),
+ beans::PropertyValue(ITEM_DESCRIPTOR_CONTAINER, 0, uno::Any(elem.m_xPopupMenu), beans::PropertyState_DIRECT_VALUE)
+ };
+
+ if (elem.m_sPrevSibling.isEmpty())
+ xTemp->insertByIndex(0, uno::Any(aPropSeq));
+ else {
+ sal_Int32 nCount = xTemp->getCount();
+ sal_Int32 i = 0;
+ for (; i<nCount; ++i) {
+ OUString sCmd;
+ uno::Sequence< beans::PropertyValue > aTempPropSeq;
+ xTemp->getByIndex(i) >>= aTempPropSeq;
+ for (beans::PropertyValue const & prop : std::as_const(aTempPropSeq)) {
+ if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL ) {
+ prop.Value >>= sCmd;
+ break;
+ }
+ }
+
+ if (sCmd == elem.m_sPrevSibling)
+ break;
+ }
+
+ xTemp->insertByIndex(i+1, uno::Any(aPropSeq));
+ }
+ }
+ }
+
+ if (xIndexContainer.is())
+ xCfgManager->replaceSettings(sResourceURL, xIndexContainer);
+
+ uno::Reference< ui::XUIConfigurationPersistence > xUIConfigurationPersistence(xCfgManager, uno::UNO_QUERY);
+ if (xUIConfigurationPersistence.is())
+ xUIConfigurationPersistence->store();
+}
+
+uno::Reference< ui::XUIConfigurationManager > NewVersionUIInfo::getConfigManager(std::u16string_view sModuleShortName) const
+{
+ uno::Reference< ui::XUIConfigurationManager > xCfgManager;
+
+ for ( const css::beans::PropertyValue& rProp : m_lCfgManagerSeq) {
+ if (rProp.Name == sModuleShortName) {
+ rProp.Value >>= xCfgManager;
+ break;
+ }
+ }
+
+ return xCfgManager;
+}
+
+uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewMenubarSettings(std::u16string_view sModuleShortName) const
+{
+ uno::Reference< container::XIndexContainer > xNewMenuSettings;
+
+ for (auto const & prop : m_lNewVersionMenubarSettingsSeq) {
+ if (prop.Name == sModuleShortName) {
+ prop.Value >>= xNewMenuSettings;
+ break;
+ }
+ }
+
+ return xNewMenuSettings;
+}
+
+uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewToolbarSettings(std::u16string_view sModuleShortName, std::u16string_view sToolbarName) const
+{
+ uno::Reference< container::XIndexContainer > xNewToolbarSettings;
+
+ for (auto const & newProp : m_lNewVersionToolbarSettingsSeq) {
+ if (newProp.Name == sModuleShortName) {
+ uno::Sequence< beans::PropertyValue > lToolbarSettingsSeq;
+ newProp.Value >>= lToolbarSettingsSeq;
+ for (auto const & prop : std::as_const(lToolbarSettingsSeq)) {
+ if (prop.Name == sToolbarName) {
+ prop.Value >>= xNewToolbarSettings;
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return xNewToolbarSettings;
+}
+
+void NewVersionUIInfo::init(const std::vector< MigrationModuleInfo >& vModulesInfo)
+{
+ m_lCfgManagerSeq.resize(vModulesInfo.size());
+ m_lNewVersionMenubarSettingsSeq.realloc(vModulesInfo.size());
+ auto p_lNewVersionMenubarSettingsSeq = m_lNewVersionMenubarSettingsSeq.getArray();
+ m_lNewVersionToolbarSettingsSeq.realloc(vModulesInfo.size());
+ auto p_lNewVersionToolbarSettingsSeq = m_lNewVersionToolbarSettingsSeq.getArray();
+
+ static constexpr OUStringLiteral sMenubarResourceURL(u"private:resource/menubar/menubar");
+ static constexpr OUStringLiteral sToolbarResourcePre(u"private:resource/toolbar/");
+
+ uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier = ui::theModuleUIConfigurationManagerSupplier::get( ::comphelper::getProcessComponentContext() );
+
+ for (size_t i=0; i<vModulesInfo.size(); ++i) {
+ OUString sModuleIdentifier = mapModuleShortNameToIdentifier(vModulesInfo[i].sModuleShortName);
+ if (!sModuleIdentifier.isEmpty()) {
+ uno::Reference< ui::XUIConfigurationManager > xCfgManager = xModuleCfgSupplier->getUIConfigurationManager(sModuleIdentifier);
+ m_lCfgManagerSeq[i].Name = vModulesInfo[i].sModuleShortName;
+ m_lCfgManagerSeq[i].Value <<= xCfgManager;
+
+ if (vModulesInfo[i].bHasMenubar) {
+ p_lNewVersionMenubarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName;
+ p_lNewVersionMenubarSettingsSeq[i].Value <<= xCfgManager->getSettings(sMenubarResourceURL, true);
+ }
+
+ sal_Int32 nToolbars = vModulesInfo[i].m_vToolbars.size();
+ if (nToolbars > 0) {
+ uno::Sequence< beans::PropertyValue > lPropSeq(nToolbars);
+ auto plPropSeq = lPropSeq.getArray();
+ for (sal_Int32 j=0; j<nToolbars; ++j) {
+ OUString sToolbarName = vModulesInfo[i].m_vToolbars[j];
+ OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName;
+
+ plPropSeq[j].Name = sToolbarName;
+ plPropSeq[j].Value <<= xCfgManager->getSettings(sToolbarResourceURL, true);
+ }
+
+ p_lNewVersionToolbarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName;
+ p_lNewVersionToolbarSettingsSeq[i].Value <<= lPropSeq;
+ }
+ }
+ }
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/migration_impl.hxx b/desktop/source/migration/migration_impl.hxx
new file mode 100644
index 0000000000..6b0923d292
--- /dev/null
+++ b/desktop/source/migration/migration_impl.hxx
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <o3tl/string_view.hxx>
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/container/XIndexContainer.hpp>
+#include <com/sun/star/ui/XUIConfigurationManager.hpp>
+
+namespace desktop
+{
+
+struct install_info
+{
+ OUString productname; // human readable product name
+ OUString userdata; // file: url for user installation
+};
+
+typedef std::vector< OUString > strings_v;
+typedef std::unique_ptr< strings_v > strings_vr;
+
+struct migration_step
+{
+ strings_v includeFiles;
+ strings_v excludeFiles;
+ strings_v includeConfig;
+ strings_v excludeConfig;
+ strings_v excludeExtensions;
+ OUString service;
+};
+
+struct supported_migration
+{
+ OUString name;
+ sal_Int32 nPriority;
+ strings_v supported_versions;
+};
+
+typedef std::vector< migration_step > migrations_v;
+typedef std::unique_ptr< migrations_v > migrations_vr;
+typedef std::vector< supported_migration > migrations_available;
+
+inline bool areBothOpenFrom(std::u16string_view cmd1, std::u16string_view cmd2)
+{
+ return cmd1 == u".uno:Open" && o3tl::starts_with(cmd2, u".uno:OpenFrom");
+}
+
+/**
+ define the item, e.g.:menuitem, toolbaritem, to be migrated. we keep the information
+ of the command URL, the previous sibling node and the parent node of an item
+*/
+struct MigrationItem
+{
+ OUString m_sParentNodeName;
+ OUString m_sPrevSibling;
+ OUString m_sCommandURL;
+ css::uno::Reference< css::container::XIndexContainer > m_xPopupMenu;
+
+ MigrationItem()
+ {
+ }
+
+ MigrationItem(OUString sParentNodeName,
+ OUString sPrevSibling,
+ OUString sCommandURL,
+ css::uno::Reference< css::container::XIndexContainer > xPopupMenu)
+ : m_sParentNodeName(std::move(sParentNodeName)), m_sPrevSibling(std::move(sPrevSibling)),
+ m_sCommandURL(std::move(sCommandURL)), m_xPopupMenu(std::move(xPopupMenu))
+ {
+ }
+
+ bool operator==(const MigrationItem& aMigrationItem) const
+ {
+ return
+ (aMigrationItem.m_sCommandURL == m_sCommandURL
+ || areBothOpenFrom(aMigrationItem.m_sCommandURL, m_sCommandURL)
+ || areBothOpenFrom(m_sCommandURL, aMigrationItem.m_sCommandURL))
+ && aMigrationItem.m_sParentNodeName == m_sParentNodeName
+ && aMigrationItem.m_sPrevSibling == m_sPrevSibling
+ && aMigrationItem.m_xPopupMenu.is() == m_xPopupMenu.is();
+ }
+};
+
+typedef std::unordered_map< OUString, std::vector< MigrationItem > > MigrationHashMap;
+
+/**
+ information for the UI elements to be migrated for one module
+*/
+struct MigrationModuleInfo
+{
+ OUString sModuleShortName;
+ bool bHasMenubar;
+ std::vector< OUString > m_vToolbars;
+
+ MigrationModuleInfo() : bHasMenubar(false) {};
+};
+
+
+/**
+ get the information before copying the ui configuration files of old version to new version
+*/
+class NewVersionUIInfo
+{
+public:
+
+ css::uno::Reference< css::ui::XUIConfigurationManager > getConfigManager(std::u16string_view sModuleShortName) const;
+ css::uno::Reference< css::container::XIndexContainer > getNewMenubarSettings(std::u16string_view sModuleShortName) const;
+ css::uno::Reference< css::container::XIndexContainer > getNewToolbarSettings(std::u16string_view sModuleShortName, std::u16string_view sToolbarName) const;
+ void init(const std::vector< MigrationModuleInfo >& vModulesInfo);
+
+private:
+
+ std::vector< css::beans::PropertyValue > m_lCfgManagerSeq;
+ css::uno::Sequence< css::beans::PropertyValue > m_lNewVersionMenubarSettingsSeq;
+ css::uno::Sequence< css::beans::PropertyValue > m_lNewVersionToolbarSettingsSeq;
+};
+
+class MigrationImpl
+{
+
+private:
+ migrations_available m_vMigrationsAvailable; // list of all available migrations
+ migrations_vr m_vrMigrations; // list of all migration specs from config
+ install_info m_aInfo; // info about the version being migrated
+ strings_vr m_vrFileList; // final list of files to be copied
+ MigrationHashMap m_aOldVersionItemsHashMap;
+
+ // functions to control the migration process
+ static void readAvailableMigrations(migrations_available&);
+ bool alreadyMigrated();
+ static migrations_vr readMigrationSteps(const OUString& rMigrationName);
+ sal_Int32 findPreferredMigrationProcess(const migrations_available&);
+#if defined UNX && ! defined MACOSX
+ static OUString preXDGConfigDir(const OUString& rConfigDir);
+#endif
+ static void setInstallInfoIfExist(install_info& aInfo, std::u16string_view rConfigDir, const OUString& rVersion);
+ static install_info findInstallation(const strings_v& rVersions);
+ strings_vr compileFileList();
+
+ // helpers
+ strings_vr getAllFiles(const OUString& baseURL) const;
+ static strings_vr applyPatterns(const strings_v& vSet, const strings_v& vPatterns);
+ static css::uno::Reference< css::container::XNameAccess > getConfigAccess(const char* path, bool rw=false);
+
+ std::vector< MigrationModuleInfo > detectUIChangesForAllModules() const;
+ void compareOldAndNewConfig(const OUString& sParentNodeName,
+ const css::uno::Reference< css::container::XIndexContainer >& xOldIndexContainer,
+ const css::uno::Reference< css::container::XIndexContainer >& xNewIndexContainer,
+ const OUString& sToolbarName);
+ void mergeOldToNewVersion(const css::uno::Reference< css::ui::XUIConfigurationManager >& xCfgManager,
+ const css::uno::Reference< css::container::XIndexContainer>& xIndexContainer,
+ const OUString& sModuleIdentifier,
+ const OUString& sResourceURL);
+
+ // actual processing function that perform the migration steps
+ void copyFiles();
+ void copyConfig();
+ void runServices();
+
+ static void setMigrationCompleted();
+ static bool checkMigrationCompleted();
+
+public:
+ MigrationImpl();
+ ~MigrationImpl();
+ bool initializeMigration();
+ bool doMigration();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/basicmigration.cxx b/desktop/source/migration/services/basicmigration.cxx
new file mode 100644
index 0000000000..94e8677de7
--- /dev/null
+++ b/desktop/source/migration/services/basicmigration.cxx
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "basicmigration.hxx"
+#include <cppuhelper/supportsservice.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <osl/file.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+namespace migration
+{
+
+
+ #define sSourceUserBasic "/user/basic"
+ #define sTargetUserBasic "/user/__basic_80"
+
+
+ // BasicMigration
+
+
+ BasicMigration::BasicMigration()
+ {
+ }
+
+
+ BasicMigration::~BasicMigration()
+ {
+ }
+
+
+ TStringVectorPtr BasicMigration::getFiles( const OUString& rBaseURL ) const
+ {
+ TStringVectorPtr aResult( new TStringVector );
+ ::osl::Directory aDir( rBaseURL);
+
+ if ( aDir.open() == ::osl::FileBase::E_None )
+ {
+ // iterate over directory content
+ TStringVector aSubDirs;
+ ::osl::DirectoryItem aItem;
+ while ( aDir.getNextItem( aItem ) == ::osl::FileBase::E_None )
+ {
+ ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL );
+ if ( aItem.getFileStatus( aFileStatus ) == ::osl::FileBase::E_None )
+ {
+ if ( aFileStatus.getFileType() == ::osl::FileStatus::Directory )
+ aSubDirs.push_back( aFileStatus.getFileURL() );
+ else
+ aResult->push_back( aFileStatus.getFileURL() );
+ }
+ }
+
+ // iterate recursive over subfolders
+ for (auto const& subDir : aSubDirs)
+ {
+ TStringVectorPtr aSubResult = getFiles(subDir);
+ aResult->insert( aResult->end(), aSubResult->begin(), aSubResult->end() );
+ }
+ }
+
+ return aResult;
+ }
+
+
+ void BasicMigration::checkAndCreateDirectory( INetURLObject const & rDirURL )
+ {
+ ::osl::FileBase::RC aResult = ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) );
+ if ( aResult == ::osl::FileBase::E_NOENT )
+ {
+ INetURLObject aBaseURL( rDirURL );
+ aBaseURL.removeSegment();
+ checkAndCreateDirectory( aBaseURL );
+ ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) );
+ }
+ }
+
+
+ void BasicMigration::copyFiles()
+ {
+ OUString sTargetDir;
+ ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( sTargetDir );
+ if ( aStatus == ::utl::Bootstrap::PATH_EXISTS )
+ {
+ sTargetDir += sTargetUserBasic;
+ TStringVectorPtr aFileList = getFiles( m_sSourceDir );
+ for (auto const& elem : *aFileList)
+ {
+ std::u16string_view sLocalName = elem.subView( m_sSourceDir.getLength() );
+ OUString sTargetName = sTargetDir + sLocalName;
+ INetURLObject aURL( sTargetName );
+ aURL.removeSegment();
+ checkAndCreateDirectory( aURL );
+ ::osl::FileBase::RC aResult = ::osl::File::copy( elem, sTargetName );
+ if ( aResult != ::osl::FileBase::E_None )
+ {
+ SAL_WARN( "desktop", "BasicMigration::copyFiles: cannot copy "
+ << elem << " to " << sTargetName );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "BasicMigration::copyFiles: no user installation!" );
+ }
+ }
+
+
+ // XServiceInfo
+
+
+ OUString BasicMigration::getImplementationName()
+ {
+ return "com.sun.star.comp.desktop.migration.Basic";
+ }
+
+
+ sal_Bool BasicMigration::supportsService(OUString const & ServiceName)
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+
+ Sequence< OUString > BasicMigration::getSupportedServiceNames()
+ {
+ return { "com.sun.star.migration.Basic" };
+ }
+
+
+ // XInitialization
+
+
+ void BasicMigration::initialize( const Sequence< Any >& aArguments )
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ const Any* pIter = aArguments.getConstArray();
+ const Any* pEnd = pIter + aArguments.getLength();
+ for ( ; pIter != pEnd ; ++pIter )
+ {
+ beans::NamedValue aValue;
+ *pIter >>= aValue;
+ if ( aValue.Name == "UserData" )
+ {
+ if ( !(aValue.Value >>= m_sSourceDir) )
+ {
+ OSL_FAIL( "BasicMigration::initialize: argument UserData has wrong type!" );
+ }
+ m_sSourceDir += sSourceUserBasic;
+ break;
+ }
+ }
+ }
+
+
+ // XJob
+
+
+ Any BasicMigration::execute( const Sequence< beans::NamedValue >& )
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ copyFiles();
+
+ return Any();
+ }
+
+
+} // namespace migration
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_BasicMigration_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new migration::BasicMigration());
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/basicmigration.hxx b/desktop/source/migration/services/basicmigration.hxx
new file mode 100644
index 0000000000..889b9245d0
--- /dev/null
+++ b/desktop/source/migration/services/basicmigration.hxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "misc.hxx"
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <osl/mutex.hxx>
+
+
+class INetURLObject;
+
+
+namespace migration
+{
+ typedef ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XInitialization,
+ css::task::XJob > BasicMigration_BASE;
+
+ class BasicMigration : public BasicMigration_BASE
+ {
+ private:
+ ::osl::Mutex m_aMutex;
+ OUString m_sSourceDir;
+
+ TStringVectorPtr getFiles( const OUString& rBaseURL ) const;
+ void checkAndCreateDirectory( INetURLObject const & rDirURL );
+ void copyFiles();
+
+ public:
+ BasicMigration();
+ virtual ~BasicMigration() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XJob
+ virtual css::uno::Any SAL_CALL execute(
+ const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override;
+ };
+
+
+} // namespace migration
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/cppumaker.mk b/desktop/source/migration/services/cppumaker.mk
new file mode 100644
index 0000000000..57e070f80d
--- /dev/null
+++ b/desktop/source/migration/services/cppumaker.mk
@@ -0,0 +1,27 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+.IF "$(debug)" != ""
+
+# MSVC++: no inlining
+.IF "$(COM)" == "MSC"
+CFLAGS += -Ob0
+.ENDIF
+
+.ENDIF
+
diff --git a/desktop/source/migration/services/jvmfwk.cxx b/desktop/source/migration/services/jvmfwk.cxx
new file mode 100644
index 0000000000..892c651f29
--- /dev/null
+++ b/desktop/source/migration/services/jvmfwk.cxx
@@ -0,0 +1,395 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/types.h>
+#include <sal/config.h>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/WrappedTargetException.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <com/sun/star/configuration/backend/XLayer.hpp>
+#include <com/sun/star/configuration/backend/XLayerHandler.hpp>
+#include <com/sun/star/configuration/backend/MalformedDataException.hpp>
+#include <com/sun/star/configuration/backend/TemplateIdentifier.hpp>
+#include <jvmfwk/framework.hxx>
+#include "jvmfwk.hxx"
+#include <memory>
+#include <stack>
+#include <stdio.h>
+
+#include <osl/diagnose.h>
+
+constexpr OUString SERVICE_NAME = u"com.sun.star.migration.Java"_ustr;
+#define IMPL_NAME "com.sun.star.comp.desktop.migration.Java"
+
+#define ENABLE_JAVA 1
+#define USER_CLASS_PATH 2
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::configuration::backend;
+
+namespace migration
+{
+
+namespace {
+
+class JavaMigration : public ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XInitialization,
+ css::task::XJob,
+ css::configuration::backend::XLayerHandler>
+{
+public:
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString & rServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ //XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ //XJob
+ virtual css::uno::Any SAL_CALL execute(
+ const css::uno::Sequence<css::beans::NamedValue >& Arguments ) override;
+
+ // XLayerHandler
+ virtual void SAL_CALL startLayer() override;
+
+ virtual void SAL_CALL endLayer() override;
+
+ virtual void SAL_CALL overrideNode(
+ const OUString& aName,
+ sal_Int16 aAttributes,
+ sal_Bool bClear) override;
+
+ virtual void SAL_CALL addOrReplaceNode(
+ const OUString& aName,
+ sal_Int16 aAttributes) override;
+
+ virtual void SAL_CALL addOrReplaceNodeFromTemplate(
+ const OUString& aName,
+ const css::configuration::backend::TemplateIdentifier& aTemplate,
+ sal_Int16 aAttributes ) override;
+
+ virtual void SAL_CALL endNode() override;
+
+ virtual void SAL_CALL dropNode(
+ const OUString& aName ) override;
+
+ virtual void SAL_CALL overrideProperty(
+ const OUString& aName,
+ sal_Int16 aAttributes,
+ const css::uno::Type& aType,
+ sal_Bool bClear ) override;
+
+ virtual void SAL_CALL setPropertyValue(
+ const css::uno::Any& aValue ) override;
+
+ virtual void SAL_CALL setPropertyValueForLocale(
+ const css::uno::Any& aValue,
+ const OUString& aLocale ) override;
+
+ virtual void SAL_CALL endProperty() override;
+
+ virtual void SAL_CALL addProperty(
+ const OUString& aName,
+ sal_Int16 aAttributes,
+ const css::uno::Type& aType ) override;
+
+ virtual void SAL_CALL addPropertyWithValue(
+ const OUString& aName,
+ sal_Int16 aAttributes,
+ const css::uno::Any& aValue ) override;
+
+
+ virtual ~JavaMigration() override;
+
+private:
+ OUString m_sUserDir;
+ css::uno::Reference< css::configuration::backend::XLayer> m_xLayer;
+
+ void migrateJavarc();
+ typedef std::pair< OUString, sal_Int16> TElementType;
+ typedef std::stack< TElementType > TElementStack;
+ TElementStack m_aStack;
+
+};
+
+}
+
+JavaMigration::~JavaMigration()
+{
+ OSL_ASSERT(m_aStack.empty());
+}
+
+OUString jvmfwk_getImplementationName()
+{
+ return IMPL_NAME;
+}
+
+css::uno::Sequence< OUString > jvmfwk_getSupportedServiceNames()
+{
+ return { SERVICE_NAME };
+}
+
+// XServiceInfo
+OUString SAL_CALL JavaMigration::getImplementationName()
+{
+ return jvmfwk_getImplementationName();
+}
+
+sal_Bool JavaMigration::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL JavaMigration::getSupportedServiceNames()
+{
+ return jvmfwk_getSupportedServiceNames();
+}
+
+//XInitialization ----------------------------------------------------------------------
+void SAL_CALL JavaMigration::initialize( const css::uno::Sequence< css::uno::Any >& aArguments )
+{
+ const css::uno::Any* pIter = aArguments.getConstArray();
+ const css::uno::Any* pEnd = pIter + aArguments.getLength();
+ css::uno::Sequence<css::beans::NamedValue> aOldConfigValues;
+ css::beans::NamedValue aValue;
+ for(;pIter != pEnd;++pIter)
+ {
+ *pIter >>= aValue;
+ if ( aValue.Name == "OldConfiguration" )
+ {
+ bool bSuccess = aValue.Value >>= aOldConfigValues;
+ OSL_ENSURE(bSuccess, "[Service implementation " IMPL_NAME
+ "] XInitialization::initialize: Argument OldConfiguration has wrong type.");
+ if (bSuccess)
+ {
+ const css::beans::NamedValue* pIter2 = aOldConfigValues.getConstArray();
+ const css::beans::NamedValue* pEnd2 = pIter2 + aOldConfigValues.getLength();
+ for(;pIter2 != pEnd2;++pIter2)
+ {
+ if ( pIter2->Name == "org.openoffice.Office.Java" )
+ {
+ pIter2->Value >>= m_xLayer;
+ break;
+ }
+ }
+ }
+ }
+ else if ( aValue.Name == "UserData" )
+ {
+ if ( !(aValue.Value >>= m_sUserDir) )
+ {
+ OSL_FAIL(
+ "[Service implementation " IMPL_NAME
+ "] XInitialization::initialize: Argument UserData has wrong type.");
+ }
+ }
+ }
+
+}
+
+//XJob
+css::uno::Any SAL_CALL JavaMigration::execute(
+ const css::uno::Sequence<css::beans::NamedValue >& )
+{
+ migrateJavarc();
+ if (m_xLayer.is())
+ m_xLayer->readData(this);
+
+ return css::uno::Any();
+}
+
+void JavaMigration::migrateJavarc()
+{
+ if (m_sUserDir.isEmpty())
+ return;
+
+ OUString sValue;
+ rtl::Bootstrap javaini(m_sUserDir + "/user/config/" SAL_CONFIGFILE("java"));
+ bool bSuccess = javaini.getFrom("Home", sValue);
+ OSL_ENSURE(bSuccess, "[Service implementation " IMPL_NAME
+ "] XJob::execute: Could not get Home entry from java.ini/javarc.");
+ if (!bSuccess || sValue.isEmpty())
+ return;
+
+ //get the directory
+ std::unique_ptr<JavaInfo> aInfo;
+ javaFrameworkError err = jfw_getJavaInfoByPath(sValue, &aInfo);
+
+ if (err == JFW_E_NONE)
+ {
+ if (jfw_setSelectedJRE(aInfo.get()) != JFW_E_NONE)
+ {
+ OSL_FAIL("[Service implementation " IMPL_NAME
+ "] XJob::execute: jfw_setSelectedJRE failed.");
+ fprintf(stderr, "\nCannot migrate Java. An error occurred.\n");
+ }
+ }
+ else if (err == JFW_E_FAILED_VERSION)
+ {
+ fprintf(stderr, "\nCannot migrate Java settings because the version of the Java "
+ "is not supported anymore.\n");
+ }
+}
+
+
+// XLayerHandler
+void SAL_CALL JavaMigration::startLayer()
+{
+}
+
+
+void SAL_CALL JavaMigration::endLayer()
+{
+}
+
+
+void SAL_CALL JavaMigration::overrideNode(
+ const OUString&,
+ sal_Int16,
+ sal_Bool)
+
+{
+
+}
+
+
+void SAL_CALL JavaMigration::addOrReplaceNode(
+ const OUString&,
+ sal_Int16)
+{
+
+}
+void SAL_CALL JavaMigration::endNode()
+{
+}
+
+
+void SAL_CALL JavaMigration::dropNode(
+ const OUString& )
+{
+}
+
+
+void SAL_CALL JavaMigration::overrideProperty(
+ const OUString& aName,
+ sal_Int16,
+ const Type&,
+ sal_Bool )
+{
+ if ( aName == "Enable" )
+ m_aStack.push(TElementStack::value_type(aName,ENABLE_JAVA));
+ else if ( aName == "UserClassPath" )
+ m_aStack.push(TElementStack::value_type(aName, USER_CLASS_PATH));
+}
+
+
+void SAL_CALL JavaMigration::setPropertyValue(
+ const Any& aValue )
+{
+ if ( m_aStack.empty())
+ return;
+
+ switch (m_aStack.top().second)
+ {
+ case ENABLE_JAVA:
+ {
+ bool val;
+ if (!(aValue >>= val))
+ throw MalformedDataException(
+ "[Service implementation " IMPL_NAME
+ "] XLayerHandler::setPropertyValue received wrong type for Enable property", nullptr, Any());
+ if (jfw_setEnabled(val) != JFW_E_NONE)
+ throw WrappedTargetException(
+ "[Service implementation " IMPL_NAME
+ "] XLayerHandler::setPropertyValue: jfw_setEnabled failed.", nullptr, Any());
+
+ break;
+ }
+ case USER_CLASS_PATH:
+ {
+ OUString cp;
+ if (!(aValue >>= cp))
+ throw MalformedDataException(
+ "[Service implementation " IMPL_NAME
+ "] XLayerHandler::setPropertyValue received wrong type for UserClassPath property", nullptr, Any());
+
+ if (jfw_setUserClassPath(cp) != JFW_E_NONE)
+ throw WrappedTargetException(
+ "[Service implementation " IMPL_NAME
+ "] XLayerHandler::setPropertyValue: jfw_setUserClassPath failed.", nullptr, Any());
+ break;
+ }
+ default:
+ OSL_ASSERT(false);
+ }
+}
+
+
+void SAL_CALL JavaMigration::setPropertyValueForLocale(
+ const Any&,
+ const OUString& )
+{
+}
+
+
+void SAL_CALL JavaMigration::endProperty()
+{
+ if (!m_aStack.empty())
+ m_aStack.pop();
+}
+
+
+void SAL_CALL JavaMigration::addProperty(
+ const OUString&,
+ sal_Int16,
+ const Type& )
+{
+}
+
+
+void SAL_CALL JavaMigration::addPropertyWithValue(
+ const OUString&,
+ sal_Int16,
+ const Any& )
+{
+}
+
+void SAL_CALL JavaMigration::addOrReplaceNodeFromTemplate(
+ const OUString&,
+ const TemplateIdentifier&,
+ sal_Int16 )
+{
+}
+
+
+//ToDo enable java, user class path
+
+} //end namespace jfw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/jvmfwk.hxx b/desktop/source/migration/services/jvmfwk.hxx
new file mode 100644
index 0000000000..63ec7e7179
--- /dev/null
+++ b/desktop/source/migration/services/jvmfwk.hxx
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+namespace migration
+{
+OUString jvmfwk_getImplementationName();
+
+css::uno::Sequence<OUString> jvmfwk_getSupportedServiceNames();
+
+} //end blind namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/migrationoo2.component b/desktop/source/migration/services/migrationoo2.component
new file mode 100644
index 0000000000..2550235665
--- /dev/null
+++ b/desktop/source/migration/services/migrationoo2.component
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.desktop.migration.Basic"
+ constructor="desktop_BasicMigration_get_implementation">
+ <service name="com.sun.star.migration.Basic"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.desktop.migration.Wordbooks"
+ constructor="desktop_WordbookMigration_get_implementation">
+ <service name="com.sun.star.migration.Wordbooks"/>
+ </implementation>
+</component>
diff --git a/desktop/source/migration/services/migrationoo3.component b/desktop/source/migration/services/migrationoo3.component
new file mode 100644
index 0000000000..74432e586d
--- /dev/null
+++ b/desktop/source/migration/services/migrationoo3.component
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.desktop.migration.OOo3Extensions"
+ constructor="desktop_OO3ExtensionMigration_get_implementation">
+ <service name="com.sun.star.migration.Extensions"/>
+ </implementation>
+</component>
diff --git a/desktop/source/migration/services/misc.hxx b/desktop/source/migration/services/misc.hxx
new file mode 100644
index 0000000000..c3b83b82e6
--- /dev/null
+++ b/desktop/source/migration/services/misc.hxx
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+#include <vector>
+#include <memory>
+
+
+namespace migration
+{
+
+
+ typedef std::vector< OUString > TStringVector;
+ typedef std::unique_ptr< TStringVector > TStringVectorPtr;
+
+
+} // namespace migration
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/oo3extensionmigration.cxx b/desktop/source/migration/services/oo3extensionmigration.cxx
new file mode 100644
index 0000000000..174f82ec69
--- /dev/null
+++ b/desktop/source/migration/services/oo3extensionmigration.cxx
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "oo3extensionmigration.hxx"
+#include <sal/log.hxx>
+#include <osl/file.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/bootstrap.hxx>
+#include <unotools/textsearch.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/ref.hxx>
+
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/xml/xpath/XPathAPI.hpp>
+#include <com/sun/star/xml/xpath/XPathException.hpp>
+#include <com/sun/star/xml/dom/DOMException.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/XExtensionManager.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace migration
+{
+
+// ExtensionMigration
+
+
+OO3ExtensionMigration::OO3ExtensionMigration(Reference< XComponentContext > const & ctx) :
+m_ctx(ctx)
+{
+}
+
+
+OO3ExtensionMigration::~OO3ExtensionMigration()
+{
+}
+
+void OO3ExtensionMigration::scanUserExtensions( const OUString& sSourceDir, TStringVector& aMigrateExtensions )
+{
+ osl::Directory aScanRootDir( sSourceDir );
+ osl::FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL);
+ osl::FileBase::RC nRetCode = aScanRootDir.open();
+ if ( nRetCode != osl::Directory::E_None )
+ return;
+
+ sal_uInt32 nHint( 0 );
+ osl::DirectoryItem aItem;
+ while ( aScanRootDir.getNextItem( aItem, nHint ) == osl::Directory::E_None )
+ {
+ if (( aItem.getFileStatus(fs) == osl::FileBase::E_None ) &&
+ ( fs.getFileType() == osl::FileStatus::Directory ))
+ {
+ //Check next folder as the "real" extension folder is below a temp folder!
+ OUString sExtensionFolderURL = fs.getFileURL();
+
+ osl::Directory aExtensionRootDir( sExtensionFolderURL );
+
+ nRetCode = aExtensionRootDir.open();
+ if ( nRetCode == osl::Directory::E_None )
+ {
+ osl::DirectoryItem aExtDirItem;
+ while ( aExtensionRootDir.getNextItem( aExtDirItem, nHint ) == osl::Directory::E_None )
+ {
+ bool bFileStatus = aExtDirItem.getFileStatus(fs) == osl::FileBase::E_None;
+ bool bIsDir = fs.getFileType() == osl::FileStatus::Directory;
+
+ if ( bFileStatus && bIsDir )
+ {
+ sExtensionFolderURL = fs.getFileURL();
+ ScanResult eResult = scanExtensionFolder( sExtensionFolderURL );
+ if ( eResult == SCANRESULT_MIGRATE_EXTENSION )
+ aMigrateExtensions.push_back( sExtensionFolderURL );
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+OO3ExtensionMigration::ScanResult OO3ExtensionMigration::scanExtensionFolder( const OUString& sExtFolder )
+{
+ ScanResult aResult = SCANRESULT_NOTFOUND;
+ osl::Directory aDir(sExtFolder);
+
+ // get sub dirs
+ if (aDir.open() == osl::FileBase::E_None)
+ {
+ // work through directory contents...
+ osl::DirectoryItem item;
+ osl::FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL);
+ TStringVector aDirectories;
+ while ((aDir.getNextItem(item) == osl::FileBase::E_None ) &&
+ ( aResult == SCANRESULT_NOTFOUND ))
+ {
+ if (item.getFileStatus(fs) == osl::FileBase::E_None)
+ {
+ if (fs.getFileType() == osl::FileStatus::Directory)
+ aDirectories.push_back( fs.getFileURL() );
+ else
+ {
+ OUString aDirEntryURL = fs.getFileURL();
+ if ( aDirEntryURL.indexOf( "/description.xml" ) > 0 )
+ aResult = scanDescriptionXml( aDirEntryURL ) ? SCANRESULT_MIGRATE_EXTENSION : SCANRESULT_DONTMIGRATE_EXTENSION;
+ }
+ }
+ }
+
+ for (auto const& directory : aDirectories)
+ {
+ aResult = scanExtensionFolder(directory);
+ if (aResult != SCANRESULT_NOTFOUND)
+ break;
+ }
+ }
+ return aResult;
+}
+
+bool OO3ExtensionMigration::scanDescriptionXml( const OUString& sDescriptionXmlURL )
+{
+ if ( !m_xDocBuilder.is() )
+ {
+ m_xDocBuilder.set( xml::dom::DocumentBuilder::create(m_ctx) );
+ }
+
+ if ( !m_xSimpleFileAccess.is() )
+ {
+ m_xSimpleFileAccess = ucb::SimpleFileAccess::create(m_ctx);
+ }
+
+ OUString aExtIdentifier;
+ try
+ {
+ uno::Reference< io::XInputStream > xIn =
+ m_xSimpleFileAccess->openFileRead( sDescriptionXmlURL );
+
+ if ( xIn.is() )
+ {
+ uno::Reference< xml::dom::XDocument > xDoc = m_xDocBuilder->parse( xIn );
+ if ( xDoc.is() )
+ {
+ uno::Reference< xml::dom::XElement > xRoot = xDoc->getDocumentElement();
+ if ( xRoot.is() && xRoot->getTagName() == "description" )
+ {
+ uno::Reference< xml::xpath::XXPathAPI > xPath = xml::xpath::XPathAPI::create(m_ctx);
+
+ xPath->registerNS("desc", xRoot->getNamespaceURI());
+ xPath->registerNS("xlink", "http://www.w3.org/1999/xlink");
+
+ try
+ {
+ uno::Reference< xml::dom::XNode > xNode(
+ xPath->selectSingleNode(
+ xRoot, "desc:identifier/@value" ));
+ if ( xNode.is() )
+ aExtIdentifier = xNode->getNodeValue();
+ }
+ catch ( const xml::xpath::XPathException& )
+ {
+ }
+ catch ( const xml::dom::DOMException& )
+ {
+ }
+ }
+ }
+ }
+
+ if ( !aExtIdentifier.isEmpty() )
+ {
+ // scan extension identifier and try to match with our black list entries
+ for (const OUString & i : m_aDenyList)
+ {
+ utl::SearchParam param(i, utl::SearchParam::SearchType::Regexp);
+ utl::TextSearch ts(param, LANGUAGE_DONTKNOW);
+
+ sal_Int32 start = 0;
+ sal_Int32 end = aExtIdentifier.getLength();
+ if (ts.SearchForward(aExtIdentifier, &start, &end))
+ return false;
+ }
+ }
+ }
+ catch ( const ucb::CommandAbortedException& )
+ {
+ }
+ catch ( const uno::RuntimeException& )
+ {
+ }
+
+ if ( aExtIdentifier.isEmpty() )
+ {
+ // Fallback:
+ // Try to use the folder name to match our black list
+ // as some extensions don't provide an identifier in the
+ // description.xml!
+ for (const OUString & i : m_aDenyList)
+ {
+ utl::SearchParam param(i, utl::SearchParam::SearchType::Regexp);
+ utl::TextSearch ts(param, LANGUAGE_DONTKNOW);
+
+ sal_Int32 start = 0;
+ sal_Int32 end = sDescriptionXmlURL.getLength();
+ if (ts.SearchForward(sDescriptionXmlURL, &start, &end))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void OO3ExtensionMigration::migrateExtension( const OUString& sSourceDir )
+{
+ css::uno::Reference< css::deployment::XExtensionManager > extMgr(
+ deployment::ExtensionManager::get( m_ctx ) );
+ try
+ {
+ rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv();
+
+ uno::Reference< task::XAbortChannel > xAbortChannel;
+ extMgr->addExtension(
+ sSourceDir, uno::Sequence<beans::NamedValue>(), "user",
+ xAbortChannel, pCmdEnv );
+ }
+ catch ( css::uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.migration",
+ "Ignoring UNO Exception while migrating extension from <" << sSourceDir << ">");
+ }
+}
+
+
+// XServiceInfo
+
+
+OUString OO3ExtensionMigration::getImplementationName()
+{
+ return "com.sun.star.comp.desktop.migration.OOo3Extensions";
+}
+
+
+sal_Bool OO3ExtensionMigration::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+
+Sequence< OUString > OO3ExtensionMigration::getSupportedServiceNames()
+{
+ return { "com.sun.star.migration.Extensions" };
+}
+
+
+// XInitialization
+
+
+void OO3ExtensionMigration::initialize( const Sequence< Any >& aArguments )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ const Any* pIter = aArguments.getConstArray();
+ const Any* pEnd = pIter + aArguments.getLength();
+ for ( ; pIter != pEnd ; ++pIter )
+ {
+ beans::NamedValue aValue;
+ *pIter >>= aValue;
+ if ( aValue.Name == "UserData" )
+ {
+ if ( !(aValue.Value >>= m_sSourceDir) )
+ {
+ OSL_FAIL( "ExtensionMigration::initialize: argument UserData has wrong type!" );
+ }
+ }
+ else if ( aValue.Name == "ExtensionDenyList" )
+ {
+ Sequence< OUString > aDenyList;
+ if ( (aValue.Value >>= aDenyList ) && aDenyList.hasElements())
+ {
+ m_aDenyList.resize( aDenyList.getLength() );
+ ::comphelper::sequenceToArray< OUString >( m_aDenyList.data(), aDenyList );
+ }
+ }
+ }
+}
+
+Any OO3ExtensionMigration::execute( const Sequence< beans::NamedValue >& )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( m_sTargetDir );
+ if ( aStatus == ::utl::Bootstrap::PATH_EXISTS )
+ {
+ // copy all extensions
+ OUString sSourceDir = m_sSourceDir +
+ "/user/uno_packages/cache/uno_packages";
+ TStringVector aExtensionToMigrate;
+ scanUserExtensions( sSourceDir, aExtensionToMigrate );
+ for (auto const& extensionToMigrate : aExtensionToMigrate)
+ {
+ migrateExtension(extensionToMigrate);
+ }
+ }
+
+ return Any();
+}
+
+
+// TmpRepositoryCommandEnv
+
+
+TmpRepositoryCommandEnv::TmpRepositoryCommandEnv()
+{
+}
+
+TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv()
+{
+}
+// XCommandEnvironment
+
+uno::Reference< task::XInteractionHandler > TmpRepositoryCommandEnv::getInteractionHandler()
+{
+ return this;
+}
+
+
+uno::Reference< ucb::XProgressHandler > TmpRepositoryCommandEnv::getProgressHandler()
+{
+ return this;
+}
+
+// XInteractionHandler
+void TmpRepositoryCommandEnv::handle(
+ uno::Reference< task::XInteractionRequest> const & xRequest )
+{
+ OSL_ASSERT( xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION );
+
+ bool approve = true;
+
+ // select:
+ uno::Sequence< Reference< task::XInteractionContinuation > > conts(
+ xRequest->getContinuations() );
+ Reference< task::XInteractionContinuation > const * pConts =
+ conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if (approve) {
+ uno::Reference< task::XInteractionApprove > xInteractionApprove(
+ pConts[ pos ], uno::UNO_QUERY );
+ if (xInteractionApprove.is()) {
+ xInteractionApprove->select();
+ // don't query again for ongoing continuations:
+ approve = false;
+ }
+ }
+ }
+}
+
+// XProgressHandler
+void TmpRepositoryCommandEnv::push( uno::Any const & /*Status*/ )
+{
+}
+
+
+void TmpRepositoryCommandEnv::update( uno::Any const & /*Status */)
+{
+}
+
+void TmpRepositoryCommandEnv::pop()
+{
+}
+
+
+} // namespace migration
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_OO3ExtensionMigration_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new migration::OO3ExtensionMigration(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/oo3extensionmigration.hxx b/desktop/source/migration/services/oo3extensionmigration.hxx
new file mode 100644
index 0000000000..586e7e99e3
--- /dev/null
+++ b/desktop/source/migration/services/oo3extensionmigration.hxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "misc.hxx"
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/xml/dom/XDocumentBuilder.hpp>
+#include <com/sun/star/ucb/XSimpleFileAccess3.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+
+#include <osl/mutex.hxx>
+#include <cppuhelper/implbase.hxx>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+class INetURLObject;
+
+
+namespace migration
+{
+
+ typedef ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XInitialization,
+ css::task::XJob > ExtensionMigration_BASE;
+
+ class OO3ExtensionMigration : public ExtensionMigration_BASE
+ {
+ private:
+ css::uno::Reference< css::uno::XComponentContext > m_ctx;
+ css::uno::Reference< css::xml::dom::XDocumentBuilder > m_xDocBuilder;
+ css::uno::Reference< css::ucb::XSimpleFileAccess3 > m_xSimpleFileAccess;
+ ::osl::Mutex m_aMutex;
+ OUString m_sSourceDir;
+ OUString m_sTargetDir;
+ TStringVector m_aDenyList;
+
+ enum ScanResult
+ {
+ SCANRESULT_NOTFOUND,
+ SCANRESULT_MIGRATE_EXTENSION,
+ SCANRESULT_DONTMIGRATE_EXTENSION
+ };
+
+ ScanResult scanExtensionFolder( const OUString& sExtFolder );
+ void scanUserExtensions( const OUString& sSourceDir, TStringVector& aMigrateExtensions );
+ bool scanDescriptionXml( const OUString& sDescriptionXmlFilePath );
+ void migrateExtension( const OUString& sSourceDir );
+
+ public:
+ explicit OO3ExtensionMigration(css::uno::Reference<
+ css::uno::XComponentContext > const & ctx);
+ virtual ~OO3ExtensionMigration() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XJob
+ virtual css::uno::Any SAL_CALL execute(
+ const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override;
+ };
+
+ class TmpRepositoryCommandEnv
+ : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
+ css::task::XInteractionHandler,
+ css::ucb::XProgressHandler >
+ {
+ public:
+ virtual ~TmpRepositoryCommandEnv() override;
+ TmpRepositoryCommandEnv();
+
+ // XCommandEnvironment
+ virtual css::uno::Reference< css::task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual css::uno::Reference< css::ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ css::uno::Reference< css::task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL update( css::uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+ };
+
+
+} // namespace migration
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/wordbookmigration.cxx b/desktop/source/migration/services/wordbookmigration.cxx
new file mode 100644
index 0000000000..a3fff88239
--- /dev/null
+++ b/desktop/source/migration/services/wordbookmigration.cxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "wordbookmigration.hxx"
+#include <cppuhelper/supportsservice.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/bootstrap.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <sal/log.hxx>
+#include <osl/file.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+namespace migration
+{
+ WordbookMigration::WordbookMigration()
+ {
+ }
+
+
+ WordbookMigration::~WordbookMigration()
+ {
+ }
+
+
+ TStringVectorPtr WordbookMigration::getFiles( const OUString& rBaseURL ) const
+ {
+ TStringVectorPtr aResult( new TStringVector );
+ ::osl::Directory aDir( rBaseURL);
+
+ if ( aDir.open() == ::osl::FileBase::E_None )
+ {
+ // iterate over directory content
+ TStringVector aSubDirs;
+ ::osl::DirectoryItem aItem;
+ while ( aDir.getNextItem( aItem ) == ::osl::FileBase::E_None )
+ {
+ ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL );
+ if ( aItem.getFileStatus( aFileStatus ) == ::osl::FileBase::E_None )
+ {
+ if ( aFileStatus.getFileType() == ::osl::FileStatus::Directory )
+ aSubDirs.push_back( aFileStatus.getFileURL() );
+ else
+ aResult->push_back( aFileStatus.getFileURL() );
+ }
+ }
+
+ // iterate recursive over subfolders
+ for (auto const& subDir : aSubDirs)
+ {
+ TStringVectorPtr aSubResult = getFiles(subDir);
+ aResult->insert( aResult->end(), aSubResult->begin(), aSubResult->end() );
+ }
+ }
+
+ return aResult;
+ }
+
+
+ void WordbookMigration::checkAndCreateDirectory( INetURLObject const & rDirURL )
+ {
+ ::osl::FileBase::RC aResult = ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) );
+ if ( aResult == ::osl::FileBase::E_NOENT )
+ {
+ INetURLObject aBaseURL( rDirURL );
+ aBaseURL.removeSegment();
+ checkAndCreateDirectory( aBaseURL );
+ ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) );
+ }
+ }
+
+#define MAX_HEADER_LENGTH 16
+static bool IsUserWordbook( const OUString& rFile )
+{
+ bool bRet = false;
+ std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( rFile, StreamMode::STD_READ );
+ if ( pStream && !pStream->GetError() )
+ {
+ static const char* const pVerOOo7 = "OOoUserDict1";
+ sal_uInt64 const nSniffPos = pStream->Tell();
+ static std::size_t nVerOOo7Len = sal::static_int_cast< std::size_t >(strlen( pVerOOo7 ));
+ char pMagicHeader[MAX_HEADER_LENGTH];
+ pMagicHeader[ nVerOOo7Len ] = '\0';
+ if (pStream->ReadBytes(static_cast<void *>(pMagicHeader), nVerOOo7Len) == nVerOOo7Len)
+ {
+ if ( !strcmp(pMagicHeader, pVerOOo7) )
+ bRet = true;
+ else
+ {
+ sal_uInt16 nLen;
+ pStream->Seek (nSniffPos);
+ pStream->ReadUInt16( nLen );
+ if ( nLen < MAX_HEADER_LENGTH )
+ {
+ pStream->ReadBytes(pMagicHeader, nLen);
+ pMagicHeader[nLen] = '\0';
+ if ( !strcmp(pMagicHeader, "WBSWG2")
+ || !strcmp(pMagicHeader, "WBSWG5")
+ || !strcmp(pMagicHeader, "WBSWG6") )
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+ void WordbookMigration::copyFiles()
+ {
+ OUString sTargetDir;
+ ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( sTargetDir );
+ if ( aStatus == ::utl::Bootstrap::PATH_EXISTS )
+ {
+ sTargetDir += "/user/wordbook";
+ TStringVectorPtr aFileList = getFiles( m_sSourceDir );
+ for (auto const& elem : *aFileList)
+ {
+ if (IsUserWordbook(elem) )
+ {
+ std::u16string_view sSourceLocalName = elem.subView( m_sSourceDir.getLength() );
+ OUString sTargetName = sTargetDir + sSourceLocalName;
+ INetURLObject aURL( sTargetName );
+ aURL.removeSegment();
+ checkAndCreateDirectory( aURL );
+ ::osl::FileBase::RC aResult = ::osl::File::copy( elem, sTargetName );
+ if ( aResult != ::osl::FileBase::E_None )
+ {
+ SAL_WARN( "desktop", "WordbookMigration::copyFiles: cannot copy "
+ << elem << " to " << sTargetName);
+ }
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "WordbookMigration::copyFiles: no user installation!" );
+ }
+ }
+
+
+ // XServiceInfo
+
+
+ OUString WordbookMigration::getImplementationName()
+ {
+ return "com.sun.star.comp.desktop.migration.Wordbooks";
+ }
+
+
+ sal_Bool WordbookMigration::supportsService(OUString const & ServiceName)
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+
+ Sequence< OUString > WordbookMigration::getSupportedServiceNames()
+ {
+ return { "com.sun.star.migration.Wordbooks" };
+ }
+
+
+ // XInitialization
+
+
+ void WordbookMigration::initialize( const Sequence< Any >& aArguments )
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ const Any* pIter = aArguments.getConstArray();
+ const Any* pEnd = pIter + aArguments.getLength();
+ for ( ; pIter != pEnd ; ++pIter )
+ {
+ beans::NamedValue aValue;
+ *pIter >>= aValue;
+ if ( aValue.Name == "UserData" )
+ {
+ if ( !(aValue.Value >>= m_sSourceDir) )
+ {
+ OSL_FAIL( "WordbookMigration::initialize: argument UserData has wrong type!" );
+ }
+ m_sSourceDir += "/user/wordbook";
+ break;
+ }
+ }
+ }
+
+
+ // XJob
+
+
+ Any WordbookMigration::execute( const Sequence< beans::NamedValue >& )
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ copyFiles();
+
+ return Any();
+ }
+
+} // namespace migration
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_WordbookMigration_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new migration::WordbookMigration());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/migration/services/wordbookmigration.hxx b/desktop/source/migration/services/wordbookmigration.hxx
new file mode 100644
index 0000000000..a6388fa421
--- /dev/null
+++ b/desktop/source/migration/services/wordbookmigration.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "misc.hxx"
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <osl/mutex.hxx>
+
+
+class INetURLObject;
+
+
+namespace migration
+{
+
+ typedef ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XInitialization,
+ css::task::XJob > WordbookMigration_BASE;
+
+ class WordbookMigration : public WordbookMigration_BASE
+ {
+ private:
+ ::osl::Mutex m_aMutex;
+ OUString m_sSourceDir;
+
+ TStringVectorPtr getFiles( const OUString& rBaseURL ) const;
+ void checkAndCreateDirectory( INetURLObject const & rDirURL );
+ void copyFiles();
+
+ public:
+ WordbookMigration();
+ virtual ~WordbookMigration() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XJob
+ virtual css::uno::Any SAL_CALL execute(
+ const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override;
+ };
+
+
+} // namespace migration
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/minidump/minidump.cxx b/desktop/source/minidump/minidump.cxx
new file mode 100644
index 0000000000..0a31fff6f2
--- /dev/null
+++ b/desktop/source/minidump/minidump.cxx
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <desktop/minidump.hxx>
+#include <sal/log.hxx>
+
+#include <map>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <curl/curl.h>
+
+#include <systools/curlinit.hxx>
+
+#ifdef _WIN32
+#include <memory>
+#include <windows.h>
+#endif
+
+const char kUserAgent[] = "Breakpad/1.0 (Linux)";
+
+static std::map<std::string, std::string> readStrings(std::istream& file)
+{
+ std::map<std::string, std::string> parameters;
+
+ // when file is not readable, the status eof would not be set
+ // better test of state is okay
+ while (file)
+ {
+ std::string line;
+ std::getline(file, line);
+ int sep = line.find('=');
+ if (sep >= 0)
+ {
+ std::string key = line.substr(0, sep);
+ std::string value = line.substr(sep + 1);
+ parameters[key] = value;
+ }
+ }
+
+ return parameters;
+}
+
+// Callback to get the response data from server.
+static size_t WriteCallback(void const *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ std::string* response = static_cast<std::string *>(userp);
+ size_t real_size = size * nmemb;
+ response->append(static_cast<char const *>(ptr), real_size);
+ return real_size;
+}
+
+static void getProperty(const std::string& key, std::string& value,
+ std::map<std::string, std::string>& parameters)
+{
+ auto itr = parameters.find(key);
+ if (itr != parameters.end())
+ {
+ value = itr->second;
+ parameters.erase(itr);
+ }
+}
+
+static std::string generate_json(const std::map<std::string, std::string>& parameters)
+{
+ std::ostringstream stream;
+ stream << "{\n";
+ bool first = true;
+ for (auto itr = parameters.begin(), itrEnd = parameters.end(); itr != itrEnd; ++itr)
+ {
+ if (!first)
+ {
+ stream << ",\n";
+ }
+ first = false;
+ stream << "\"" << itr->first << "\": \"" << itr->second << "\"";
+ }
+ stream << "\n}";
+
+ return stream.str();
+}
+
+static bool uploadContent(std::map<std::string, std::string>& parameters, std::string& response)
+{
+ CURL* curl = curl_easy_init();
+ if (!curl)
+ return false;
+
+ ::InitCurl_easy(curl);
+
+ std::string proxy, proxy_user_pwd, ca_certificate_file, file, url, version;
+
+ getProperty("Proxy", proxy, parameters);
+ getProperty("ProxyUserPW", proxy_user_pwd, parameters);
+ getProperty("CAFile", ca_certificate_file, parameters);
+
+ getProperty("DumpFile", file, parameters);
+ getProperty("URL", url, parameters);
+ getProperty("Version", version, parameters);
+ if (url.empty())
+ return false;
+
+ if (file.empty())
+ return false;
+
+ if (version.empty())
+ return false;
+
+ curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
+ // Set proxy information if necessary.
+ if (!proxy.empty())
+ {
+ curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
+
+ curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE);
+
+ if (!proxy_user_pwd.empty())
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
+ else
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, ":");
+ }
+
+ if (!ca_certificate_file.empty())
+ curl_easy_setopt(curl, CURLOPT_CAINFO, ca_certificate_file.c_str());
+
+ curl_mime* mime = curl_mime_init(curl);
+ std::string additional_data = generate_json(parameters);
+ curl_mimepart* part = curl_mime_addpart(mime);
+ curl_mime_name(part, "AdditionalData");
+ curl_mime_data(part, additional_data.c_str(), CURL_ZERO_TERMINATED);
+
+ part = curl_mime_addpart(mime);
+ curl_mime_name(part, "Version");
+ curl_mime_data(part, version.c_str(), CURL_ZERO_TERMINATED);
+
+ part = curl_mime_addpart(mime);
+ curl_mime_name(part, "upload_file_minidump");
+ curl_mime_filedata(part, file.c_str());
+
+ curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
+
+
+ // Disable 100-continue header.
+ char buf[] = "Expect:";
+ curl_slist* headerlist = nullptr;
+ headerlist = curl_slist_append(headerlist, buf);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
+
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
+ std::string response_body;
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA,
+ static_cast<void *>(&response_body));
+
+ // Fail if 400+ is returned from the web server.
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+
+ CURLcode cc = curl_easy_perform(curl);
+ long response_code;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+ SAL_WARN_IF(cc != CURLE_OK, "desktop",
+ "Failed to send http request to " <<
+ url.c_str() <<
+ ", error: " <<
+ curl_easy_strerror(cc));
+
+ if (headerlist != nullptr)
+ {
+ curl_slist_free_all(headerlist);
+ }
+
+ response = response_body;
+
+ if( CURLE_OK != cc )
+ return false;
+
+ return true;
+}
+
+namespace crashreport {
+
+bool readConfig(const std::string& iniPath, std::string * response)
+{
+#if defined _WIN32
+ std::wstring iniPathW;
+ const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
+ auto buf = std::make_unique<wchar_t[]>(nChars);
+ if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
+ iniPathW = buf.get();
+
+ std::ifstream file = iniPathW.empty() ? std::ifstream(iniPath) : std::ifstream(iniPathW);
+#else
+ std::ifstream file(iniPath);
+#endif
+ std::map<std::string, std::string> parameters = readStrings(file);
+
+ // make sure that at least the mandatory parameters are in there
+ if (parameters.find("DumpFile") == parameters.end())
+ {
+ if(response != nullptr)
+ *response = "ini file needs to contain a key DumpFile!";
+ return false;
+ }
+
+ if (parameters.find("Version") == parameters.end())
+ {
+ if (response != nullptr)
+ *response = "ini file needs to contain a key Version!";
+ return false;
+ }
+
+ if (parameters.find("URL") == parameters.end())
+ {
+ if (response != nullptr)
+ *response = "ini file needs to contain a key URL!";
+ return false;
+ }
+
+ if (response != nullptr)
+ return uploadContent(parameters, *response);
+
+ return true;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/minidump/minidump_upload.cxx b/desktop/source/minidump/minidump_upload.cxx
new file mode 100644
index 0000000000..0434fda684
--- /dev/null
+++ b/desktop/source/minidump/minidump_upload.cxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <desktop/minidump.hxx>
+
+#include <iostream>
+#include <string>
+
+#ifdef _WIN32
+#include <memory>
+#include <windows.h>
+
+int wmain(int argc, wchar_t** argv)
+#else
+int main(int argc, char** argv)
+#endif
+{
+ if (argc < 2)
+ {
+ std::cerr << "minidump_upload path_to_ini_file" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+#ifdef _WIN32
+ const int nBytes = WideCharToMultiByte(CP_UTF8, 0, argv[1], -1, nullptr, 0, nullptr, nullptr);
+ auto buf = std::make_unique<char[]>(nBytes);
+ if (WideCharToMultiByte(CP_UTF8, 0, argv[1], -1, buf.get(), nBytes, nullptr, nullptr) == 0)
+ return EXIT_FAILURE;
+ std::string iniPath(buf.get());
+#else
+ std::string iniPath(argv[1]);
+#endif
+ std::string response;
+ if (!crashreport::readConfig(iniPath, &response))
+ return EXIT_FAILURE;
+
+ std::cout << "Response: " << response << std::endl;
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/offacc/acceptor.cxx b/desktop/source/offacc/acceptor.cxx
new file mode 100644
index 0000000000..9598466d9c
--- /dev/null
+++ b/desktop/source/offacc/acceptor.cxx
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include "acceptor.hxx"
+#include <com/sun/star/bridge/BridgeFactory.hpp>
+#include <com/sun/star/connection/Acceptor.hpp>
+#include <com/sun/star/uno/XNamingService.hpp>
+#include <officecfg/Office/Security.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace css::bridge;
+using namespace css::connection;
+using namespace css::lang;
+using namespace css::uno;
+
+namespace desktop
+{
+
+extern "C" {
+
+static void offacc_workerfunc (void * acc)
+{
+ osl_setThreadName("URP Acceptor");
+
+ static_cast<Acceptor*>(acc)->run();
+}
+
+}
+
+Acceptor::Acceptor( const Reference< XComponentContext >& rxContext )
+ : m_thread(nullptr)
+ , m_rContext(rxContext)
+ , m_bInit(false)
+ , m_bDying(false)
+{
+ m_rAcceptor = css::connection::Acceptor::create(m_rContext);
+ m_rBridgeFactory = BridgeFactory::create(m_rContext);
+}
+
+
+Acceptor::~Acceptor()
+{
+ m_rAcceptor->stopAccepting();
+ oslThread t;
+ {
+ std::unique_lock g(m_aMutex);
+ t = m_thread;
+ }
+ //prevent locking if the thread is still waiting
+ m_bDying = true;
+ m_cEnable.set();
+ osl_joinWithThread(t);
+ osl_destroyThread(t);
+ {
+ // Make the final state of m_bridges visible to this thread (since
+ // m_thread is joined, the code that follows is the only one left
+ // accessing m_bridges):
+ std::unique_lock g(m_aMutex);
+ }
+ for (;;) {
+ css::uno::Reference< css::bridge::XBridge > b(m_bridges.remove());
+ if (!b.is()) {
+ break;
+ }
+ css::uno::Reference< css::lang::XComponent >(
+ b, css::uno::UNO_QUERY_THROW)->dispose();
+ }
+}
+
+void Acceptor::run()
+{
+ SAL_INFO( "desktop.offacc", "Acceptor::run" );
+ for (;;)
+ {
+ try
+ {
+ // wait until we get enabled
+ SAL_INFO( "desktop.offacc",
+ "Acceptor::run waiting for office to come up");
+ m_cEnable.wait();
+ if (m_bDying) //see destructor
+ break;
+ SAL_INFO( "desktop.offacc",
+ "Acceptor::run now enabled and continuing");
+
+ // accept connection
+ Reference< XConnection > rConnection = m_rAcceptor->accept( m_aConnectString );
+ // if we return without a valid connection we must assume that the acceptor
+ // is destructed so we break out of the run method terminating the thread
+ if (! rConnection.is()) break;
+ OUString aDescription = rConnection->getDescription();
+ SAL_INFO( "desktop.offacc", "Acceptor::run connection " << aDescription );
+
+ // create instanceprovider for this connection
+ Reference< XInstanceProvider > rInstanceProvider(new AccInstanceProvider(m_rContext));
+ // create the bridge. The remote end will have a reference to this bridge
+ // thus preventing the bridge from being disposed. When the remote end releases
+ // the bridge, it will be destructed.
+ Reference< XBridge > rBridge = m_rBridgeFactory->createBridge(
+ "", m_aProtocol, rConnection, rInstanceProvider);
+ std::unique_lock g(m_aMutex);
+ m_bridges.add(rBridge);
+ } catch (const Exception&) {
+ TOOLS_WARN_EXCEPTION("desktop.offacc", "");
+ // connection failed...
+ // something went wrong during connection setup.
+ // just wait for a new connection to accept
+ }
+ }
+}
+
+// XInitialize
+void Acceptor::initialize( const Sequence<Any>& aArguments )
+{
+ // prevent multiple initialization
+ std::unique_lock aGuard( m_aMutex );
+ SAL_INFO( "desktop.offacc", "Acceptor::initialize()" );
+
+ bool bOk = false;
+
+ // arg count
+ int nArgs = aArguments.getLength();
+
+ // not yet initialized and accept-string
+ if (!m_bInit && nArgs > 0 && (aArguments[0] >>= m_aAcceptString))
+ {
+ SAL_INFO( "desktop.offacc", "Acceptor::initialize string=" << m_aAcceptString );
+
+ // get connect string and protocol from accept string
+ // "<connectString>;<protocol>"
+ sal_Int32 nIndex1 = m_aAcceptString.indexOf( ';' );
+ if (nIndex1 < 0)
+ throw IllegalArgumentException(
+ "Invalid accept-string format", m_rContext, 1);
+ m_aConnectString = o3tl::trim(m_aAcceptString.subView( 0 , nIndex1 ));
+ nIndex1++;
+ sal_Int32 nIndex2 = m_aAcceptString.indexOf( ';' , nIndex1 );
+ if (nIndex2 < 0) nIndex2 = m_aAcceptString.getLength();
+ m_aProtocol = m_aAcceptString.copy( nIndex1, nIndex2 - nIndex1 );
+
+ // start accepting in new thread...
+ m_thread = osl_createThread(offacc_workerfunc, this);
+ m_bInit = true;
+ bOk = true;
+ }
+
+ // do we want to enable accepting?
+ bool bEnable = false;
+ if (((nArgs == 1 && (aArguments[0] >>= bEnable)) ||
+ (nArgs == 2 && (aArguments[1] >>= bEnable))) &&
+ bEnable )
+ {
+ m_cEnable.set();
+ bOk = true;
+ }
+
+ if (!bOk)
+ {
+ throw IllegalArgumentException( "invalid initialization", m_rContext, 1);
+ }
+}
+
+// XServiceInfo
+OUString Acceptor::getImplementationName()
+{
+ return "com.sun.star.office.comp.Acceptor";
+}
+Sequence<OUString> Acceptor::getSupportedServiceNames()
+{
+ return { "com.sun.star.office.Acceptor" };
+}
+
+sal_Bool Acceptor::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+
+// InstanceProvider
+AccInstanceProvider::AccInstanceProvider(const Reference<XComponentContext>& rxContext)
+ : m_rContext(rxContext)
+{
+}
+
+AccInstanceProvider::~AccInstanceProvider()
+{
+}
+
+Reference<XInterface> AccInstanceProvider::getInstance (const OUString& aName )
+{
+
+ Reference<XInterface> rInstance;
+
+ if ( aName == "StarOffice.ServiceManager" )
+ {
+ rInstance.set( m_rContext->getServiceManager() );
+ }
+ else if ( aName == "StarOffice.ComponentContext" )
+ {
+ rInstance = m_rContext;
+ }
+ else if ( aName == "StarOffice.NamingService" )
+ {
+ Reference< XNamingService > rNamingService(
+ m_rContext->getServiceManager()->createInstanceWithContext("com.sun.star.uno.NamingService", m_rContext),
+ UNO_QUERY );
+ if ( rNamingService.is() )
+ {
+ rNamingService->registerObject( "StarOffice.ServiceManager", m_rContext->getServiceManager() );
+ rNamingService->registerObject( "StarOffice.ComponentContext", m_rContext );
+ rInstance = rNamingService;
+ }
+ }
+ return rInstance;
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_Acceptor_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ if (!officecfg::Office::Security::Net::AllowInsecureUNORemoteProtocol::get())
+ {
+ // this is not allowed to throw
+ SAL_WARN("desktop", "UNO Remote Protocol is disabled by configuration");
+ return nullptr;
+ }
+ return cppu::acquire(new desktop::Acceptor(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/offacc/acceptor.hxx b/desktop/source/offacc/acceptor.hxx
new file mode 100644
index 0000000000..9e210459a5
--- /dev/null
+++ b/desktop/source/offacc/acceptor.hxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/connection/XAcceptor.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/bridge/XInstanceProvider.hpp>
+#include <com/sun/star/bridge/XBridgeFactory2.hpp>
+#include <cppuhelper/implbase.hxx>
+
+#include <comphelper/weakbag.hxx>
+#include <osl/conditn.hxx>
+#include <osl/thread.hxx>
+
+#include <mutex>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+namespace desktop {
+
+class Acceptor
+ : public ::cppu::WeakImplHelper<css::lang::XServiceInfo, css::lang::XInitialization>
+{
+private:
+ std::mutex m_aMutex;
+
+ oslThread m_thread;
+ comphelper::WeakBag< css::bridge::XBridge > m_bridges;
+
+ ::osl::Condition m_cEnable;
+
+ css::uno::Reference< css::uno::XComponentContext > m_rContext;
+ css::uno::Reference< css::connection::XAcceptor > m_rAcceptor;
+ css::uno::Reference< css::bridge::XBridgeFactory2 > m_rBridgeFactory;
+
+ OUString m_aAcceptString;
+ OUString m_aConnectString;
+ OUString m_aProtocol;
+
+ bool m_bInit;
+ bool m_bDying;
+
+public:
+ explicit Acceptor( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+ virtual ~Acceptor() override;
+
+ void run();
+
+ // XService info
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& aName ) override;
+
+ // XInitialize
+ virtual void SAL_CALL initialize( const css::uno::Sequence<css::uno::Any>& aArguments ) override;
+};
+
+class AccInstanceProvider : public ::cppu::WeakImplHelper<css::bridge::XInstanceProvider>
+{
+private:
+ css::uno::Reference<css::uno::XComponentContext> m_rContext;
+
+public:
+ AccInstanceProvider(const css::uno::Reference< css::uno::XComponentContext >& rxContext);
+ virtual ~AccInstanceProvider() override;
+
+ // XInstanceProvider
+ virtual css::uno::Reference<css::uno::XInterface> SAL_CALL getInstance (const OUString& aName ) override;
+};
+
+
+} //namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/offacc/offacc.component b/desktop/source/offacc/offacc.component
new file mode 100644
index 0000000000..d46f8fac7c
--- /dev/null
+++ b/desktop/source/offacc/offacc.component
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.office.comp.Acceptor"
+ constructor="desktop_Acceptor_get_implementation">
+ <service name="com.sun.star.office.Acceptor"/>
+ </implementation>
+</component>
diff --git a/desktop/source/pkgchk/unopkg/unopkg_app.cxx b/desktop/source/pkgchk/unopkg/unopkg_app.cxx
new file mode 100644
index 0000000000..0ecb328b7c
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_app.cxx
@@ -0,0 +1,655 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <dp_misc.h>
+#include "unopkg_main.h"
+#include "unopkg_shared.h"
+#include <dp_identifier.hxx>
+#include <tools/extendapplicationenvironment.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/textenc.h>
+#include <rtl/ustring.hxx>
+#include <osl/process.h>
+#include <osl/conditn.hxx>
+#include <unotools/tempfile.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <comphelper/anytostring.hxx>
+#include <comphelper/logging.hxx>
+#include <comphelper/sequence.hxx>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+
+#include <com/sun/star/deployment/ui/PackageManagerDialog.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/logging/ConsoleHandler.hpp>
+#include <com/sun/star/logging/FileHandler.hpp>
+#include <com/sun/star/logging/LogLevel.hpp>
+#include <com/sun/star/logging/SimpleTextFormatter.hpp>
+#include <com/sun/star/logging/XLogger.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ui/dialogs/XDialogClosedListener.hpp>
+#if defined(UNX)
+ #include <unistd.h>
+#endif
+#include <iostream>
+#include <utility>
+#include <vector>
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::logging;
+using namespace ::com::sun::star::uno;
+using namespace ::unopkg;
+
+namespace {
+
+struct ExtensionName
+{
+ OUString m_str;
+ explicit ExtensionName( OUString str ) : m_str(std::move( str )) {}
+ bool operator () ( Reference<deployment::XPackage> const & e ) const
+ {
+ return m_str == dp_misc::getIdentifier(e)
+ || m_str == e->getName();
+ }
+};
+
+
+const char16_t s_usingText [] =
+u"\n"
+"using: " APP_NAME " add <options> extension-path...\n"
+" " APP_NAME " validate <options> extension-identifier...\n"
+" " APP_NAME " remove <options> extension-identifier...\n"
+" " APP_NAME " list <options> extension-identifier...\n"
+" " APP_NAME " reinstall <options>\n"
+" " APP_NAME " gui\n"
+" " APP_NAME " -V\n"
+" " APP_NAME " -h\n"
+"\n"
+"sub-commands:\n"
+" add add extension\n"
+" validate checks the prerequisites of an installed extension and\n"
+" registers it if possible\n"
+" remove remove extensions by identifier\n"
+" reinstall expert feature: reinstall all deployed extensions\n"
+" list list information about deployed extensions\n"
+" gui raise Extensions dialog\n"
+"\n"
+"options:\n"
+" -h, --help this help\n"
+" -V, --version version information\n"
+" -v, --verbose verbose output\n"
+" -f, --force force overwriting existing extensions\n"
+" -s, --suppress-license prevents showing the license\n"
+" --log-file <file> custom log file; default: <cache-dir>/log.txt\n"
+" --shared expert feature: operate on shared installation\n"
+" deployment context;\n"
+" run only when no concurrent Office\n"
+" process(es) are running!\n"
+" --bundled expert feature: operate on bundled extensions. Only\n"
+" works with list, validate, reinstall;\n"
+" --deployment-context expert feature: explicit deployment context\n"
+" <context>\n"
+"\n"
+"To learn more about extensions, see:\n"
+"https://wiki.documentfoundation.org/Documentation/DevGuide/Extensions\n\n";
+
+
+const OptionInfo s_option_infos [] = {
+ { RTL_CONSTASCII_STRINGPARAM("help"), 'h', false },
+ { RTL_CONSTASCII_STRINGPARAM("version"), 'V', false },
+ { RTL_CONSTASCII_STRINGPARAM("verbose"), 'v', false },
+ { RTL_CONSTASCII_STRINGPARAM("force"), 'f', false },
+ { RTL_CONSTASCII_STRINGPARAM("log-file"), '\0', true },
+ { RTL_CONSTASCII_STRINGPARAM("shared"), '\0', false },
+ { RTL_CONSTASCII_STRINGPARAM("deployment-context"), '\0', true },
+ { RTL_CONSTASCII_STRINGPARAM("bundled"), '\0', false},
+ { RTL_CONSTASCII_STRINGPARAM("suppress-license"), 's', false},
+
+ { nullptr, 0, '\0', false }
+};
+
+void logFatal(
+ comphelper::EventLogger const * logger, sal_Int32 level, OUString const & message,
+ OUString const & argument)
+{
+ if (logger == nullptr) {
+ // Best effort; potentially loses data due to conversion failures (stray surrogate halves)
+ // and embedded null characters:
+ std::cerr
+ << OUStringToOString(message.replaceFirst("$1$", argument), RTL_TEXTENCODING_UTF8)
+ << '\n';
+ } else {
+ logger->log(level, message, argument);
+ }
+}
+
+class DialogClosedListenerImpl :
+ public ::cppu::WeakImplHelper< ui::dialogs::XDialogClosedListener >
+{
+ osl::Condition & m_rDialogClosedCondition;
+
+public:
+ explicit DialogClosedListenerImpl( osl::Condition & rDialogClosedCondition )
+ : m_rDialogClosedCondition( rDialogClosedCondition ) {}
+
+ // XEventListener (base of XDialogClosedListener)
+ virtual void SAL_CALL disposing( lang::EventObject const & Source ) override;
+
+ // XDialogClosedListener
+ virtual void SAL_CALL dialogClosed(
+ ui::dialogs::DialogClosedEvent const & aEvent ) override;
+};
+
+// XEventListener (base of XDialogClosedListener)
+void DialogClosedListenerImpl::disposing( lang::EventObject const & )
+{
+ // nothing to do
+}
+
+// XDialogClosedListener
+void DialogClosedListenerImpl::dialogClosed(
+ ui::dialogs::DialogClosedEvent const & )
+{
+ m_rDialogClosedCondition.set();
+}
+
+// If a package had been installed with a pre OOo 2.2, it could not normally be
+// found via its identifier; similarly (and for ease of use), a package
+// installed with OOo 2.2 or later could not normally be found via its file
+// name.
+Reference<deployment::XPackage> findPackage(
+ OUString const & repository,
+ Reference<deployment::XExtensionManager> const & manager,
+ Reference<ucb::XCommandEnvironment > const & environment,
+ std::u16string_view idOrFileName )
+{
+ const Sequence< Reference<deployment::XPackage> > ps(
+ manager->getDeployedExtensions(repository,
+ Reference<task::XAbortChannel>(), environment ) );
+ for ( auto const & package : ps )
+ if ( dp_misc::getIdentifier( package ) == idOrFileName )
+ return package;
+ for ( auto const & package : ps )
+ if ( package->getName() == idOrFileName )
+ return package;
+ return Reference<deployment::XPackage>();
+}
+
+} // anon namespace
+
+extern "C" int unopkg_main()
+{
+ tools::extendApplicationEnvironment();
+ bool bShowFailedMsg = true;
+ OUString subCommand;
+ bool option_shared = false;
+ bool option_force = false;
+ bool option_verbose = false;
+ bool option_bundled = false;
+ bool option_suppressLicense = false;
+ bool option_help = false;
+ bool subcmd_gui = false;
+ OUString logFile;
+ OUString repository;
+ OUString cmdArg;
+ std::vector<OUString> cmdPackages;
+ Reference<XLogHandler> xFileHandler;
+ Reference<XLogHandler> xConsoleHandler;
+ std::unique_ptr<comphelper::EventLogger> logger;
+ std::unique_ptr<utl::TempFileNamed> pUserProfileTempDir;
+
+ OptionInfo const * info_shared = getOptionInfo(
+ s_option_infos, "shared" );
+ OptionInfo const * info_force = getOptionInfo(
+ s_option_infos, "force" );
+ OptionInfo const * info_verbose = getOptionInfo(
+ s_option_infos, "verbose" );
+ OptionInfo const * info_log = getOptionInfo(
+ s_option_infos, "log-file" );
+ OptionInfo const * info_context = getOptionInfo(
+ s_option_infos, "deployment-context" );
+ OptionInfo const * info_help = getOptionInfo(
+ s_option_infos, "help" );
+ OptionInfo const * info_version = getOptionInfo(
+ s_option_infos, "version" );
+ OptionInfo const * info_bundled = getOptionInfo(
+ s_option_infos, "bundled" );
+ OptionInfo const * info_suppressLicense = getOptionInfo(
+ s_option_infos, "suppress-license" );
+
+
+ Reference<XComponentContext> xComponentContext;
+ Reference<XComponentContext> xLocalComponentContext;
+
+ try {
+ sal_uInt32 nPos = 0;
+ sal_uInt32 nCount = osl_getCommandArgCount();
+ if (nCount == 0 || isOption( info_help, &nPos ))
+ {
+ dp_misc::writeConsole(s_usingText);
+ return 0;
+ }
+ else if (isOption( info_version, &nPos )) {
+ dp_misc::writeConsole(u"\n" APP_NAME " Version 3.3\n");
+ return 0;
+ }
+ //consume all bootstrap variables which may occur before the sub-command
+ while(isBootstrapVariable(&nPos))
+ ;
+
+ if(nPos >= nCount)
+ return 0;
+ //get the sub-command
+ osl_getCommandArg( nPos, &subCommand.pData );
+ ++nPos;
+ subCommand = subCommand.trim();
+ bool subcmd_add = subCommand == "add";
+ subcmd_gui = subCommand == "gui";
+
+ // sub-command options and packages:
+ while (nPos < nCount)
+ {
+ if (readArgument( &cmdArg, info_log, &nPos )) {
+ logFile = makeAbsoluteFileUrl(
+ cmdArg.trim(), getProcessWorkingDir() );
+ }
+ else if (!readOption( &option_verbose, info_verbose, &nPos ) &&
+ !readOption( &option_shared, info_shared, &nPos ) &&
+ !readOption( &option_force, info_force, &nPos ) &&
+ !readOption( &option_bundled, info_bundled, &nPos ) &&
+ !readOption( &option_suppressLicense, info_suppressLicense, &nPos ) &&
+ !readOption( &option_help, info_help, &nPos ) &&
+ !readArgument( &repository, info_context, &nPos ) &&
+ !isBootstrapVariable(&nPos))
+ {
+ osl_getCommandArg( nPos, &cmdArg.pData );
+ ++nPos;
+ cmdArg = cmdArg.trim();
+ if (!cmdArg.isEmpty())
+ {
+ if (cmdArg[ 0 ] == '-')
+ {
+ // is option:
+ dp_misc::writeConsoleError(Concat2View(
+ "\nERROR: unexpected option " +
+ cmdArg +
+ "!\n Use " APP_NAME " " +
+ toString(info_help) +
+ " to print all options.\n"));
+ return 1;
+ }
+ else
+ {
+ // is package:
+ cmdPackages.push_back(
+ subcmd_add || subcmd_gui
+ ? makeAbsoluteFileUrl(
+ cmdArg, getProcessWorkingDir() )
+ : cmdArg );
+ }
+ }
+ }
+ }
+
+ // tdf#129917 Use temp user profile when installing shared extensions
+ if (option_shared)
+ {
+ pUserProfileTempDir.reset(new utl::TempFileNamed(nullptr, true));
+ pUserProfileTempDir->EnableKillingFile();
+ }
+
+ xComponentContext = getUNO(option_verbose, subcmd_gui,
+ pUserProfileTempDir ? pUserProfileTempDir->GetURL() : "",
+ xLocalComponentContext);
+
+ // Initialize logging. This will log errors to the console and
+ // also to file if the --log-file parameter was provided.
+ logger.reset(new comphelper::EventLogger(xLocalComponentContext, "unopkg"));
+ const Reference<XLogger> xLogger(logger->getLogger());
+ xLogger->setLevel(LogLevel::WARNING);
+ Reference<XLogFormatter> xLogFormatter(SimpleTextFormatter::create(xLocalComponentContext));
+ Sequence < beans::NamedValue > aSeq { { "Formatter", Any(xLogFormatter) } };
+
+ xConsoleHandler.set(ConsoleHandler::createWithSettings(xLocalComponentContext, aSeq));
+ xLogger->addLogHandler(xConsoleHandler);
+ xConsoleHandler->setLevel(LogLevel::WARNING);
+ xLogger->setLevel(LogLevel::WARNING);
+
+
+ if (!logFile.isEmpty())
+ {
+ Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} };
+ xFileHandler.set(css::logging::FileHandler::createWithSettings(xLocalComponentContext, aSeq2));
+ xFileHandler->setLevel(LogLevel::WARNING);
+ xLogger->addLogHandler(xFileHandler);
+ }
+
+ if (option_verbose)
+ {
+ xLogger->setLevel(LogLevel::INFO);
+ xConsoleHandler->setLevel(LogLevel::INFO);
+ if (xFileHandler.is())
+ xFileHandler->setLevel(LogLevel::INFO);
+ }
+
+ if (repository.isEmpty())
+ {
+ if (option_shared)
+ repository = "shared";
+ else if (option_bundled)
+ repository = "bundled";
+ else
+ repository = "user";
+ }
+ else
+ {
+ if ( repository == "shared" ) {
+ option_shared = true;
+ }
+ else if (option_shared)
+ {
+ logger->log(LogLevel::WARNING, "Explicit context given! Ignoring option '$1$'", toString(info_shared));
+ }
+ }
+#if defined(UNX)
+ if ( geteuid() == 0 )
+ {
+ if ( !(option_shared || option_bundled || option_help) )
+ {
+ logger->log(LogLevel::SEVERE, "Cannot run $1$ as root without $2$ or $3$ option.",
+ APP_NAME, toString(info_shared), toString(info_bundled));
+ return 1;
+ }
+ }
+#endif
+
+ if (subCommand == "reinstall")
+ {
+ //We must prevent that services and types are loaded by UNO,
+ //otherwise we cannot delete the registry data folder.
+ OUString extensionUnorc;
+ if (repository == "user")
+ extensionUnorc = "$UNO_USER_PACKAGES_CACHE/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
+ else if (repository == "shared")
+ extensionUnorc = "$SHARED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
+ else if (repository == "bundled")
+ extensionUnorc = "$BUNDLED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
+ else
+ OSL_ASSERT(false);
+
+ ::rtl::Bootstrap::expandMacros(extensionUnorc);
+ oslFileError e = osl_removeFile(extensionUnorc.pData);
+ if (e != osl_File_E_None && e != osl_File_E_NOENT)
+ throw Exception("Could not delete " + extensionUnorc, nullptr);
+ }
+
+ Reference<deployment::XExtensionManager> xExtensionManager(
+ deployment::ExtensionManager::get( xComponentContext ) );
+
+ Reference<css::ucb::XCommandEnvironment> xCmdEnv(
+ createCmdEnv(xComponentContext, option_force, option_verbose, option_suppressLicense));
+
+ //synchronize bundled/shared extensions
+ //Do not synchronize when command is "reinstall". This could add types and services to UNO and
+ //prevent the deletion of the registry data folder
+ //syncing is done in XExtensionManager.reinstall
+ if (!subcmd_gui && subCommand != "reinstall"
+ && ! dp_misc::office_is_running())
+ dp_misc::syncRepositories(false, xCmdEnv);
+
+ if ( subcmd_add || subCommand == "remove" )
+ {
+ for (const OUString & cmdPackage : cmdPackages)
+ {
+ if (subcmd_add)
+ {
+ beans::NamedValue nvSuppress(
+ "SUPPRESS_LICENSE", option_suppressLicense ?
+ Any(OUString("1")):Any(OUString("0")));
+ xExtensionManager->addExtension(
+ cmdPackage, Sequence<beans::NamedValue>(&nvSuppress, 1),
+ repository, Reference<task::XAbortChannel>(), xCmdEnv);
+ }
+ else
+ {
+ try
+ {
+ xExtensionManager->removeExtension(
+ cmdPackage, cmdPackage, repository,
+ Reference<task::XAbortChannel>(), xCmdEnv );
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ Reference<deployment::XPackage> p(
+ findPackage(repository,
+ xExtensionManager, xCmdEnv, cmdPackage ) );
+ if ( !p.is())
+ throw;
+ else if (p.is())
+ xExtensionManager->removeExtension(
+ ::dp_misc::getIdentifier(p), p->getName(),
+ repository,
+ Reference<task::XAbortChannel>(), xCmdEnv );
+ }
+ }
+ }
+ }
+ else if ( subCommand == "reinstall" )
+ {
+ xExtensionManager->reinstallDeployedExtensions(
+ false, repository, Reference<task::XAbortChannel>(), xCmdEnv);
+ }
+ else if ( subCommand == "list" )
+ {
+ std::vector<Reference<deployment::XPackage> > vecExtUnaccepted;
+ ::comphelper::sequenceToContainer(vecExtUnaccepted,
+ xExtensionManager->getExtensionsWithUnacceptedLicenses(
+ repository, xCmdEnv));
+
+ //This vector tells what XPackage in allExtensions has an
+ //unaccepted license.
+ std::vector<bool> vecUnaccepted;
+ std::vector<Reference<deployment::XPackage> > allExtensions;
+ if (cmdPackages.empty())
+ {
+ Sequence< Reference<deployment::XPackage> >
+ packages = xExtensionManager->getDeployedExtensions(
+ repository, Reference<task::XAbortChannel>(), xCmdEnv );
+
+ std::vector<Reference<deployment::XPackage> > vec_packages;
+ ::comphelper::sequenceToContainer(vec_packages, packages);
+
+ //First copy the extensions with the unaccepted license
+ //to vector allExtensions.
+ allExtensions.resize(vecExtUnaccepted.size() + vec_packages.size());
+
+ std::vector<Reference<deployment::XPackage> >::iterator i_all_ext =
+ std::copy(vecExtUnaccepted.begin(), vecExtUnaccepted.end(),
+ allExtensions.begin());
+ //Now copy those we got from getDeployedExtensions
+ std::copy(vec_packages.begin(), vec_packages.end(), i_all_ext);
+
+ //Now prepare the vector which tells what extension has an
+ //unaccepted license
+ vecUnaccepted.resize(vecExtUnaccepted.size() + vec_packages.size());
+ std::fill_n(vecUnaccepted.begin(), vecExtUnaccepted.size(), true);
+ std::fill_n(vecUnaccepted.begin() + vecExtUnaccepted.size(),
+ vec_packages.size(), false);
+
+ dp_misc::writeConsole(
+ Concat2View("All deployed " + repository + " extensions:\n\n"));
+ }
+ else
+ {
+ //The user provided the names (ids or file names) of the extensions
+ //which shall be listed
+ for (const OUString & cmdPackage : cmdPackages)
+ {
+ Reference<deployment::XPackage> extension;
+ try
+ {
+ extension = xExtensionManager->getDeployedExtension(
+ repository, cmdPackage, cmdPackage, xCmdEnv );
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ extension = findPackage(repository,
+ xExtensionManager, xCmdEnv, cmdPackage );
+ }
+
+ //Now look if the requested extension has an unaccepted license
+ bool bUnacceptedLic = false;
+ if (!extension.is())
+ {
+ std::vector<Reference<deployment::XPackage> >::const_iterator
+ i = std::find_if(
+ vecExtUnaccepted.begin(),
+ vecExtUnaccepted.end(), ExtensionName(cmdPackage));
+ if (i != vecExtUnaccepted.end())
+ {
+ extension = *i;
+ bUnacceptedLic = true;
+ }
+ }
+
+ if (!extension.is())
+ throw lang::IllegalArgumentException(
+ "There is no such extension deployed: " +
+ cmdPackage,nullptr,-1);
+ allExtensions.push_back(extension);
+ vecUnaccepted.push_back(bUnacceptedLic);
+ }
+
+ }
+
+ printf_packages(allExtensions, vecUnaccepted, xCmdEnv );
+ }
+ else if ( subCommand == "validate" )
+ {
+ std::vector<Reference<deployment::XPackage> > vecExtUnaccepted;
+ ::comphelper::sequenceToContainer(
+ vecExtUnaccepted, xExtensionManager->getExtensionsWithUnacceptedLicenses(
+ repository, xCmdEnv));
+
+ for (const OUString & cmdPackage : cmdPackages)
+ {
+ Reference<deployment::XPackage> extension;
+ try
+ {
+ extension = xExtensionManager->getDeployedExtension(
+ repository, cmdPackage, cmdPackage, xCmdEnv );
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ extension = findPackage(
+ repository, xExtensionManager, xCmdEnv, cmdPackage );
+ }
+
+ if (!extension.is())
+ {
+ std::vector<Reference<deployment::XPackage> >::const_iterator
+ i = std::find_if(
+ vecExtUnaccepted.begin(),
+ vecExtUnaccepted.end(), ExtensionName(cmdPackage));
+ if (i != vecExtUnaccepted.end())
+ {
+ extension = *i;
+ }
+ }
+
+ if (extension.is())
+ xExtensionManager->checkPrerequisitesAndEnable(
+ extension, Reference<task::XAbortChannel>(), xCmdEnv);
+ }
+ }
+ else if ( subCommand == "gui" )
+ {
+ Reference<ui::dialogs::XAsynchronousExecutableDialog> xDialog(
+ deployment::ui::PackageManagerDialog::createAndInstall(
+ xComponentContext,
+ !cmdPackages.empty() ? cmdPackages[0] : OUString() ));
+
+ osl::Condition dialogEnded;
+ dialogEnded.reset();
+
+ Reference< ui::dialogs::XDialogClosedListener > xListener(
+ new DialogClosedListenerImpl( dialogEnded ) );
+
+ xDialog->startExecuteModal(xListener);
+ dialogEnded.wait();
+ return 0;
+ }
+ else
+ {
+ logger->log(LogLevel::SEVERE,
+ "Unknown sub-command: '$1$'. Use $2$ $3$ to print all options.",
+ subCommand, APP_NAME, toString(info_help));
+ return 1;
+ }
+
+ logger->log(LogLevel::INFO, "$1$ done.", APP_NAME);
+ //Force to release all bridges which connect us to the child processes
+ dp_misc::disposeBridges(xLocalComponentContext);
+ css::uno::Reference<css::lang::XComponent>(
+ xLocalComponentContext, css::uno::UNO_QUERY_THROW)->dispose();
+ return 0;
+ }
+ catch (const ucb::CommandFailedException &e)
+ {
+ logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", e.Message);
+ }
+ catch (const ucb::CommandAbortedException &)
+ {
+ logFatal(logger.get(), LogLevel::SEVERE, "$1$ aborted.", APP_NAME);
+ bShowFailedMsg = false;
+ }
+ catch (const deployment::DeploymentException & exc)
+ {
+ logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", exc.Message);
+ logFatal(
+ logger.get(), LogLevel::INFO, " Cause: $1$", comphelper::anyToString(exc.Cause));
+ }
+ catch (const LockFileException & e)
+ {
+ // No logger since it requires UNO which we don't have here
+ dp_misc::writeConsoleError(Concat2View(e.Message + "\n"));
+ bShowFailedMsg = false;
+ }
+ catch (const css::uno::Exception & e ) {
+ Any exc( ::cppu::getCaughtException() );
+
+ logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", e.Message);
+ logFatal(logger.get(), LogLevel::INFO, " Cause: $1$", comphelper::anyToString(exc));
+ }
+ if (bShowFailedMsg)
+ logFatal(logger.get(), LogLevel::SEVERE, "$1$ failed.", APP_NAME);
+ dp_misc::disposeBridges(xLocalComponentContext);
+ if (xLocalComponentContext.is()) {
+ css::uno::Reference<css::lang::XComponent>(
+ xLocalComponentContext, css::uno::UNO_QUERY_THROW)->dispose();
+ }
+ return 1;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx b/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx
new file mode 100644
index 0000000000..581922a3c3
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <strings.hrc>
+#include <dp_misc.h>
+#include <dp_shared.hxx>
+#include "unopkg_shared.h"
+#include <i18nlangtag/languagetag.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/anytostring.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/configmgr.hxx>
+#include <com/sun/star/lang/WrappedTargetException.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/InstallException.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/VersionException.hpp>
+#include <com/sun/star/deployment/PlatformException.hpp>
+#include <com/sun/star/i18n/Collator.hpp>
+#include <com/sun/star/i18n/CollatorOptions.hpp>
+
+#include <dp_version.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::uno;
+using namespace ::unopkg;
+
+
+namespace {
+
+
+class CommandEnvironmentImpl
+ : public ::cppu::WeakImplHelper< XCommandEnvironment,
+ task::XInteractionHandler,
+ XProgressHandler >
+{
+ sal_Int32 m_logLevel;
+ bool m_option_force_overwrite;
+ bool m_option_verbose;
+ bool m_option_suppress_license;
+ Reference< XComponentContext > m_xComponentContext;
+ Reference< XProgressHandler > m_xLogFile;
+
+ /// @throws RuntimeException
+ void update_( Any const & Status );
+ void printLicense(std::u16string_view sName,std::u16string_view sLicense,
+ bool & accept, bool & decline);
+
+public:
+ virtual ~CommandEnvironmentImpl() override;
+ CommandEnvironmentImpl(
+ Reference<XComponentContext> const & xComponentContext,
+ bool option_force_overwrite,
+ bool option_verbose,
+ bool option_suppress_license);
+
+ // XCommandEnvironment
+ virtual Reference< task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual Reference< XProgressHandler > SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ Reference< task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( Any const & Status ) override;
+ virtual void SAL_CALL update( Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+
+CommandEnvironmentImpl::CommandEnvironmentImpl(
+ Reference<XComponentContext> const & xComponentContext,
+ bool option_force_overwrite,
+ bool option_verbose,
+ bool option_suppressLicense)
+ : m_logLevel(0),
+ m_option_force_overwrite( option_force_overwrite ),
+ m_option_verbose( option_verbose ),
+ m_option_suppress_license( option_suppressLicense ),
+ m_xComponentContext(xComponentContext)
+{
+ m_xLogFile.set(
+ xComponentContext->getServiceManager()
+ ->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.deployment.ProgressLog",
+ Sequence<Any>(), xComponentContext ),
+ UNO_QUERY_THROW );
+}
+
+
+CommandEnvironmentImpl::~CommandEnvironmentImpl()
+{
+ try {
+ Reference< lang::XComponent > xComp( m_xLogFile, UNO_QUERY );
+ if (xComp.is())
+ xComp->dispose();
+ }
+ catch (const RuntimeException &) {
+ TOOLS_WARN_EXCEPTION( "desktop", "" );
+ }
+}
+
+//May throw exceptions
+void CommandEnvironmentImpl::printLicense(
+ std::u16string_view sName, std::u16string_view sLicense, bool & accept, bool &decline)
+{
+ OUString s1tmp(DpResId(RID_STR_UNOPKG_ACCEPT_LIC_1));
+ OUString s1(s1tmp.replaceAll("$NAME", sName));
+ OUString s2 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_2);
+ OUString s3 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_3);
+ OUString s4 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_4);
+ OUString sYES = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_YES);
+ OUString sY = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_Y);
+ OUString sNO = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_NO);
+ OUString sN = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_N);
+
+ OUString sNewLine("\n");
+
+ dp_misc::writeConsole(Concat2View(sNewLine + sNewLine + s1 + sNewLine + sNewLine));
+ dp_misc::writeConsole(Concat2View(sLicense + sNewLine + sNewLine));
+ dp_misc::writeConsole(Concat2View(s2 + sNewLine));
+ dp_misc::writeConsole(s3);
+
+ //the user may enter "yes" or "no", we compare in a case insensitive way
+ Reference< css::i18n::XCollator > xCollator =
+ css::i18n::Collator::create( m_xComponentContext );
+ xCollator->loadDefaultCollator(
+ LanguageTag(utl::ConfigManager::getUILocale()).getLocale(),
+ css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE);
+
+ do
+ {
+ OUString sAnswer = dp_misc::readConsole();
+ if (xCollator->compareString(sAnswer, sYES) == 0
+ || xCollator->compareString(sAnswer, sY) == 0)
+ {
+ accept = true;
+ break;
+ }
+ else if(xCollator->compareString(sAnswer, sNO) == 0
+ || xCollator->compareString(sAnswer, sN) == 0)
+ {
+ decline = true;
+ break;
+ }
+ else
+ {
+ dp_misc::writeConsole(Concat2View(sNewLine + sNewLine + s4 + sNewLine));
+ }
+ }
+ while(true);
+}
+
+// XCommandEnvironment
+
+Reference< task::XInteractionHandler >
+CommandEnvironmentImpl::getInteractionHandler()
+{
+ return this;
+}
+
+
+Reference< XProgressHandler > CommandEnvironmentImpl::getProgressHandler()
+{
+ return this;
+}
+
+// XInteractionHandler
+
+void CommandEnvironmentImpl::handle(
+ Reference<task::XInteractionRequest> const & xRequest )
+{
+ Any request( xRequest->getRequest() );
+ OSL_ASSERT( request.getValueTypeClass() == TypeClass_EXCEPTION );
+ dp_misc::TRACE("[unopkg_cmdenv.cxx] incoming request:\n"
+ + ::comphelper::anyToString(request) + "\n\n");
+
+ // selections:
+ bool approve = false;
+ bool abort = false;
+
+ lang::WrappedTargetException wtExc;
+ deployment::LicenseException licExc;
+ deployment::InstallException instExc;
+ deployment::PlatformException platExc;
+
+ if (request >>= wtExc) {
+ // ignore intermediate errors of legacy packages, i.e.
+ // former pkgchk behaviour:
+ const Reference<deployment::XPackage> xPackage(
+ wtExc.Context, UNO_QUERY );
+ OSL_ASSERT( xPackage.is() );
+ if (xPackage.is()) {
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is()) {
+ approve = (xPackage->isBundle() &&
+ xPackageType->getMediaType().match(
+ "application/vnd.sun.star.legacy-package-bundle") );
+ }
+ }
+ abort = !approve;
+ if (abort) {
+ // notify cause as error:
+ request = wtExc.TargetException;
+ }
+ else {
+ // handable deployment error signalled, e.g.
+ // bundle item registration failed, notify as warning:
+ update_( wtExc.TargetException );
+ }
+ }
+ else if (request >>= licExc)
+ {
+ if ( !m_option_suppress_license )
+ printLicense(licExc.ExtensionName, licExc.Text, approve, abort);
+ else
+ {
+ approve = true;
+ abort = false;
+ }
+ }
+ else if (request >>= instExc)
+ {
+ //Only if the unopgk was started with gui + extension then the user is asked.
+ //In console mode there is no asking.
+ approve = true;
+ }
+ else if (request >>= platExc)
+ {
+ OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM));
+ sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName());
+ dp_misc::writeConsole(Concat2View("\n" + sMsg + "\n\n"));
+ approve = true;
+ }
+ else {
+ deployment::VersionException nc_exc;
+ if (request >>= nc_exc) {
+ approve = m_option_force_overwrite ||
+ (::dp_misc::compareVersions(nc_exc.NewVersion, nc_exc.Deployed->getVersion())
+ == ::dp_misc::GREATER);
+ abort = !approve;
+ }
+ else
+ return; // unknown request => no selection at all
+ }
+
+ if (abort && m_option_verbose)
+ {
+ OUString msg = ::comphelper::anyToString(request);
+ dp_misc::writeConsoleError(Concat2View("\nERROR: " + msg + "\n"));
+ }
+
+ // select:
+ const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> xIC = xRequest->getContinuations();
+ for ( auto const& rCont : xIC )
+ {
+ if (approve) {
+ Reference<task::XInteractionApprove> xInteractionApprove(
+ rCont, UNO_QUERY );
+ if (xInteractionApprove.is()) {
+ xInteractionApprove->select();
+ break;
+ }
+ }
+ else if (abort) {
+ Reference<task::XInteractionAbort> xInteractionAbort(
+ rCont, UNO_QUERY );
+ if (xInteractionAbort.is()) {
+ xInteractionAbort->select();
+ break;
+ }
+ }
+ }
+}
+
+// XProgressHandler
+
+void CommandEnvironmentImpl::push( Any const & Status )
+{
+ update_( Status );
+ OSL_ASSERT( m_logLevel >= 0 );
+ ++m_logLevel;
+ if (m_xLogFile.is())
+ m_xLogFile->push( Status );
+}
+
+
+void CommandEnvironmentImpl::update_( Any const & Status )
+{
+ if (! Status.hasValue())
+ return;
+ bool bUseErr = false;
+ OUString msg;
+ if (Status >>= msg) {
+ if (! m_option_verbose)
+ return;
+ }
+ else {
+ OUStringBuffer buf( "WARNING: " );
+ deployment::DeploymentException dp_exc;
+ if (Status >>= dp_exc) {
+ buf.append( dp_exc.Message + ", Cause: " + ::comphelper::anyToString(dp_exc.Cause) );
+ }
+ else {
+ buf.append( ::comphelper::anyToString(Status) );
+ }
+ msg = buf.makeStringAndClear();
+ bUseErr = true;
+ }
+ OSL_ASSERT( m_logLevel >= 0 );
+ for ( sal_Int32 n = 0; n < m_logLevel; ++n )
+ {
+ if (bUseErr)
+ dp_misc::writeConsoleError(u" ");
+ else
+ dp_misc::writeConsole(u" ");
+ }
+
+ if (bUseErr)
+ dp_misc::writeConsoleError(Concat2View(msg + "\n"));
+ else
+ dp_misc::writeConsole(Concat2View(msg + "\n"));
+}
+
+
+void CommandEnvironmentImpl::update( Any const & Status )
+{
+ update_( Status );
+ if (m_xLogFile.is())
+ m_xLogFile->update( Status );
+}
+
+
+void CommandEnvironmentImpl::pop()
+{
+ OSL_ASSERT( m_logLevel > 0 );
+ --m_logLevel;
+ if (m_xLogFile.is())
+ m_xLogFile->pop();
+}
+
+
+} // anon namespace
+
+namespace unopkg {
+
+
+Reference< XCommandEnvironment > createCmdEnv(
+ Reference< XComponentContext > const & xContext,
+ bool option_force_overwrite,
+ bool option_verbose,
+ bool option_suppress_license)
+{
+ return new CommandEnvironmentImpl(
+ xContext, option_force_overwrite, option_verbose, option_suppress_license);
+}
+} // unopkg
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/pkgchk/unopkg/unopkg_main.c b/desktop/source/pkgchk/unopkg/unopkg_main.c
new file mode 100644
index 0000000000..83b20b0b48
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_main.c
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/main.h>
+
+#include "unopkg_main.h"
+
+SAL_IMPLEMENT_MAIN() { return unopkg_main(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/pkgchk/unopkg/unopkg_main.h b/desktop/source/pkgchk/unopkg/unopkg_main.h
new file mode 100644
index 0000000000..0fcb1013d3
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_main.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <desktop/dllapi.h>
+
+#if defined __cplusplus
+extern "C" {
+#endif
+
+DESKTOP_DLLPUBLIC int unopkg_main(void);
+
+#if defined __cplusplus
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/pkgchk/unopkg/unopkg_misc.cxx b/desktop/source/pkgchk/unopkg/unopkg_misc.cxx
new file mode 100644
index 0000000000..5539529177
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_misc.cxx
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <config_folders.h>
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <osl/process.h>
+#include <osl/file.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/bootstrap.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+
+#include <strings.hrc>
+#include "unopkg_shared.h"
+#include <dp_identifier.hxx>
+#include <dp_misc.h>
+#include <dp_shared.hxx>
+#include <lockfile.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+namespace unopkg {
+
+OUString toString( OptionInfo const * info )
+{
+ assert(info != nullptr);
+ OUStringBuffer buf("--");
+ buf.appendAscii(info->m_name);
+ if (info->m_short_option != '\0')
+ {
+ buf.append(" (short -" + OUStringChar(info->m_short_option) + ")");
+ }
+ if (info->m_has_argument)
+ buf.append(" <argument>" );
+ return buf.makeStringAndClear();
+}
+
+
+OptionInfo const * getOptionInfo(
+ OptionInfo const * list,
+ OUString const & opt )
+{
+ for ( ; list->m_name != nullptr; ++list )
+ {
+ OptionInfo const & option_info = *list;
+ if (!opt.isEmpty())
+ {
+ if (opt.equalsAsciiL(
+ option_info.m_name, option_info.m_name_length ))
+ {
+ return &option_info;
+ }
+ }
+ }
+ SAL_WARN( "desktop", opt );
+ return nullptr;
+}
+
+
+bool isOption( OptionInfo const * option_info, sal_uInt32 * pIndex )
+{
+ assert(option_info != nullptr);
+ if (osl_getCommandArgCount() <= *pIndex)
+ return false;
+
+ OUString arg;
+ osl_getCommandArg( *pIndex, &arg.pData );
+ sal_Int32 len = arg.getLength();
+
+ if (len < 2 || arg[ 0 ] != '-')
+ return false;
+
+ if (len == 2 && arg[ 1 ] == option_info->m_short_option)
+ {
+ ++(*pIndex);
+ dp_misc::TRACE(__FILE__ ": identified option \'\'"
+ + OUStringChar( option_info->m_short_option ) + "\n");
+ return true;
+ }
+ if (arg[ 1 ] == '-' && rtl_ustr_ascii_compare(
+ arg.pData->buffer + 2, option_info->m_name ) == 0)
+ {
+ ++(*pIndex);
+ dp_misc::TRACE(__FILE__ ": identified option \'"
+ + OUString::createFromAscii(option_info->m_name) + "\'\n");
+ return true;
+ }
+ return false;
+}
+
+
+bool isBootstrapVariable(sal_uInt32 * pIndex)
+{
+ OSL_ASSERT(osl_getCommandArgCount() >= *pIndex);
+
+ OUString arg;
+ osl_getCommandArg(*pIndex, &arg.pData);
+ if (arg.match("-env:"))
+ {
+ ++(*pIndex);
+ return true;
+ }
+ return false;
+}
+
+
+bool readArgument(
+ OUString * pValue, OptionInfo const * option_info, sal_uInt32 * pIndex )
+{
+ if (isOption( option_info, pIndex ))
+ {
+ if (*pIndex < osl_getCommandArgCount())
+ {
+ OSL_ASSERT( pValue != nullptr );
+ osl_getCommandArg( *pIndex, &pValue->pData );
+ dp_misc::TRACE(__FILE__ ": argument value: "
+ + *pValue + "\n");
+ ++(*pIndex);
+ return true;
+ }
+ --(*pIndex);
+ }
+ return false;
+}
+
+
+OUString const & getExecutableDir()
+{
+ static const OUString EXEC =
+ []()
+ {
+ OUString path;
+ if (osl_getExecutableFile( &path.pData ) != osl_Process_E_None) {
+ throw RuntimeException("cannot locate executable directory!",nullptr);
+ }
+ return path.copy( 0, path.lastIndexOf( '/' ) );
+ }();
+ return EXEC;
+}
+
+
+OUString const & getProcessWorkingDir()
+{
+ static const OUString WORKING =
+ []()
+ {
+ OUString workingDir;
+ utl::Bootstrap::getProcessWorkingDir(workingDir);
+ return workingDir;
+ }();
+ return WORKING;
+}
+
+
+OUString makeAbsoluteFileUrl(
+ OUString const & sys_path, OUString const & base_url )
+{
+ // system path to file url
+ OUString file_url;
+ oslFileError rc = osl_getFileURLFromSystemPath( sys_path.pData, &file_url.pData );
+ if ( rc != osl_File_E_None) {
+ OUString tempPath;
+ if ( osl_getSystemPathFromFileURL( sys_path.pData, &tempPath.pData) != osl_File_E_None )
+ {
+ throw RuntimeException("cannot get file url from system path: " +
+ sys_path );
+ }
+ file_url = sys_path;
+ }
+
+ OUString abs;
+ if (osl_getAbsoluteFileURL(
+ base_url.pData, file_url.pData, &abs.pData ) != osl_File_E_None)
+ {
+ throw RuntimeException(
+ "making absolute file url failed: \"" + base_url
+ + "\" (base-url) and \"" + file_url + "\" (file-url)!" );
+ }
+ return abs[ abs.getLength() -1 ] == '/'
+ ? abs.copy( 0, abs.getLength() -1 ) : abs;
+}
+
+
+namespace {
+
+
+void printf_space( sal_Int32 space )
+{
+ while (space--)
+ dp_misc::writeConsole(u" ");
+}
+
+
+void printf_line(
+ std::u16string_view name, std::u16string_view value, sal_Int32 level )
+{
+ printf_space( level );
+ dp_misc::writeConsole(Concat2View(OUString::Concat(name) + ": " + value + "\n"));
+}
+
+
+void printf_package(
+ Reference<deployment::XPackage> const & xPackage,
+ Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level )
+{
+ beans::Optional< OUString > id(
+ level == 0
+ ? beans::Optional< OUString >(
+ true, dp_misc::getIdentifier( xPackage ) )
+ : xPackage->getIdentifier() );
+ if (id.IsPresent)
+ printf_line( u"Identifier", id.Value, level );
+ OUString version(xPackage->getVersion());
+ if (!version.isEmpty())
+ printf_line( u"Version", version, level + 1 );
+ printf_line( u"URL", xPackage->getURL(), level + 1 );
+
+ beans::Optional< beans::Ambiguous<sal_Bool> > option(
+ xPackage->isRegistered( Reference<task::XAbortChannel>(), xCmdEnv ) );
+ OUString value;
+ if (option.IsPresent) {
+ beans::Ambiguous<sal_Bool> const & reg = option.Value;
+ if (reg.IsAmbiguous)
+ value = "unknown";
+ else
+ value = reg.Value ? std::u16string_view(u"yes") : std::u16string_view(u"no");
+ }
+ else
+ value = "n/a";
+ printf_line( u"is registered", value, level + 1 );
+
+ const Reference<deployment::XPackageTypeInfo> xPackageType(
+ xPackage->getPackageType() );
+ OSL_ASSERT( xPackageType.is() );
+ if (xPackageType.is()) {
+ printf_line( u"Media-Type", xPackageType->getMediaType(), level + 1 );
+ }
+ printf_line( u"Description", xPackage->getDescription(), level + 1 );
+ if (!xPackage->isBundle())
+ return;
+
+ Sequence< Reference<deployment::XPackage> > seq(
+ xPackage->getBundle( Reference<task::XAbortChannel>(), xCmdEnv ) );
+ printf_space( level + 1 );
+ dp_misc::writeConsole(u"bundled Packages: {\n");
+ std::vector<Reference<deployment::XPackage> >vec_bundle;
+ ::comphelper::sequenceToContainer(vec_bundle, seq);
+ printf_packages( vec_bundle, std::vector<bool>(vec_bundle.size()),
+ xCmdEnv, level + 2 );
+ printf_space( level + 1 );
+ dp_misc::writeConsole(u"}\n");
+}
+
+} // anon namespace
+
+static void printf_unaccepted_licenses(
+ Reference<deployment::XPackage> const & ext)
+{
+ OUString id(
+ dp_misc::getIdentifier(ext) );
+ printf_line( u"Identifier", id, 0 );
+ printf_space(1);
+ dp_misc::writeConsole(u"License not accepted\n\n");
+}
+
+
+void printf_packages(
+ std::vector< Reference<deployment::XPackage> > const & allExtensions,
+ std::vector<bool> const & vecUnaccepted,
+ Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level )
+{
+ OSL_ASSERT(allExtensions.size() == vecUnaccepted.size());
+
+ if (allExtensions.empty())
+ {
+ printf_space( level );
+ dp_misc::writeConsole(u"<none>\n");
+ }
+ else
+ {
+ int index = 0;
+ for (auto const& extension : allExtensions)
+ {
+ if (vecUnaccepted[index])
+ printf_unaccepted_licenses(extension);
+ else
+ printf_package( extension, xCmdEnv, level );
+ dp_misc::writeConsole(u"\n");
+ ++index;
+ }
+ }
+}
+
+
+namespace {
+
+
+Reference<XComponentContext> bootstrapStandAlone()
+{
+ Reference<XComponentContext> xContext =
+ ::cppu::defaultBootstrap_InitialComponentContext();
+
+ Reference<lang::XMultiServiceFactory> xServiceManager(
+ xContext->getServiceManager(), UNO_QUERY_THROW );
+ // set global process service factory used by unotools config helpers
+ ::comphelper::setProcessServiceFactory( xServiceManager );
+
+ // Initialize the UCB (for backwards compatibility, in case some code still
+ // uses plain createInstance w/o args directly to obtain an instance):
+ UniversalContentBroker::create( xContext );
+
+ return xContext;
+}
+
+
+Reference<XComponentContext> connectToOffice(
+ Reference<XComponentContext> const & xLocalComponentContext,
+ bool verbose )
+{
+ OUString pipeId( ::dp_misc::generateRandomPipeId() );
+ OUString acceptArg = "--accept=pipe,name=" + pipeId + ";urp;";
+
+ Sequence<OUString> args { "--nologo", "--nodefault", acceptArg };
+ OUString appURL( getExecutableDir() + "/soffice" );
+
+ if (verbose)
+ {
+ dp_misc::writeConsole(Concat2View(
+ "Raising process: " + appURL +
+ "\nArguments: --nologo --nodefault " + args[2] +
+ "\n"));
+ }
+
+ ::dp_misc::raiseProcess( appURL, args );
+
+ if (verbose)
+ dp_misc::writeConsole(u"OK. Connecting...");
+
+ OUString sUnoUrl = "uno:pipe,name=" + pipeId + ";urp;StarOffice.ComponentContext";
+ Reference<XComponentContext> xRet(
+ ::dp_misc::resolveUnoURL(
+ sUnoUrl, xLocalComponentContext ),
+ UNO_QUERY_THROW );
+ if (verbose)
+ dp_misc::writeConsole(u"OK.\n");
+
+ return xRet;
+}
+
+} // anon namespace
+
+/** returns the path to the lock file used by unopkg.
+ @return the path. An empty string signifies an error.
+*/
+static OUString getLockFilePath()
+{
+ OUString ret;
+ OUString sBootstrap("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}");
+ rtl::Bootstrap::expandMacros(sBootstrap);
+ OUString sAbs;
+ if (::osl::File::E_None == ::osl::File::getAbsoluteFileURL(
+ sBootstrap, ".lock", sAbs))
+ {
+ if (::osl::File::E_None ==
+ ::osl::File::getSystemPathFromFileURL(sAbs, sBootstrap))
+ {
+ ret = sBootstrap;
+ }
+ }
+
+ return ret;
+}
+
+Reference<XComponentContext> getUNO(
+ bool verbose, bool bGui, const OUString& sTempDir,
+ Reference<XComponentContext> & out_localContext)
+{
+ // do not create any user data (for the root user) in --shared mode:
+ if (!sTempDir.isEmpty())
+ rtl::Bootstrap::set("UserInstallation", sTempDir);
+
+ // hold lock during process runtime:
+ static ::desktop::Lockfile s_lockfile( false /* no IPC server */ );
+ Reference<XComponentContext> xComponentContext( bootstrapStandAlone() );
+ out_localContext = xComponentContext;
+ if (::dp_misc::office_is_running()) {
+ xComponentContext.set(
+ connectToOffice( xComponentContext, verbose ) );
+ }
+ else
+ {
+ if (! s_lockfile.check( nullptr ))
+ {
+ OUString sMsg(DpResId(RID_STR_CONCURRENTINSTANCE));
+ OUString sError(DpResId(RID_STR_UNOPKG_ERROR));
+
+ sMsg += "\n" + getLockFilePath();
+
+ if (bGui)
+ {
+ //We show a message box or print to the console that there
+ //is another instance already running
+ if ( ! InitVCL() )
+ throw RuntimeException( "Cannot initialize VCL!" );
+ {
+ std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ sMsg));
+ xWarn->set_title(utl::ConfigManager::getProductName());
+ xWarn->run();
+ }
+ DeInitVCL();
+ }
+
+ throw LockFileException(sError + sMsg);
+ }
+ }
+
+ return xComponentContext;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/pkgchk/unopkg/unopkg_shared.h b/desktop/source/pkgchk/unopkg/unopkg_shared.h
new file mode 100644
index 0000000000..21d0f6a928
--- /dev/null
+++ b/desktop/source/pkgchk/unopkg/unopkg_shared.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <osl/diagnose.h>
+#include <rtl/ustring.hxx>
+
+#include <utility>
+#include <vector>
+
+#define APP_NAME "unopkg"
+
+namespace unopkg {
+
+struct OptionInfo
+{
+ char const * m_name;
+ sal_uInt32 m_name_length;
+ sal_Unicode m_short_option;
+ bool m_has_argument;
+};
+
+struct LockFileException
+{
+ explicit LockFileException(OUString sMessage) :
+ Message(std::move(sMessage)) {}
+
+ OUString Message;
+};
+
+
+OUString toString( OptionInfo const * info );
+
+
+OptionInfo const * getOptionInfo(
+ OptionInfo const * list,
+ OUString const & opt );
+
+
+bool isOption( OptionInfo const * option_info, sal_uInt32 * pIndex );
+
+
+bool readArgument(
+ OUString * pValue, OptionInfo const * option_info,
+ sal_uInt32 * pIndex );
+
+
+inline bool readOption(
+ bool * flag, OptionInfo const * option_info, sal_uInt32 * pIndex )
+{
+ if (isOption( option_info, pIndex )) {
+ OSL_ASSERT( flag != nullptr );
+ *flag = true;
+ return true;
+ }
+ return false;
+}
+
+
+/** checks if an argument is a bootstrap variable. These start with -env:. For example
+ -env:UNO_JAVA_JFW_USER_DATA=file:///d:/user
+*/
+bool isBootstrapVariable(sal_uInt32 * pIndex);
+
+OUString const & getExecutableDir();
+
+
+OUString const & getProcessWorkingDir();
+
+
+OUString makeAbsoluteFileUrl(
+ OUString const & sys_path, OUString const & base_url );
+
+
+
+
+css::uno::Reference<css::ucb::XCommandEnvironment> createCmdEnv(
+ css::uno::Reference<css::uno::XComponentContext> const & xContext,
+ bool option_force_overwrite,
+ bool option_verbose,
+ bool option_suppressLicense);
+
+void printf_packages(
+ std::vector<
+ css::uno::Reference<css::deployment::XPackage> > const & allExtensions,
+ std::vector<bool> const & vecUnaccepted,
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv,
+ sal_Int32 level = 0 );
+
+
+
+
+css::uno::Reference<css::uno::XComponentContext> getUNO(
+ bool verbose, bool bGui, const OUString& sTempDir,
+ css::uno::Reference<css::uno::XComponentContext> & out_LocalComponentContext);
+
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/splash/spl.component b/desktop/source/splash/spl.component
new file mode 100644
index 0000000000..a15cbdf544
--- /dev/null
+++ b/desktop/source/splash/spl.component
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.office.comp.SplashScreen"
+ constructor="desktop_SplashScreen_get_implementation">
+ <service name="com.sun.star.office.SplashScreen"/>
+ </implementation>
+ <implementation name="com.sun.star.office.comp.PipeSplashScreen"
+ constructor="desktop_UnxSplash_get_implementation" single-instance="true">
+ <service name="com.sun.star.office.PipeSplashScreen"/>
+ </implementation>
+</component>
diff --git a/desktop/source/splash/splash.cxx b/desktop/source/splash/splash.cxx
new file mode 100644
index 0000000000..17cc7fb534
--- /dev/null
+++ b/desktop/source/splash/splash.cxx
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/log.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/salnativewidgets.hxx>
+
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/math.hxx>
+#include <vcl/introwin.hxx>
+#include <vcl/virdev.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <mutex>
+
+#define NOT_LOADED (tools::Long(-1))
+#define NOT_LOADED_COLOR (Color(ColorTransparency, 0xffffffff))
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::uno;
+
+namespace {
+
+class SplashScreen;
+
+class SplashScreenWindow : public IntroWindow
+{
+public:
+ SplashScreen *pSpl;
+ ScopedVclPtr<VirtualDevice> _vdev;
+ explicit SplashScreenWindow(SplashScreen *);
+ virtual ~SplashScreenWindow() override { disposeOnce(); }
+ virtual void dispose() override;
+ // workwindow
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override;
+ void Redraw();
+
+};
+
+class SplashScreen
+ : public ::cppu::WeakImplHelper< XStatusIndicator, XInitialization, XServiceInfo >
+{
+ friend class SplashScreenWindow;
+private:
+ VclPtr<SplashScreenWindow> pWindow;
+
+ DECL_LINK( AppEventListenerHdl, VclSimpleEvent&, void );
+ virtual ~SplashScreen() override;
+ void loadConfig();
+ void updateStatus();
+ void SetScreenBitmap(BitmapEx &rBitmap);
+ static void determineProgressRatioValues( double& rXRelPos, double& rYRelPos, double& rRelWidth, double& rRelHeight );
+
+ BitmapEx _aIntroBmp;
+ Color _cProgressFrameColor;
+ Color _cProgressBarColor;
+ Color _cProgressTextColor;
+ bool _bNativeProgress;
+ OUString _sAppName;
+ OUString _sProgressText;
+
+ sal_Int32 _iMax;
+ sal_Int32 _iProgress;
+ bool _bPaintProgress;
+ bool _bVisible;
+ bool _bShowLogo;
+ bool _bFullScreenSplash;
+ bool _bProgressEnd;
+ tools::Long _height, _width, _tlx, _tly, _barwidth;
+ tools::Long _barheight, _barspace, _textBaseline;
+ double _fXPos, _fYPos;
+ double _fWidth, _fHeight;
+ static constexpr tools::Long _xoffset = 12, _yoffset = 18;
+
+public:
+ SplashScreen();
+
+ // XStatusIndicator
+ virtual void SAL_CALL end() override;
+ virtual void SAL_CALL reset() override;
+ virtual void SAL_CALL setText(const OUString& aText) override;
+ virtual void SAL_CALL setValue(sal_Int32 nValue) override;
+ virtual void SAL_CALL start(const OUString& aText, sal_Int32 nRange) override;
+
+ // XInitialize
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ { return "com.sun.star.office.comp.SplashScreen"; }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ { return cppu::supportsService(this, ServiceName); }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ { return { "com.sun.star.office.SplashScreen" }; }
+};
+
+SplashScreenWindow::SplashScreenWindow(SplashScreen *pSplash)
+ : pSpl( pSplash )
+ , _vdev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
+{
+ _vdev->EnableRTL(IsRTLEnabled());
+}
+
+void SplashScreenWindow::dispose()
+{
+ pSpl = nullptr;
+ IntroWindow::dispose();
+}
+
+void SplashScreenWindow::Redraw()
+{
+ Invalidate();
+ // Trigger direct painting too - otherwise the splash screen won't be
+ // shown in some cases (when the idle timer won't be hit).
+ Paint(*GetOutDev(), tools::Rectangle());
+ GetOutDev()->Flush();
+}
+
+SplashScreen::SplashScreen()
+ : pWindow( VclPtr<SplashScreenWindow>::Create(this) )
+ , _cProgressFrameColor(NOT_LOADED_COLOR)
+ , _cProgressBarColor(NOT_LOADED_COLOR)
+ , _cProgressTextColor(NOT_LOADED_COLOR)
+ , _bNativeProgress(true)
+ , _iMax(100)
+ , _iProgress(0)
+ , _bPaintProgress(false)
+ , _bVisible(true)
+ , _bShowLogo(true)
+ , _bFullScreenSplash(false)
+ , _bProgressEnd(false)
+ , _height(0)
+ , _width(0)
+ , _tlx(NOT_LOADED)
+ , _tly(NOT_LOADED)
+ , _barwidth(NOT_LOADED)
+ , _barheight(NOT_LOADED)
+ , _barspace(2)
+ , _textBaseline(NOT_LOADED)
+ , _fXPos(-1.0)
+ , _fYPos(-1.0)
+ , _fWidth(-1.0)
+ , _fHeight(-1.0)
+{
+ loadConfig();
+}
+
+SplashScreen::~SplashScreen()
+{
+ Application::RemoveEventListener(
+ LINK( this, SplashScreen, AppEventListenerHdl ) );
+ pWindow->Hide();
+ pWindow.disposeAndClear();
+}
+
+void SAL_CALL SplashScreen::start(const OUString&, sal_Int32 nRange)
+{
+ _iMax = nRange;
+ if (_bVisible) {
+ _bProgressEnd = false;
+ SolarMutexGuard aSolarGuard;
+ pWindow->Show();
+ pWindow->Redraw();
+ }
+}
+
+void SAL_CALL SplashScreen::end()
+{
+ _iProgress = _iMax;
+ if (_bVisible )
+ {
+ pWindow->Hide();
+ }
+ _bProgressEnd = true;
+}
+
+void SAL_CALL SplashScreen::reset()
+{
+ _iProgress = 0;
+ if (_bVisible && !_bProgressEnd )
+ {
+ pWindow->Show();
+ updateStatus();
+ }
+}
+
+void SAL_CALL SplashScreen::setText(const OUString& rText)
+{
+ SolarMutexGuard aSolarGuard;
+ if ( _sProgressText != rText )
+ {
+ _sProgressText = rText;
+
+ if (_bVisible && !_bProgressEnd)
+ {
+ pWindow->Show();
+ updateStatus();
+ }
+ }
+}
+
+void SAL_CALL SplashScreen::setValue(sal_Int32 nValue)
+{
+ SAL_INFO( "desktop.splash", "setValue: " << nValue );
+
+ SolarMutexGuard aSolarGuard;
+ if (_bVisible && !_bProgressEnd) {
+ pWindow->Show();
+ if (nValue >= _iMax)
+ _iProgress = _iMax;
+ else
+ _iProgress = nValue;
+ updateStatus();
+ }
+}
+
+// XInitialize
+void SAL_CALL
+SplashScreen::initialize( const css::uno::Sequence< css::uno::Any>& aArguments )
+{
+ static std::mutex aMutex;
+ std::lock_guard aGuard( aMutex );
+ if (!aArguments.hasElements())
+ return;
+
+ aArguments[0] >>= _bVisible;
+ if (aArguments.getLength() > 1 )
+ aArguments[1] >>= _sAppName;
+
+ // start to determine bitmap and all other required value
+ if ( _bShowLogo )
+ SetScreenBitmap (_aIntroBmp);
+ Size aSize = _aIntroBmp.GetSizePixel();
+ pWindow->SetOutputSizePixel( aSize );
+ pWindow->_vdev->SetOutputSizePixel( aSize );
+ _height = aSize.Height();
+ _width = aSize.Width();
+ if (_width > 500)
+ {
+ Point xtopleft(212,216);
+ if ( NOT_LOADED == _tlx || NOT_LOADED == _tly )
+ {
+ _tlx = xtopleft.X(); // top-left x
+ _tly = xtopleft.Y(); // top-left y
+ }
+ if ( NOT_LOADED == _barwidth )
+ _barwidth = 263;
+ if ( NOT_LOADED == _barheight )
+ _barheight = 8;
+ }
+ else
+ {
+ if ( NOT_LOADED == _barwidth )
+ _barwidth = _width - (2 * _xoffset);
+ if ( NOT_LOADED == _barheight )
+ _barheight = 6;
+ if ( NOT_LOADED == _tlx || NOT_LOADED == _tly )
+ {
+ _tlx = _xoffset; // top-left x
+ _tly = _height - _yoffset; // top-left y
+ }
+ }
+
+ if ( NOT_LOADED == _textBaseline )
+ _textBaseline = _height;
+
+ if ( NOT_LOADED_COLOR == _cProgressFrameColor )
+ _cProgressFrameColor = COL_LIGHTGRAY;
+
+ if ( NOT_LOADED_COLOR == _cProgressBarColor )
+ {
+ // progress bar: new color only for big bitmap format
+ if ( _width > 500 )
+ _cProgressBarColor = Color( 157, 202, 18 );
+ else
+ _cProgressBarColor = COL_BLUE;
+ }
+
+ if ( NOT_LOADED_COLOR == _cProgressTextColor )
+ _cProgressTextColor = COL_BLACK;
+
+ Application::AddEventListener(
+ LINK( this, SplashScreen, AppEventListenerHdl ) );
+}
+
+void SplashScreen::updateStatus()
+{
+ if (!_bVisible || _bProgressEnd)
+ return;
+ if (!_bPaintProgress)
+ _bPaintProgress = true;
+ pWindow->Redraw();
+}
+
+// internal private methods
+IMPL_LINK( SplashScreen, AppEventListenerHdl, VclSimpleEvent&, inEvent, void )
+{
+ if (static_cast<VclWindowEvent&>(inEvent).GetWindow() == pWindow)
+ {
+ switch ( inEvent.GetId() )
+ {
+ case VclEventId::WindowShow:
+ pWindow->Redraw();
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+// Read keys from soffice{.ini|rc}:
+OUString implReadBootstrapKey( const OUString& _rKey )
+{
+ OUString sValue;
+ rtl::Bootstrap::get(_rKey, sValue);
+ return sValue;
+}
+
+void SplashScreen::loadConfig()
+{
+ _bShowLogo = implReadBootstrapKey( "Logo" ) != "0";
+
+ OUString sProgressFrameColor = implReadBootstrapKey( "ProgressFrameColor" );
+ OUString sProgressBarColor = implReadBootstrapKey( "ProgressBarColor" );
+ OUString sProgressTextColor = implReadBootstrapKey( "ProgressTextColor" );
+ OUString sProgressTextBaseline = implReadBootstrapKey( "ProgressTextBaseline" );
+ OUString sSize = implReadBootstrapKey( "ProgressSize" );
+ OUString sPosition = implReadBootstrapKey( "ProgressPosition" );
+ OUString sFullScreenSplash = implReadBootstrapKey( "FullScreenSplash" );
+ OUString sNativeProgress = implReadBootstrapKey( "NativeProgress" );
+
+
+ // Determine full screen splash mode
+ _bFullScreenSplash = (( !sFullScreenSplash.isEmpty() ) &&
+ ( sFullScreenSplash != "0" ));
+
+ // Try to retrieve the relative values for the progress bar. The current
+ // schema uses the screen ratio to retrieve the associated values.
+ if ( _bFullScreenSplash )
+ determineProgressRatioValues( _fXPos, _fYPos, _fWidth, _fHeight );
+
+ if ( !sProgressFrameColor.isEmpty() )
+ {
+ sal_uInt8 nRed = 0;
+ sal_Int32 idx = 0;
+ sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ nRed = static_cast< sal_uInt8 >( temp );
+ temp = o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx ));
+ }
+ if ( idx != -1 )
+ {
+ sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp );
+ sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx )) );
+ _cProgressFrameColor = Color( nRed, nGreen, nBlue );
+ }
+ }
+
+ if ( !sProgressBarColor.isEmpty() )
+ {
+ sal_uInt8 nRed = 0;
+ sal_Int32 idx = 0;
+ sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ nRed = static_cast< sal_uInt8 >( temp );
+ temp = o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx ));
+ }
+ if ( idx != -1 )
+ {
+ sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp );
+ sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx )) );
+ _cProgressBarColor = Color( nRed, nGreen, nBlue );
+ }
+ }
+
+ if ( !sProgressTextColor.isEmpty() )
+ {
+ sal_uInt8 nRed = 0;
+ sal_Int32 idx = 0;
+ sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ nRed = static_cast< sal_uInt8 >( temp );
+ temp = o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx ));
+ }
+ if ( idx != -1 )
+ {
+ sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp );
+ sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx )) );
+ _cProgressTextColor = Color( nRed, nGreen, nBlue );
+ }
+ }
+
+ if ( !sProgressTextBaseline.isEmpty() )
+ {
+ _textBaseline = sProgressTextBaseline.toInt32();
+ }
+
+ if( !sNativeProgress.isEmpty() )
+ {
+ _bNativeProgress = sNativeProgress.toBoolean();
+ }
+
+ if ( !sSize.isEmpty() )
+ {
+ sal_Int32 idx = 0;
+ sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sSize, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ _barwidth = temp;
+ _barheight = o3tl::toInt32(o3tl::getToken(sSize, 0, ',', idx ));
+ }
+ }
+
+ if ( _barheight >= 10 )
+ _barspace = 3; // more space between frame and bar
+
+ if ( !sPosition.isEmpty() )
+ {
+ sal_Int32 idx = 0;
+ sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sPosition, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ _tlx = temp;
+ _tly = o3tl::toInt32(o3tl::getToken(sPosition, 0, ',', idx ));
+ }
+ }
+}
+
+void SplashScreen::SetScreenBitmap(BitmapEx &rBitmap)
+{
+ sal_Int32 nWidth( 0 );
+ sal_Int32 nHeight( 0 );
+
+ // determine desktop resolution
+ sal_uInt32 nCount = Application::GetScreenCount();
+ if ( nCount > 0 )
+ {
+ // retrieve size from first screen
+ AbsoluteScreenPixelRectangle aScreenArea = Application::GetScreenPosSizePixel(static_cast<unsigned int>(0));
+ nWidth = aScreenArea.GetWidth();
+ nHeight = aScreenArea.GetHeight();
+ }
+
+ // create file name from screen resolution information
+ OUString aResBuf = "_" + OUString::number(nWidth) + "x" + OUString::number(nHeight);
+ if ( !_sAppName.isEmpty() )
+ if (Application::LoadBrandBitmap(Concat2View("intro_" + _sAppName + aResBuf), rBitmap))
+ return;
+
+ if (Application::LoadBrandBitmap(Concat2View("intro" + aResBuf), rBitmap))
+ return;
+
+ (void)Application::LoadBrandBitmap (u"intro", rBitmap);
+}
+
+void SplashScreen::determineProgressRatioValues(
+ double& rXRelPos, double& rYRelPos,
+ double& rRelWidth, double& rRelHeight )
+{
+ sal_Int32 nScreenRatio( 0 );
+
+ // determine desktop resolution
+ sal_uInt32 nCount = Application::GetScreenCount();
+ if ( nCount > 0 )
+ {
+ // retrieve size from first screen
+ AbsoluteScreenPixelRectangle aScreenArea = Application::GetScreenPosSizePixel(static_cast<unsigned int>(0));
+ sal_Int32 nWidth = aScreenArea.GetWidth();
+ sal_Int32 nHeight = aScreenArea.GetHeight();
+ nScreenRatio = nHeight ? sal_Int32( rtl::math::round( double( nWidth ) / double( nHeight ), 2 ) * 100 ) : 0;
+ }
+
+ char szFullScreenProgressRatio[] = "FullScreenProgressRatio0";
+ char szFullScreenProgressPos[] = "FullScreenProgressPos0";
+ char szFullScreenProgressSize[] = "FullScreenProgressSize0";
+ for ( sal_Int32 i = 0; i <= 9; i++ )
+ {
+ char cNum = '0' + char( i );
+ szFullScreenProgressRatio[23] = cNum;
+ szFullScreenProgressPos[21] = cNum;
+ szFullScreenProgressSize[22] = cNum;
+
+ OUString sFullScreenProgressRatio = implReadBootstrapKey(
+ OUString::createFromAscii( szFullScreenProgressRatio ) );
+
+ if ( !sFullScreenProgressRatio.isEmpty() )
+ {
+ double fRatio = sFullScreenProgressRatio.toDouble();
+ sal_Int32 nRatio = sal_Int32( rtl::math::round( fRatio, 2 ) * 100 );
+ if ( nRatio == nScreenRatio )
+ {
+ OUString sFullScreenProgressPos = implReadBootstrapKey(
+ OUString::createFromAscii( szFullScreenProgressPos ) );
+ OUString sFullScreenProgressSize = implReadBootstrapKey(
+ OUString::createFromAscii( szFullScreenProgressSize ) );
+
+ if ( !sFullScreenProgressPos.isEmpty() )
+ {
+ sal_Int32 idx = 0;
+ double temp = o3tl::toDouble(o3tl::getToken(sFullScreenProgressPos, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ rXRelPos = temp;
+ rYRelPos = o3tl::toDouble(o3tl::getToken(sFullScreenProgressPos, 0, ',', idx ));
+ }
+ }
+
+ if ( !sFullScreenProgressSize.isEmpty() )
+ {
+ sal_Int32 idx = 0;
+ double temp = o3tl::toDouble(o3tl::getToken(sFullScreenProgressSize, 0, ',', idx ));
+ if ( idx != -1 )
+ {
+ rRelWidth = temp;
+ rRelHeight = o3tl::toDouble(o3tl::getToken(sFullScreenProgressSize, 0, ',', idx ));
+ }
+ }
+ }
+ }
+ else
+ break;
+ }
+}
+
+void SplashScreenWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ if (!pSpl || !pSpl->_bVisible)
+ return;
+
+ //native drawing
+ // in case of native controls we need to draw directly to the window
+ if (pSpl->_bNativeProgress && rRenderContext.IsNativeControlSupported(ControlType::IntroProgress, ControlPart::Entire))
+ {
+ rRenderContext.DrawBitmapEx(Point(), pSpl->_aIntroBmp);
+
+ ImplControlValue aValue( pSpl->_iProgress * pSpl->_barwidth / pSpl->_iMax);
+ tools::Rectangle aDrawRect( Point(pSpl->_tlx, pSpl->_tly), Size( pSpl->_barwidth, pSpl->_barheight));
+ tools::Rectangle aNativeControlRegion, aNativeContentRegion;
+
+ if (rRenderContext.GetNativeControlRegion(ControlType::IntroProgress, ControlPart::Entire, aDrawRect,
+ ControlState::ENABLED, aValue,
+ aNativeControlRegion, aNativeContentRegion))
+ {
+ tools::Long nProgressHeight = aNativeControlRegion.GetHeight();
+ aDrawRect.AdjustTop( -((nProgressHeight - pSpl->_barheight)/2) );
+ aDrawRect.AdjustBottom((nProgressHeight - pSpl->_barheight)/2 );
+ }
+
+ if (rRenderContext.DrawNativeControl(ControlType::IntroProgress, ControlPart::Entire, aDrawRect,
+ ControlState::ENABLED, aValue, pSpl->_sProgressText))
+ {
+ return;
+ }
+ }
+
+ // non native drawing
+ // draw bitmap
+ _vdev->DrawBitmapEx(Point(), pSpl->_aIntroBmp);
+
+ if (pSpl->_bPaintProgress) {
+ // draw progress...
+ tools::Long length = (pSpl->_iProgress * pSpl->_barwidth / pSpl->_iMax) - (2 * pSpl->_barspace);
+ if (length < 0) length = 0;
+
+ // border
+ _vdev->SetFillColor();
+ _vdev->SetLineColor( pSpl->_cProgressFrameColor );
+ _vdev->DrawRect(tools::Rectangle(pSpl->_tlx, pSpl->_tly, pSpl->_tlx+pSpl->_barwidth, pSpl->_tly+pSpl->_barheight));
+ _vdev->SetFillColor( pSpl->_cProgressBarColor );
+ _vdev->SetLineColor();
+ _vdev->DrawRect(tools::Rectangle(pSpl->_tlx+pSpl->_barspace, pSpl->_tly+pSpl->_barspace, pSpl->_tlx+pSpl->_barspace+length, pSpl->_tly+pSpl->_barheight-pSpl->_barspace));
+ vcl::Font aFont;
+ aFont.SetFontSize(Size(0, 12));
+ aFont.SetAlignment(ALIGN_BASELINE);
+ _vdev->SetFont(aFont);
+ _vdev->SetTextColor(pSpl->_cProgressTextColor);
+ _vdev->DrawText(Point(pSpl->_tlx, pSpl->_textBaseline), pSpl->_sProgressText);
+ }
+ rRenderContext.DrawOutDev(Point(), GetOutputSizePixel(), Point(), _vdev->GetOutputSizePixel(), *_vdev);
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_SplashScreen_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SplashScreen());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/splash/unxsplash.cxx b/desktop/source/splash/unxsplash.cxx
new file mode 100644
index 0000000000..b218b75919
--- /dev/null
+++ b/desktop/source/splash/unxsplash.cxx
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "unxsplash.hxx"
+#include <stdio.h>
+#include <osl/process.h>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+using namespace com::sun::star;
+
+namespace desktop
+{
+ UnxSplashScreen::UnxSplashScreen()
+ : m_pOutFd( nullptr )
+{
+}
+
+UnxSplashScreen::~UnxSplashScreen()
+{
+ SAL_INFO("desktop.splash", "UnxSplashScreen::~UnxSplashScreen()");
+ if ( m_pOutFd )
+ {
+ fclose( m_pOutFd );
+ m_pOutFd = nullptr;
+ }
+}
+
+void SAL_CALL UnxSplashScreen::start( const OUString& /*aText*/, sal_Int32 /*nRange*/ )
+{
+}
+
+void SAL_CALL UnxSplashScreen::end()
+{
+ SAL_INFO("desktop.splash", "UnxSplashScreen::end()");
+ if( !m_pOutFd )
+ return;
+
+ fprintf( m_pOutFd, "end\n" );
+ fflush( m_pOutFd );
+}
+
+void SAL_CALL UnxSplashScreen::reset()
+{
+ SAL_INFO("desktop.splash", "UNXSplashScreen::reset()");
+ if( !m_pOutFd )
+ return;
+
+ fprintf( m_pOutFd, "restart\n" );
+ fflush( m_pOutFd );
+}
+
+void SAL_CALL UnxSplashScreen::setText( const OUString& /*aText*/ )
+{
+ // TODO?
+}
+
+void SAL_CALL UnxSplashScreen::setValue( sal_Int32 nValue )
+{
+ if ( m_pOutFd )
+ {
+ fprintf( m_pOutFd, "%" SAL_PRIdINT32 "%%\n", nValue );
+ fflush( m_pOutFd );
+ }
+}
+
+// XInitialize
+void SAL_CALL
+UnxSplashScreen::initialize( const css::uno::Sequence< css::uno::Any>& )
+{
+ for ( sal_uInt32 i = 0; i < osl_getCommandArgCount(); i++ )
+ {
+ OUString aArg;
+ osl_getCommandArg( i, &aArg.pData );
+ OUString aNum;
+ if ( aArg.startsWithIgnoreAsciiCase("--splash-pipe=", &aNum) )
+ {
+ auto fd = aNum.toUInt32();
+ m_pOutFd = fdopen( fd, "w" );
+ SAL_INFO("desktop.splash", "Got argument '--splash-pipe=" << fd << " ('"
+ << aNum << "') ("
+ << static_cast<void *>(m_pOutFd) << ")");
+ }
+ }
+}
+
+OUString UnxSplashScreen::getImplementationName()
+{
+ return "com.sun.star.office.comp.PipeSplashScreen";
+}
+
+sal_Bool UnxSplashScreen::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> UnxSplashScreen::getSupportedServiceNames()
+{
+ return { "com.sun.star.office.PipeSplashScreen" };
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+desktop_UnxSplash_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new desktop::UnxSplashScreen());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/splash/unxsplash.hxx b/desktop/source/splash/unxsplash.hxx
new file mode 100644
index 0000000000..52b148abf0
--- /dev/null
+++ b/desktop/source/splash/unxsplash.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <cppuhelper/implbase.hxx>
+
+namespace desktop {
+
+class UnxSplashScreen : public ::cppu::WeakImplHelper< css::task::XStatusIndicator, css::lang::XInitialization, css::lang::XServiceInfo >
+{
+private:
+ UnxSplashScreen( const UnxSplashScreen& ) = delete;
+ UnxSplashScreen operator =( const UnxSplashScreen& ) = delete;
+
+ virtual ~UnxSplashScreen() override;
+
+ FILE *m_pOutFd;
+
+public:
+ explicit UnxSplashScreen();
+
+ // XStatusIndicator
+ virtual void SAL_CALL start( const OUString& aText, sal_Int32 nRange ) override;
+ virtual void SAL_CALL end() override;
+ virtual void SAL_CALL reset() override;
+ virtual void SAL_CALL setText( const OUString& aText ) override;
+ virtual void SAL_CALL setValue( sal_Int32 nValue ) override;
+
+ // XInitialize
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) override;
+
+ virtual OUString SAL_CALL getImplementationName() override;
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */