diff options
Diffstat (limited to 'desktop/source')
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 “Check for Updates” 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 “Download and Installation” 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 + “unknown” 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 "all". + 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><update-website></code>. If the element exists, then it must + have at least one child element containing a URL. + + The <code><update-website></code> and <code><update-download></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><update-download></code> and the <code><update-website></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, ¶ms )) + { + 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, ¶ms )) + { + 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, ¶ms )) + { + 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, ¶ms )) + { + 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, ¶ms )) + { + 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, ¶ms )) + { + 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, ¶ms ) && + 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, ¶ms )) + 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, ¶ms )) + { + 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, ¶ms )) + { + 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, + ¤tIsRedundant](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: */ |