From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- desktop/source/deployment/manager/dp_manager.cxx | 1600 ++++++++++++++++++++++ 1 file changed, 1600 insertions(+) create mode 100644 desktop/source/deployment/manager/dp_manager.cxx (limited to 'desktop/source/deployment/manager/dp_manager.cxx') diff --git a/desktop/source/deployment/manager/dp_manager.cxx b/desktop/source/deployment/manager/dp_manager.cxx new file mode 100644 index 000000000..68765a63f --- /dev/null +++ b/desktop/source/deployment/manager/dp_manager.cxx @@ -0,0 +1,1600 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + +#include +#include +#include +#include +#include +#include +#include +#include "dp_manager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "dp_commandenvironments.hxx" +#include "dp_properties.hxx" + +#include +#include + +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 const & xCmdEnv, + Reference const & xContext) +{ + ::ucbhelper::Content tempFolder( parentFolder, xCmdEnv, xContext ); + Reference xResultSet( + StrTitle::createCursor (tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + OUString title; + if (xResultSet->next()) + { + title = Reference( + xResultSet, UNO_QUERY_THROW )->getString(1 /* Title */ ) ; + } + return title; +} +} + +void PackageManagerImpl::initActivationLayer( + Reference 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 xResultSet( + StrTitle::createCursor (ucbContent, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS ) ); + + while (xResultSet->next()) + { + Reference 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( + xResultSet, UNO_QUERY_THROW )->queryContent(), + xCmdEnv, m_xComponentContext ); + + OUString mediaType( detectMediaType( sourceContent, + false /* no throw */) ); + if (!mediaType.isEmpty()) + { + ActivePackages::Data dbData; + insertToActivationLayer( + Sequence(),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 xResultSet( + StrTitle::createCursor (tempFolder, + ::ucbhelper::INCLUDE_DOCUMENTS_ONLY ) ); + + // get all temp directories: + std::vector tempEntries; + std::vector removedEntries; + while (xResultSet->next()) + { + OUString title( + Reference( + 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(), m_xComponentContext); + std::vector data = dp_misc::readFile(remFileContent); + OString osData(reinterpret_cast(data.data()), + data.size()); + OUString sData = OStringToOUString( + osData, RTL_TEXTENCODING_UTF8); + if (sData != aUserName) + continue; + } + } + // temp entry not needed anymore: + erase_path( url + "_", + Reference(), + false /* no throw: ignore errors */ ); + erase_path( url, Reference(), + false /* no throw: ignore errors */ ); + //delete the xxx.tmpremoved file + erase_path(url + "removed", + Reference(), false); + } + } + } + } +} + + +void PackageManagerImpl::initRegistryBackends() +{ + if (!m_registryCache.isEmpty()) + create_folder( nullptr, m_registryCache, + Reference(), 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 PackageManagerImpl::create( + Reference const & xComponentContext, + OUString const & context ) +{ + rtl::Reference 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(), static_cast(-1) ); + } + + Reference 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(logger.getLogger()); + Reference xLogFormatter(SimpleTextFormatter::create(xComponentContext)); + Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} }; + Reference 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(), + 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(), exc ); + } +} + + +PackageManagerImpl::~PackageManagerImpl() +{ +} + + +void PackageManagerImpl::fireModified() +{ + ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer( + cppu::UnoType::get() ); + if (pContainer != nullptr) { + pContainer->forEach( + [this] (uno::Reference const& xListener) + { return xListener->modified(lang::EventObject(static_cast(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(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 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 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 > +PackageManagerImpl::getSupportedPackageTypes() +{ + OSL_ASSERT( m_xRegistry.is() ); + return m_xRegistry->getSupportedPackageTypes(); +} + + +Reference PackageManagerImpl::createAbortChannel() +{ + check(); + return new AbortChannel; +} + +// XModifyBroadcaster + +void PackageManagerImpl::addModifyListener( + Reference const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType::get(), xListener ); +} + + +void PackageManagerImpl::removeModifyListener( + Reference const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType::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 xPackage( + m_xRegistry->bindPackage( + url, OUString(), false, OUString(), ucbContent.getCommandEnvironment() ) ); + const Reference 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 const & properties, + OUString const & mediaType, ::ucbhelper::Content const & sourceContent_, + OUString const & title, ActivePackages::Data * dbData ) +{ + ::ucbhelper::Content sourceContent(sourceContent_); + Reference xCmdEnv( + sourceContent.getCommandEnvironment() ); + + OUString baseDir(m_activePackages_expanded); + ::utl::TempFile aTemp(&baseDir, false); + OUString tempEntry = aTemp.GetURL(); + 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 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 PackageManagerImpl::importExtension( + Reference const & extension, + Reference const & xAbortChannel, + Reference const & xCmdEnv_ ) +{ + return addPackage(extension->getURL(), Sequence(), + 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 PackageManagerImpl::addPackage( + OUString const & url, + css::uno::Sequence const & properties, + OUString const & mediaType_, + Reference const & xAbortChannel, + Reference 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(this), Any() ); + } + Reference 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 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 ); + + ::osl::MutexGuard 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(this), exc ); + } +} +void PackageManagerImpl::deletePackageFromCache( + Reference 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(), + false /* no throw: ignore errors */ ); + //rm last character '_' + OUString url = destFolder.copy(0, destFolder.getLength() - 1); + erase_path( url, Reference(), + false /* no throw: ignore errors */ ); + +} + +void PackageManagerImpl::removePackage( + OUString const & id, OUString const & fileName, + Reference const & /*xAbortChannel*/, + Reference const & xCmdEnv_ ) +{ + check(); + + Reference xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + Reference 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 xData( + ::xmlscript::createInputStream( + reinterpret_cast(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(this), exc ); + } +} + + +OUString PackageManagerImpl::getDeployPath( ActivePackages::Data const & data ) +{ + OUStringBuffer buf; + buf.append( 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( "_/" ); + buf.append( ::rtl::Uri::encode( data.fileName, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + return makeURL( m_activePackages, buf.makeStringAndClear() ); +} + + +Reference PackageManagerImpl::getDeployedPackage_( + OUString const & id, OUString const & fileName, + Reference 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(this), static_cast(-1) ); +} + + +Reference PackageManagerImpl::getDeployedPackage_( + std::u16string_view id, ActivePackages::Data const & data, + Reference const & xCmdEnv, bool ignoreAlienPlatforms ) +{ + if (ignoreAlienPlatforms) + { + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( data.mediaType, type, subType, ¶ms )) + { + auto const iter = params.find(OString("platform")); + if (iter != params.end() && !platform_fits(iter->second.m_sValue)) + throw lang::IllegalArgumentException( + DpResId(RID_STR_NO_SUCH_PACKAGE) + id, + static_cast(this), + static_cast(-1) ); + } + } + Reference 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 > +PackageManagerImpl::getDeployedPackages_( + Reference const & xCmdEnv ) +{ + std::vector< Reference > 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 PackageManagerImpl::getDeployedPackage( + OUString const & id, OUString const & fileName, + Reference const & xCmdEnv_ ) +{ + check(); + Reference 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(this), exc ); + } +} + + +Sequence< Reference > +PackageManagerImpl::getDeployedPackages( + Reference const &, + Reference const & xCmdEnv_ ) +{ + check(); + Reference 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(this), exc ); + } +} + + +//ToDo: the function must not call registerPackage, do this in +//XExtensionManager.reinstallDeployedExtensions +void PackageManagerImpl::reinstallDeployedPackages( + sal_Bool force, Reference const & /*xAbortChannel*/, + Reference const & xCmdEnv_ ) +{ + check(); + if (!force && office_is_running()) + throw RuntimeException( + "You must close any running Office process before reinstalling packages!", + static_cast(this) ); + + Reference 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 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(this), exc ); + } +} + + +sal_Bool SAL_CALL PackageManagerImpl::isReadOnly( ) +{ + return m_readOnly; +} +bool PackageManagerImpl::synchronizeRemovedExtensions( + Reference const & xAbortChannel, + Reference 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( 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(), 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(), 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 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 const & xAbortChannel, + Reference 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(), false); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (!bOk) + return bModified; + + ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext ); + Reference xResultSet( + StrTitle::createCursor( tempFolder, + ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + while (xResultSet->next()) + { + try + { + OUString title( + Reference( + 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(), false)) + continue; + sExtFolder = getExtensionFolder( + m_activePackages_expanded + "/" + titleEncoded + "_", + xCmdEnv, m_xComponentContext); + url = makeURLAppendSysPathSegment(m_activePackages_expanded, title); + url = makeURLAppendSysPathSegment(url, sExtFolder); + } + Reference 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 + attr = info.getSimpleLicenseAttributes(); + ExtensionProperties props(url, xCmdEnv, m_xComponentContext); + bool bNoLicense = false; + if (attr && attr->suppressIfRequired && props.isSuppressedLicense()) + bNoLicense = true; + + Reference 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 const & xAbortChannel, + Reference const & xCmdEnv) +{ + check(); + bool bModified = false; + if (m_context == "user") + return bModified; + bModified |= + synchronizeRemovedExtensions(xAbortChannel, xCmdEnv); + bModified |= synchronizeAddedExtensions(xAbortChannel, xCmdEnv); + + return bModified; +} + +Sequence< Reference > PackageManagerImpl::getExtensionsWithUnacceptedLicenses( + Reference const & xCmdEnv) +{ + std::vector > 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( url + "_", elem.second.fileName); + + Reference 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(this), exc); + exc <<= de; + ::cppu::throwException(exc); + } + + return ::comphelper::containerToSequence(vec); +} + +sal_Int32 PackageManagerImpl::checkPrerequisites( + css::uno::Reference const & extension, + css::uno::Reference const & xAbortChannel, + css::uno::Reference 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 _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(this), excOccurred); + throw exc; + } +} + + +PackageManagerImpl::CmdEnvWrapperImpl::~CmdEnvWrapperImpl() +{ +} + + +PackageManagerImpl::CmdEnvWrapperImpl::CmdEnvWrapperImpl( + Reference const & xUserCmdEnv, + Reference const & xLogFile ) + : m_xLogFile( xLogFile ) +{ + if (xUserCmdEnv.is()) { + m_xUserProgress.set( xUserCmdEnv->getProgressHandler() ); + m_xUserInteractionHandler.set( xUserCmdEnv->getInteractionHandler() ); + } +} + +// XCommandEnvironment + +Reference +PackageManagerImpl::CmdEnvWrapperImpl::getInteractionHandler() +{ + return m_xUserInteractionHandler; +} + + +Reference +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: */ -- cgit v1.2.3