diff options
Diffstat (limited to '')
-rw-r--r-- | desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx | 1115 |
1 files changed, 1115 insertions, 0 deletions
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 000000000..fd70b7982 --- /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: */ |