diff options
Diffstat (limited to '')
22 files changed, 8568 insertions, 0 deletions
diff --git a/ucb/source/ucp/tdoc/tdoc_content.cxx b/ucb/source/ucp/tdoc/tdoc_content.cxx new file mode 100644 index 000000000..9323380bb --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_content.cxx @@ -0,0 +1,2827 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#include <sal/config.h> + +#include <string_view> + +#include <o3tl/string_view.hxx> +#include <tools/diagnose_ex.h> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/task/DocumentPasswordRequest.hpp> +#include <com/sun/star/task/XInteractionPassword.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentAction.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp> +#include <com/sun/star/ucb/MissingInputStreamException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/UnsupportedNameClashException.hpp> +#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> +#include <com/sun/star/ucb/XPersistentPropertySet.hpp> + +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/macros.hxx> + +#include "tdoc_content.hxx" +#include "tdoc_resultset.hxx" +#include "tdoc_passwordrequest.hxx" + +#include "../inc/urihelper.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +static ContentType lcl_getContentType( std::u16string_view rType ) +{ + if ( rType == TDOC_ROOT_CONTENT_TYPE ) + return ROOT; + else if ( rType == TDOC_DOCUMENT_CONTENT_TYPE ) + return DOCUMENT; + else if ( rType == TDOC_FOLDER_CONTENT_TYPE ) + return FOLDER; + else if ( rType == TDOC_STREAM_CONTENT_TYPE ) + return STREAM; + else + { + OSL_FAIL( "Content::Content - unsupported content type string" ); + return STREAM; + } +} + + +// Content Implementation. + + +// static ( "virtual" ctor ) +rtl::Reference<Content> Content::create( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier ) +{ + // Fail, if resource does not exist. + ContentProperties aProps; + if ( !Content::loadData( pProvider, + Uri( Identifier->getContentIdentifier() ), + aProps ) ) + return nullptr; + + return new Content( rxContext, pProvider, Identifier, aProps ); +} + + +// static ( "virtual" ctor ) +rtl::Reference<Content> Content::create( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + const ucb::ContentInfo& Info ) +{ + if ( Info.Type.isEmpty() ) + return nullptr; + + if ( Info.Type != TDOC_FOLDER_CONTENT_TYPE && Info.Type != TDOC_STREAM_CONTENT_TYPE ) + { + OSL_FAIL( "Content::create - unsupported content type!" ); + return nullptr; + } + + return new Content( rxContext, pProvider, Identifier, Info ); +} + + +Content::Content( + const uno::Reference< uno::XComponentContext > & rxContext, + ContentProvider * pProvider, + const uno::Reference< ucb::XContentIdentifier > & Identifier, + const ContentProperties & rProps ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_aProps( rProps ), + m_eState( PERSISTENT ), + m_pProvider( pProvider ) +{ +} + + +// ctor for a content just created via XContentCreator::createNewContent() +Content::Content( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + const ucb::ContentInfo& Info ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_aProps( lcl_getContentType( Info.Type ), OUString() ), // no Title (yet) + m_eState( TRANSIENT ), + m_pProvider( pProvider ) +{ +} + + +// virtual +Content::~Content() +{ +} + + +// XInterface methods. + + +// virtual +void SAL_CALL Content::acquire() + noexcept +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + noexcept +{ + ContentImplHelper::release(); +} + + +// virtual +uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType ) +{ + uno::Any aRet = ContentImplHelper::queryInterface( rType ); + + if ( !aRet.hasValue() ) + { + aRet = cppu::queryInterface( + rType, static_cast< ucb::XContentCreator * >( this ) ); + if ( aRet.hasValue() ) + { + if ( !m_aProps.isContentCreator() ) + return uno::Any(); + } + } + + return aRet; +} + + +// XTypeProvider methods. + + +XTYPEPROVIDER_COMMON_IMPL( Content ); + + +// virtual +uno::Sequence< uno::Type > SAL_CALL Content::getTypes() +{ + if ( m_aProps.isContentCreator() ) + { + static cppu::OTypeCollection s_aFolderTypes( + CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ), + CPPU_TYPE_REF( ucb::XContentCreator ) ); + + return s_aFolderTypes.getTypes(); + } + else + { + static cppu::OTypeCollection s_aDocumentTypes( + CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ) ); + + return s_aDocumentTypes.getTypes(); + } +} + + +// XServiceInfo methods. + + +// virtual +OUString SAL_CALL Content::getImplementationName() +{ + return "com.sun.star.comp.ucb.TransientDocumentsContent"; +} + + +// virtual +uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< OUString > aSNS( 1 ); + + if ( m_aProps.getType() == STREAM ) + aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsStreamContent"; + else if ( m_aProps.getType() == FOLDER ) + aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsFolderContent"; + else if ( m_aProps.getType() == DOCUMENT ) + aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsDocumentContent"; + else + aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsRootContent"; + + return aSNS; +} + + +// XContent methods. + + +// virtual +OUString SAL_CALL Content::getContentType() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + return m_aProps.getContentType(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > SAL_CALL +Content::getIdentifier() +{ + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // Transient? + if ( m_eState == TRANSIENT ) + { + // Transient contents have no identifier. + return uno::Reference< ucb::XContentIdentifier >(); + } + } + return ContentImplHelper::getIdentifier(); +} + + +// XCommandProcessor methods. + + +// virtual +uno::Any SAL_CALL Content::execute( + const ucb::Command& aCommand, + sal_Int32 /*CommandId*/, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + uno::Any aRet; + + if ( aCommand.Name == "getPropertyValues" ) + { + + // getPropertyValues + + + uno::Sequence< beans::Property > Properties; + if ( !( aCommand.Argument >>= Properties ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= getPropertyValues( Properties ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + + // setPropertyValues + + + uno::Sequence< beans::PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= setPropertyValues( aProperties, Environment ); + } + else if ( aCommand.Name == "getPropertySetInfo" ) + { + + // getPropertySetInfo + + + aRet <<= getPropertySetInfo( Environment ); + } + else if ( aCommand.Name == "getCommandInfo" ) + { + + // getCommandInfo + + + aRet <<= getCommandInfo( Environment ); + } + else if ( aCommand.Name == "open" ) + { + + // open + + + ucb::OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet = open( aOpenCommand, Environment ); + } + else if ( aCommand.Name == "insert" ) + { + + // insert ( Supported by folders and streams only ) + + + ContentType eType = m_aProps.getType(); + if ( ( eType != FOLDER ) && ( eType != STREAM ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "insert command only supported by " + "folders and streams!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + if ( eType == STREAM ) + { + Uri aUri( m_xIdentifier->getContentIdentifier() ); + Uri aParentUri( aUri.getParentUri() ); + if ( aParentUri.isDocument() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "insert command not supported by " + "streams that are direct children " + "of document root!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::InsertCommandArgument aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + sal_Int32 nNameClash = aArg.ReplaceExisting + ? ucb::NameClash::OVERWRITE + : ucb::NameClash::ERROR; + insert( aArg.Data, nNameClash, Environment ); + } + else if ( aCommand.Name == "delete" ) + { + + // delete ( Supported by folders and streams only ) + + + { + osl::MutexGuard aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType != FOLDER ) && ( eType != STREAM ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "delete command only supported by " + "folders and streams!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + bool bDeletePhysical = false; + aCommand.Argument >>= bDeletePhysical; + destroy( bDeletePhysical, Environment ); + + // Remove own and all children's persistent data. + if ( !removeData() ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + Environment, + "Cannot remove persistent data!", + this ); + // Unreachable + } + + // Remove own and all children's Additional Core Properties. + removeAdditionalPropertySet(); + } + else if ( aCommand.Name == "transfer" ) + { + + // transfer ( Supported by document and folders only ) + + + { + osl::MutexGuard aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType != FOLDER ) && ( eType != DOCUMENT ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "transfer command only supported " + "by folders and documents!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::TransferInfo aInfo; + if ( !( aCommand.Argument >>= aInfo ) ) + { + OSL_FAIL( "Wrong argument type!" ); + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + transfer( aInfo, Environment ); + } + else if ( aCommand.Name == "createNewContent" ) + { + + // createNewContent ( Supported by document and folders only ) + + + { + osl::MutexGuard aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType != FOLDER ) && ( eType != DOCUMENT ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "createNewContent command only " + "supported by folders and " + "documents!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::ContentInfo aInfo; + if ( !( aCommand.Argument >>= aInfo ) ) + { + OSL_FAIL( "Wrong argument type!" ); + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aInfo ); + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + return aRet; +} + + +// virtual +void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ ) +{ +} + + +// XContentCreator methods. + + +// virtual +uno::Sequence< ucb::ContentInfo > SAL_CALL +Content::queryCreatableContentsInfo() +{ + return m_aProps.getCreatableContentsInfo(); +} + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +Content::createNewContent( const ucb::ContentInfo& Info ) +{ + if ( m_aProps.isContentCreator() ) + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( Info.Type.isEmpty() ) + return uno::Reference< ucb::XContent >(); + + bool bCreateFolder = Info.Type == TDOC_FOLDER_CONTENT_TYPE; + + // streams cannot be created as direct children of document root + if ( !bCreateFolder && ( m_aProps.getType() == DOCUMENT ) ) + { + OSL_FAIL( "Content::createNewContent - streams cannot be " + "created as direct children of document root!" ); + return uno::Reference< ucb::XContent >(); + } + if ( !bCreateFolder && Info.Type != TDOC_STREAM_CONTENT_TYPE ) + { + OSL_FAIL( "Content::createNewContent - unsupported type!" ); + return uno::Reference< ucb::XContent >(); + } + + OUString aURL = m_xIdentifier->getContentIdentifier(); + + OSL_ENSURE( !aURL.isEmpty(), + "Content::createNewContent - empty identifier!" ); + + if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() ) + aURL += "/"; + + if ( bCreateFolder ) + aURL += "New_Folder"; + else + aURL += "New_Stream"; + + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aURL ); + + return create( m_xContext, m_pProvider, xId, Info ); + } + else + { + OSL_FAIL( "createNewContent called on non-contentcreator object!" ); + return uno::Reference< ucb::XContent >(); + } +} + + +// virtual +OUString Content::getParentURL() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + Uri aUri( m_xIdentifier->getContentIdentifier() ); + return aUri.getParentUri(); +} + + +uno::Reference< ucb::XContentIdentifier > +Content::makeNewIdentifier( const OUString& rTitle ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // Assemble new content identifier... + Uri aUri( m_xIdentifier->getContentIdentifier() ); + OUString aNewURL = aUri.getParentUri() + ::ucb_impl::urihelper::encodeSegment( rTitle ); + + return + uno::Reference< ucb::XContentIdentifier >( + new ::ucbhelper::ContentIdentifier( aNewURL ) ); +} + + +void Content::queryChildren( ContentRefList& rChildren ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // Only folders (root, documents, folders) have children. + if ( !m_aProps.getIsFolder() ) + return; + + // Obtain a list with a snapshot of all currently instantiated contents + // from provider and extract the contents which are direct children + // of this content. + + ::ucbhelper::ContentRefList aAllContents; + m_xProvider->queryExistingContents( aAllContents ); + + OUString aURL = m_xIdentifier->getContentIdentifier(); + sal_Int32 nURLPos = aURL.lastIndexOf( '/' ); + + if ( nURLPos != ( aURL.getLength() - 1 ) ) + { + // No trailing slash found. Append. + aURL += "/"; + } + + sal_Int32 nLen = aURL.getLength(); + + for ( const auto& rContent : aAllContents ) + { + ::ucbhelper::ContentImplHelperRef xChild = rContent; + OUString aChildURL + = xChild->getIdentifier()->getContentIdentifier(); + + // Is aURL a prefix of aChildURL? + if ( ( aChildURL.getLength() > nLen ) && + ( aChildURL.startsWith( aURL ) ) ) + { + sal_Int32 nPos = aChildURL.indexOf( '/', nLen ); + + if ( ( nPos == -1 ) || + ( nPos == ( aChildURL.getLength() - 1 ) ) ) + { + // No further slashes / only a final slash. It's a child! + rChildren.emplace_back( + static_cast< Content * >( xChild.get() ) ); + } + } + } +} + + +bool Content::exchangeIdentity( + const uno::Reference< ucb::XContentIdentifier >& xNewId ) +{ + if ( !xNewId.is() ) + return false; + + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + uno::Reference< ucb::XContent > xThis = this; + + // Already persistent? + if ( m_eState != PERSISTENT ) + { + OSL_FAIL( "Content::exchangeIdentity - Not persistent!" ); + return false; + } + + // Only folders and streams can be renamed -> exchange identity. + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + OSL_FAIL( "Content::exchangeIdentity - " + "Not supported by root or document!" ); + return false; + } + + // Exchange own identity. + + // Fail, if a content with given id already exists. + if ( !hasData( Uri( xNewId->getContentIdentifier() ) ) ) + { + OUString aOldURL = m_xIdentifier->getContentIdentifier(); + + aGuard.clear(); + if ( exchange( xNewId ) ) + { + if ( eType == FOLDER ) + { + // Process instantiated children... + + ContentRefList aChildren; + queryChildren( aChildren ); + + for ( const auto& rChild : aChildren ) + { + ContentRef xChild = rChild; + + // Create new content identifier for the child... + uno::Reference< ucb::XContentIdentifier > xOldChildId + = xChild->getIdentifier(); + OUString aOldChildURL + = xOldChildId->getContentIdentifier(); + OUString aNewChildURL + = aOldChildURL.replaceAt( + 0, + aOldURL.getLength(), + xNewId->getContentIdentifier() ); + uno::Reference< ucb::XContentIdentifier > xNewChildId + = new ::ucbhelper::ContentIdentifier( aNewChildURL ); + + if ( !xChild->exchangeIdentity( xNewChildId ) ) + return false; + } + } + return true; + } + } + + OSL_FAIL( "Content::exchangeIdentity - " + "Panic! Cannot exchange identity!" ); + return false; +} + + +// static +uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Sequence< beans::Property >& rProperties, + ContentProvider* pProvider, + const OUString& rContentId ) +{ + ContentProperties aData; + if ( loadData( pProvider, Uri(rContentId), aData ) ) + { + return getPropertyValues( + rxContext, rProperties, aData, pProvider, rContentId ); + } + else + { + rtl::Reference< ::ucbhelper::PropertyValueSet > xRow + = new ::ucbhelper::PropertyValueSet( rxContext ); + + for ( const beans::Property& rProp : rProperties ) + xRow->appendVoid( rProp ); + + return xRow; + } +} + + +// static +uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Sequence< beans::Property >& rProperties, + const ContentProperties& rData, + ContentProvider* pProvider, + const OUString& rContentId ) +{ + // Note: Empty sequence means "get values of all supported properties". + + rtl::Reference< ::ucbhelper::PropertyValueSet > xRow + = new ::ucbhelper::PropertyValueSet( rxContext ); + + if ( rProperties.hasElements() ) + { + uno::Reference< beans::XPropertySet > xAdditionalPropSet; + bool bTriedToGetAdditionalPropSet = false; + + for ( const beans::Property& rProp : rProperties ) + { + // Process Core properties. + + if ( rProp.Name == "ContentType" ) + { + xRow->appendString ( rProp, rData.getContentType() ); + } + else if ( rProp.Name == "Title" ) + { + xRow->appendString ( rProp, rData.getTitle() ); + } + else if ( rProp.Name == "IsDocument" ) + { + xRow->appendBoolean( rProp, rData.getIsDocument() ); + } + else if ( rProp.Name == "IsFolder" ) + { + xRow->appendBoolean( rProp, rData.getIsFolder() ); + } + else if ( rProp.Name == "CreatableContentsInfo" ) + { + xRow->appendObject( + rProp, uno::Any( rData.getCreatableContentsInfo() ) ); + } + else if ( rProp.Name == "Storage" ) + { + // Storage is only supported by folders. + ContentType eType = rData.getType(); + if ( eType == FOLDER ) + xRow->appendObject( + rProp, + uno::Any( + pProvider->queryStorageClone( rContentId ) ) ); + else + xRow->appendVoid( rProp ); + } + else if ( rProp.Name == "DocumentModel" ) + { + // DocumentModel is only supported by documents. + ContentType eType = rData.getType(); + if ( eType == DOCUMENT ) + xRow->appendObject( + rProp, + uno::Any( + pProvider->queryDocumentModel( rContentId ) ) ); + else + xRow->appendVoid( rProp ); + } + else + { + // Not a Core Property! Maybe it's an Additional Core Property?! + + if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() ) + { + xAdditionalPropSet = + pProvider->getAdditionalPropertySet( rContentId, + false ); + bTriedToGetAdditionalPropSet = true; + } + + if ( xAdditionalPropSet.is() ) + { + if ( !xRow->appendPropertySetValue( + xAdditionalPropSet, + rProp ) ) + { + // Append empty entry. + xRow->appendVoid( rProp ); + } + } + else + { + // Append empty entry. + xRow->appendVoid( rProp ); + } + } + } + } + else + { + // Append all Core Properties. + xRow->appendString ( + beans::Property( "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + rData.getContentType() ); + + ContentType eType = rData.getType(); + + xRow->appendString ( + beans::Property( "Title", + -1, + cppu::UnoType<OUString>::get(), + // Title is read-only for root and documents. + beans::PropertyAttribute::BOUND | + ( ( eType == ROOT ) || ( eType == DOCUMENT ) + ? beans::PropertyAttribute::READONLY + : 0 ) ), + rData.getTitle() ); + xRow->appendBoolean( + beans::Property( "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + rData.getIsDocument() ); + xRow->appendBoolean( + beans::Property( "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + rData.getIsFolder() ); + xRow->appendObject( + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + uno::Any( rData.getCreatableContentsInfo() ) ); + + // Storage is only supported by folders. + if ( eType == FOLDER ) + xRow->appendObject( + beans::Property( "Storage", + -1, + cppu::UnoType<embed::XStorage>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + uno::Any( pProvider->queryStorageClone( rContentId ) ) ); + + // DocumentModel is only supported by documents. + if ( eType == DOCUMENT ) + xRow->appendObject( + beans::Property( "DocumentModel", + -1, + cppu::UnoType<frame::XModel>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ), + uno::Any( + pProvider->queryDocumentModel( rContentId ) ) ); + + // Append all Additional Core Properties. + + uno::Reference< beans::XPropertySet > xSet = + pProvider->getAdditionalPropertySet( rContentId, false ); + xRow->appendPropertySet( xSet ); + } + + return xRow; +} + + +uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Sequence< beans::Property >& rProperties ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + return getPropertyValues( m_xContext, + rProperties, + m_aProps, + m_pProvider, + m_xIdentifier->getContentIdentifier() ); +} + + +uno::Sequence< uno::Any > Content::setPropertyValues( + const uno::Sequence< beans::PropertyValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< uno::Any > aRet( rValues.getLength() ); + auto aRetRange = asNonConstRange(aRet); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + aEvent.Further = false; + // aEvent.PropertyName = + aEvent.PropertyHandle = -1; + // aEvent.OldValue = + // aEvent.NewValue = + + const beans::PropertyValue* pValues = rValues.getConstArray(); + sal_Int32 nCount = rValues.getLength(); + + uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet; + bool bTriedToGetAdditionalPropSet = false; + + bool bExchange = false; + OUString aOldTitle; + sal_Int32 nTitlePos = -1; + + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::PropertyValue& rValue = pValues[ n ]; + + if ( rValue.Name == "ContentType" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsDocument" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsFolder" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "CreatableContentsInfo" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "Title" ) + { + // Title is read-only for root and documents. + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + OUString aNewValue; + if ( rValue.Value >>= aNewValue ) + { + // No empty titles! + if ( !aNewValue.isEmpty() ) + { + if ( aNewValue != m_aProps.getTitle() ) + { + // modified title -> modified URL -> exchange ! + if ( m_eState == PERSISTENT ) + bExchange = true; + + aOldTitle = m_aProps.getTitle(); + m_aProps.setTitle( aNewValue ); + + // property change event will be sent later... + + // remember position within sequence of values + // (for error handling). + nTitlePos = n; + } + } + else + { + aRetRange[ n ] <<= lang::IllegalArgumentException( + "Empty Title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRetRange[ n ] <<= beans::IllegalTypeException( + "Title Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + else if ( rValue.Name == "Storage" ) + { + ContentType eType = m_aProps.getType(); + if ( eType == FOLDER ) + { + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + // Storage is only supported by folders. + aRetRange[ n ] <<= beans::UnknownPropertyException( + "Storage property only supported by folders", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else if ( rValue.Name == "DocumentModel" ) + { + ContentType eType = m_aProps.getType(); + if ( eType == DOCUMENT ) + { + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + // Storage is only supported by folders. + aRetRange[ n ] <<= beans::UnknownPropertyException( + "DocumentModel property only supported by documents", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + // Not a Core Property! Maybe it's an Additional Core Property?! + + if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() ) + { + xAdditionalPropSet = getAdditionalPropertySet( false ); + bTriedToGetAdditionalPropSet = true; + } + + if ( xAdditionalPropSet.is() ) + { + try + { + uno::Any aOldValue = xAdditionalPropSet->getPropertyValue( + rValue.Name ); + if ( aOldValue != rValue.Value ) + { + xAdditionalPropSet->setPropertyValue( + rValue.Name, rValue.Value ); + + aEvent.PropertyName = rValue.Name; + aEvent.OldValue = aOldValue; + aEvent.NewValue = rValue.Value; + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + } + catch ( beans::UnknownPropertyException const & e ) + { + aRetRange[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRetRange[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRetRange[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRetRange[ n ] <<= e; + } + } + else + { + aRetRange[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + + if ( bExchange ) + { + uno::Reference< ucb::XContentIdentifier > xOldId + = m_xIdentifier; + uno::Reference< ucb::XContentIdentifier > xNewId + = makeNewIdentifier( m_aProps.getTitle() ); + + aGuard.clear(); + if ( exchangeIdentity( xNewId ) ) + { + // Adapt persistent data. + renameData( xOldId, xNewId ); + + // Adapt Additional Core Properties. + renameAdditionalPropertySet( xOldId->getContentIdentifier(), + xNewId->getContentIdentifier() ); + } + else + { + // Roll-back. + m_aProps.setTitle( aOldTitle ); + aOldTitle.clear(); + + // Set error . + aRetRange[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + + if ( !aOldTitle.isEmpty() ) + { + aEvent.PropertyName = "Title"; + aEvent.OldValue <<= aOldTitle; + aEvent.NewValue <<= m_aProps.getTitle(); + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + + if ( nChanged > 0 ) + { + // Save changes, if content was already made persistent. + if ( !bExchange && ( m_eState == PERSISTENT ) ) + { + if ( !storeData( uno::Reference< io::XInputStream >(), xEnv ) ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot store persistent data!", + this ); + // Unreachable + } + } + + aChanges.realloc( nChanged ); + + aGuard.clear(); + notifyPropertiesChange( aChanges ); + } + + return aRet; +} + + +uno::Any Content::open( + const ucb::OpenCommandArgument2& rArg, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + if ( rArg.Mode == ucb::OpenMode::ALL || + rArg.Mode == ucb::OpenMode::FOLDERS || + rArg.Mode == ucb::OpenMode::DOCUMENTS ) + { + + // open command for a folder content + + + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet( m_xContext, this, rArg ); + return uno::Any( xSet ); + } + else + { + + // open command for a document content + + + if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) ) + { + // Currently(?) unsupported. + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + sal_Int16( rArg.Mode ) ) ), + xEnv ); + // Unreachable + } + + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Reference< io::XActiveDataStreamer > xDataStreamer( + rArg.Sink, uno::UNO_QUERY ); + if ( xDataStreamer.is() ) + { + // May throw CommandFailedException, DocumentPasswordRequest! + uno::Reference< io::XStream > xStream = getStream( xEnv ); + if ( !xStream.is() ) + { + // No interaction if we are not persistent! + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_READ, + aArgs, + m_eState == PERSISTENT + ? xEnv + : uno::Reference< ucb::XCommandEnvironment >(), + "Got no data stream!", + this ); + // Unreachable + } + + // Done. + xDataStreamer->setStream( xStream ); + } + else + { + uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY ); + if ( xOut.is() ) + { + // PUSH: write data into xOut + + // May throw CommandFailedException, DocumentPasswordRequest! + uno::Reference< io::XInputStream > xIn = getInputStream( xEnv ); + if ( !xIn.is() ) + { + // No interaction if we are not persistent! + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_READ, + aArgs, + m_eState == PERSISTENT + ? xEnv + : uno::Reference< ucb::XCommandEnvironment >(), + "Got no data stream!", + this ); + // Unreachable + } + + try + { + uno::Sequence< sal_Int8 > aBuffer; + + while (true) + { + sal_Int32 nRead = xIn->readSomeBytes( aBuffer, 65536 ); + if (!nRead) + break; + aBuffer.realloc( nRead ); + xOut->writeBytes( aBuffer ); + } + + xOut->closeOutput(); + } + catch ( io::NotConnectedException const & ) + { + // closeOutput, readSomeBytes, writeBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // closeOutput, readSomeBytes, writeBytes + } + catch ( io::IOException const & ) + { + // closeOutput, readSomeBytes, writeBytes + } + } + else + { + uno::Reference< io::XActiveDataSink > xDataSink( + rArg.Sink, uno::UNO_QUERY ); + if ( xDataSink.is() ) + { + // PULL: wait for client read + + // May throw CommandFailedException, DocumentPasswordRequest! + uno::Reference< io::XInputStream > xIn = getInputStream( xEnv ); + if ( !xIn.is() ) + { + // No interaction if we are not persistent! + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_READ, + aArgs, + m_eState == PERSISTENT + ? xEnv + : uno::Reference< + ucb::XCommandEnvironment >(), + "Got no data stream!", + this ); + // Unreachable + } + + // Done. + xDataSink->setInputStream( xIn ); + } + else + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArg.Sink ) ), + xEnv ); + // Unreachable + } + } + } + } + + return uno::Any(); +} + + +void Content::insert( const uno::Reference< io::XInputStream >& xData, + sal_Int32 nNameClashResolve, + const uno::Reference< + ucb::XCommandEnvironment > & xEnv ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + + OSL_ENSURE( ( eType == FOLDER ) || ( eType == STREAM ), + "insert command only supported by streams and folders!" ); + + Uri aUri( m_xIdentifier->getContentIdentifier() ); + +#if OSL_DEBUG_LEVEL > 0 + if ( eType == STREAM ) + { + Uri aParentUri( aUri.getParentUri() ); + OSL_ENSURE( !aParentUri.isDocument(), + "insert command not supported by streams that are direct " + "children of document root!" ); + } +#endif + + // Check, if all required properties were set. + if ( eType == FOLDER ) + { + // Required: Title + + if ( m_aProps.getTitle().isEmpty() ) + m_aProps.setTitle( aUri.getDecodedName() ); + } + else // stream + { + // Required: data + + if ( !xData.is() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Required: Title + + if ( m_aProps.getTitle().isEmpty() ) + m_aProps.setTitle( aUri.getDecodedName() ); + } + + Uri aNewUri( aUri.getParentUri() + m_aProps.getTitle() ); + + // Handle possible name clash... + switch ( nNameClashResolve ) + { + // fail. + case ucb::NameClash::ERROR: + if ( hasData( aNewUri ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + m_aProps.getTitle() ) ), + xEnv ); + // Unreachable + } + break; + + // replace (possibly) existing object. + case ucb::NameClash::OVERWRITE: + break; + + // "invent" a new valid title. + case ucb::NameClash::RENAME: + if ( hasData( aNewUri ) ) + { + sal_Int32 nTry = 0; + + do + { + OUStringBuffer aNew(aNewUri.getUri()); + aNew.append( "_" ); + aNew.append( ++nTry ); + aNewUri.setUri( aNew.makeStringAndClear() ); + } + while ( hasData( aNewUri ) && ( nTry < 1000 ) ); + + if ( nTry == 1000 ) + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::UnsupportedNameClashException( + "Unable to resolve name clash!", + static_cast< cppu::OWeakObject * >( this ), + nNameClashResolve ) ), + xEnv ); + // Unreachable + } + else + { + OUStringBuffer aNewTitle(m_aProps.getTitle()); + aNewTitle.append( "_" ); + aNewTitle.append( ++nTry ); + m_aProps.setTitle( aNewTitle.makeStringAndClear() ); + } + } + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::ASK: + default: + if ( hasData( aNewUri ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + nNameClashResolve ) ), + xEnv ); + // Unreachable + } + break; + } + + // Identifier changed? + bool bNewId = ( aUri != aNewUri ); + + if ( bNewId ) + { + m_xIdentifier + = new ::ucbhelper::ContentIdentifier( aNewUri.getUri() ); + } + + if ( !storeData( xData, xEnv ) ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot store persistent data!", + this ); + // Unreachable + } + + m_eState = PERSISTENT; + + if ( bNewId ) + { + //loadData( m_pProvider, m_aUri, m_aProps ); + + aGuard.clear(); + inserted(); + } +} + + +void Content::destroy( bool bDeletePhysical, + const uno::Reference< + ucb::XCommandEnvironment > & xEnv ) +{ + // @@@ take care about bDeletePhysical -> trashcan support + + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + + OSL_ENSURE( ( eType == FOLDER ) || ( eType == STREAM ), + "delete command only supported by streams and folders!" ); + + uno::Reference< ucb::XContent > xThis = this; + + // Persistent? + if ( m_eState != PERSISTENT ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + m_eState = DEAD; + + aGuard.clear(); + deleted(); + + if ( eType == FOLDER ) + { + // Process instantiated children... + + ContentRefList aChildren; + queryChildren( aChildren ); + + for ( auto& rChild : aChildren ) + { + rChild->destroy( bDeletePhysical, xEnv ); + } + } +} + + +void Content::notifyDocumentClosed() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + m_eState = DEAD; + + // @@@ anything else to reset or such? + + // callback follows! + aGuard.clear(); + + // Propagate destruction to content event listeners + // Remove this from provider's content list. + deleted(); +} + + +uno::Reference< ucb::XContent > +Content::queryChildContent( std::u16string_view rRelativeChildUri ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + const OUString aMyId = getIdentifier()->getContentIdentifier(); + OUStringBuffer aBuf( aMyId ); + if ( !aMyId.endsWith("/") ) + aBuf.append( "/" ); + if ( !o3tl::starts_with(rRelativeChildUri, u"/") ) + aBuf.append( rRelativeChildUri ); + else + aBuf.append( rRelativeChildUri.substr(1) ); + + uno::Reference< ucb::XContentIdentifier > xChildId + = new ::ucbhelper::ContentIdentifier( aBuf.makeStringAndClear() ); + + uno::Reference< ucb::XContent > xChild; + try + { + xChild = m_pProvider->queryContent( xChildId ); + } + catch ( ucb::IllegalIdentifierException const & ) + { + // handled below. + } + + OSL_ENSURE( xChild.is(), + "Content::queryChildContent - unable to create child content!" ); + return xChild; +} + + +void Content::notifyChildRemoved( std::u16string_view rRelativeChildUri ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + // Ugly! Need to create child content object, just to fill event properly. + uno::Reference< ucb::XContent > xChild + = queryChildContent( rRelativeChildUri ); + + if ( !xChild.is() ) + return; + + // callback follows! + aGuard.clear(); + + // Notify "REMOVED" event. + ucb::ContentEvent aEvt( + static_cast< cppu::OWeakObject * >( this ), + ucb::ContentAction::REMOVED, + xChild, + getIdentifier() ); + notifyContentEvent( aEvt ); +} + + +void Content::notifyChildInserted( std::u16string_view rRelativeChildUri ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + // Ugly! Need to create child content object, just to fill event properly. + uno::Reference< ucb::XContent > xChild + = queryChildContent( rRelativeChildUri ); + + if ( !xChild.is() ) + return; + + // callback follows! + aGuard.clear(); + + // Notify "INSERTED" event. + ucb::ContentEvent aEvt( + static_cast< cppu::OWeakObject * >( this ), + ucb::ContentAction::INSERTED, + xChild, + getIdentifier() ); + notifyContentEvent( aEvt ); +} + + +void Content::transfer( + const ucb::TransferInfo& rInfo, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + // Persistent? + if ( m_eState != PERSISTENT ) + { + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Does source URI scheme match? Only vnd.sun.star.tdoc is supported. + + if ( rInfo.SourceURL.getLength() < TDOC_URL_SCHEME_LENGTH + 2 ) + { + // Invalid length (to short). + ucbhelper::cancelCommandExecution( + uno::Any( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + OUString aScheme + = rInfo.SourceURL.copy( 0, TDOC_URL_SCHEME_LENGTH + 2 ) + .toAsciiLowerCase(); + if ( aScheme != TDOC_URL_SCHEME ":/" ) + { + // Invalid scheme. + ucbhelper::cancelCommandExecution( + uno::Any( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Does source URI describe a tdoc folder or stream? + Uri aSourceUri( rInfo.SourceURL ); + if ( !aSourceUri.isValid() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Invalid source URI! Syntax!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + + if ( aSourceUri.isRoot() || aSourceUri.isDocument() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Invalid source URI! Must describe a folder or stream!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + + // Is source not a parent of me / not me? + OUString aId = m_xIdentifier->getContentIdentifier(); + sal_Int32 nPos = aId.lastIndexOf( '/' ); + if ( nPos != ( aId.getLength() - 1 ) ) + { + // No trailing slash found. Append. + aId += "/"; + } + + if ( rInfo.SourceURL.getLength() <= aId.getLength() ) + { + if ( aId.startsWith( rInfo.SourceURL ) ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_RECURSIVE, + aArgs, + xEnv, + "Target is equal to or is a child of source!", + this ); + // Unreachable + } + } + + if ( m_aProps.getType() == DOCUMENT ) + { + bool bOK = false; + + uno::Reference< embed::XStorage > xStorage + = m_pProvider->queryStorage( + aSourceUri.getParentUri(), READ_WRITE_NOCREATE ); + if ( xStorage.is() ) + { + try + { + if ( xStorage->isStreamElement( aSourceUri.getDecodedName() ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Invalid source URI! " + "Streams cannot be created as " + "children of document root!", + static_cast< cppu::OWeakObject * >( + this ), + -1 ) ), + xEnv ); + // Unreachable + } + bOK = true; + } + catch ( container::NoSuchElementException const & ) + { + // handled below. + } + catch ( lang::IllegalArgumentException const & ) + { + // handled below. + } + catch ( embed::InvalidStorageException const & ) + { + // handled below. + } + } + + if ( !bOK ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Invalid source URI! Unable to determine source type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + } + + + // Copy data. + + + OUString aNewName( !rInfo.NewTitle.isEmpty() + ? rInfo.NewTitle + : aSourceUri.getDecodedName() ); + + if ( !copyData( aSourceUri, aNewName ) ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot copy data!", + this ); + // Unreachable + } + + + // Copy own and all children's Additional Core Properties. + + + OUString aTargetUri = m_xIdentifier->getContentIdentifier(); + if ( ( aTargetUri.lastIndexOf( '/' ) + 1 ) != aTargetUri.getLength() ) + aTargetUri += "/"; + + if ( !rInfo.NewTitle.isEmpty() ) + aTargetUri += ::ucb_impl::urihelper::encodeSegment( rInfo.NewTitle ); + else + aTargetUri += aSourceUri.getName(); + + if ( !copyAdditionalPropertySet( aSourceUri.getUri(), aTargetUri ) ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot copy additional properties!", + this ); + // Unreachable + } + + + // Propagate new content. + + + rtl::Reference< Content > xTarget; + try + { + uno::Reference< ucb::XContentIdentifier > xTargetId + = new ::ucbhelper::ContentIdentifier( aTargetUri ); + + // Note: The static cast is okay here, because its sure that + // m_xProvider is always the WebDAVContentProvider. + xTarget = static_cast< Content * >( + m_pProvider->queryContent( xTargetId ).get() ); + + } + catch ( ucb::IllegalIdentifierException const & ) + { + // queryContent + } + + if ( !xTarget.is() ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(aTargetUri)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_READ, + aArgs, + xEnv, + "Cannot instantiate target object!", + this ); + // Unreachable + } + + // Announce transferred content in its new folder. + xTarget->inserted(); + + + // Remove source, if requested + + + if ( !rInfo.MoveData ) + return; + + rtl::Reference< Content > xSource; + try + { + uno::Reference< ucb::XContentIdentifier > + xSourceId = new ::ucbhelper::ContentIdentifier( rInfo.SourceURL ); + + // Note: The static cast is okay here, because its sure + // that m_xProvider is always the ContentProvider. + xSource = static_cast< Content * >( + m_xProvider->queryContent( xSourceId ).get() ); + } + catch ( ucb::IllegalIdentifierException const & ) + { + // queryContent + } + + if ( !xSource.is() ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_READ, + aArgs, + xEnv, + "Cannot instantiate target object!", + this ); + // Unreachable + } + + // Propagate destruction (recursively). + xSource->destroy( true, xEnv ); + + // Remove all persistent data of source and its children. + if ( !xSource->removeData() ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot remove persistent data of source object!", + this ); + // Unreachable + } + + // Remove own and all children's Additional Core Properties. + if ( xSource->removeAdditionalPropertySet() ) + return; + + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Uri", uno::Any(rInfo.SourceURL)} + })); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_CANT_WRITE, + aArgs, + xEnv, + "Cannot remove additional properties of source object!", + this ); + // Unreachable +} + + +//static +bool Content::hasData( ContentProvider const * pProvider, const Uri & rUri ) +{ + if ( rUri.isRoot() ) + { + return true; // root has no storage + } + else if ( rUri.isDocument() ) + { + uno::Reference< embed::XStorage > xStorage + = pProvider->queryStorage( rUri.getUri(), READ ); + return xStorage.is(); + } + else + { + // folder or stream + + // Ask parent storage. In case that rUri describes a stream, + // ContentProvider::queryStorage( rUri ) would return null. + + uno::Reference< embed::XStorage > xStorage + = pProvider->queryStorage( rUri.getParentUri(), READ ); + + if ( !xStorage.is() ) + return false; + + return xStorage->hasByName( rUri.getDecodedName() ); + } +} + + +//static +bool Content::loadData( ContentProvider const * pProvider, + const Uri & rUri, + ContentProperties& rProps ) +{ + if ( rUri.isRoot() ) // root has no storage, but can always be created + { + rProps + = ContentProperties( + ROOT, pProvider->queryStorageTitle( rUri.getUri() ) ); + } + else if ( rUri.isDocument() ) // document must have storage + { + uno::Reference< embed::XStorage > xStorage + = pProvider->queryStorage( rUri.getUri(), READ ); + + if ( !xStorage.is() ) + return false; + + rProps + = ContentProperties( + DOCUMENT, pProvider->queryStorageTitle( rUri.getUri() ) ); + } + else // stream or folder; stream has no storage; folder has storage + { + uno::Reference< embed::XStorage > xStorage + = pProvider->queryStorage( rUri.getParentUri(), READ ); + + if ( !xStorage.is() ) + return false; + + // Check whether exists at all, is stream or folder + try + { + // return: true -> folder + // return: false -> stream + // NoSuchElementException -> neither folder nor stream + bool bIsFolder + = xStorage->isStorageElement( rUri.getDecodedName() ); + + rProps + = ContentProperties( + bIsFolder ? FOLDER : STREAM, + pProvider->queryStorageTitle( rUri.getUri() ) ); + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with such name + //OSL_ENSURE( false, "Caught NoSuchElementException!" ); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( embed::InvalidStorageException const & ) + { + // this storage is in invalid state for any reason + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + } + return true; +} + + +bool Content::storeData( const uno::Reference< io::XInputStream >& xData, + const uno::Reference< + ucb::XCommandEnvironment >& xEnv ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + OSL_FAIL( "storeData not supported by root and documents!" ); + return false; + } + + Uri aUri( m_xIdentifier->getContentIdentifier() ); + + if ( eType == FOLDER ) + { + uno::Reference< embed::XStorage > xStorage + = m_pProvider->queryStorage( aUri.getUri(), READ_WRITE_CREATE ); + + if ( !xStorage.is() ) + return false; + + uno::Reference< beans::XPropertySet > xPropSet( + xStorage, uno::UNO_QUERY ); + OSL_ENSURE( xPropSet.is(), + "Content::storeData - Got no XPropertySet interface!" ); + if ( !xPropSet.is() ) + return false; + + try + { + // According to MBA, if no mediatype is set, folder and all + // its contents will be lost on save of the document!!! + xPropSet->setPropertyValue( + "MediaType", + uno::Any( + OUString( // @@@ better mediatype + "application/binary" ) ) ); + } + catch ( beans::UnknownPropertyException const & ) + { + OSL_FAIL( "Property MediaType not supported!" ); + return false; + } + catch ( beans::PropertyVetoException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + + if ( !commitStorage( xStorage ) ) + return false; + } + else if ( eType == STREAM ) + { + // stream + + // Important: Parent storage and output stream must be kept alive until + // changes have been committed! + uno::Reference< embed::XStorage > xStorage + = m_pProvider->queryStorage( + aUri.getParentUri(), READ_WRITE_CREATE ); + uno::Reference< io::XOutputStream > xOut; + + if ( !xStorage.is() ) + return false; + + if ( xData.is() ) + { + // May throw CommandFailedException, DocumentPasswordRequest! + xOut = getTruncatedOutputStream( xEnv ); + + OSL_ENSURE( xOut.is(), "No target data stream!" ); + + try + { + uno::Sequence< sal_Int8 > aBuffer; + while (true) + { + sal_Int32 nRead = xData->readSomeBytes( aBuffer, 65536 ); + if (!nRead) + break; + aBuffer.realloc( nRead ); + xOut->writeBytes( aBuffer ); + } + + closeOutputStream( xOut ); + } + catch ( io::NotConnectedException const & ) + { + // readSomeBytes, writeBytes + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + closeOutputStream( xOut ); + return false; + } + catch ( io::BufferSizeExceededException const & ) + { + // readSomeBytes, writeBytes + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + closeOutputStream( xOut ); + return false; + } + catch ( io::IOException const & ) + { + // readSomeBytes, writeBytes + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + closeOutputStream( xOut ); + return false; + } + catch ( ... ) + { + closeOutputStream( xOut ); + throw; + } + } + + // Commit changes. + if ( !commitStorage( xStorage ) ) + return false; + } + else + { + OSL_FAIL( "Unknown content type!" ); + return false; + } + return true; +} + + +void Content::renameData( + const uno::Reference< ucb::XContentIdentifier >& xOldId, + const uno::Reference< ucb::XContentIdentifier >& xNewId ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + OSL_FAIL( "renameData not supported by root and documents!" ); + return; + } + + Uri aOldUri( xOldId->getContentIdentifier() ); + uno::Reference< embed::XStorage > xStorage + = m_pProvider->queryStorage( + aOldUri.getParentUri(), READ_WRITE_NOCREATE ); + + if ( !xStorage.is() ) + return; + + try + { + Uri aNewUri( xNewId->getContentIdentifier() ); + xStorage->renameElement( + aOldUri.getDecodedName(), aNewUri.getDecodedName() ); + } + catch ( embed::InvalidStorageException const & ) + { + // this storage is in invalid state for any reason + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with old name in this storage + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + catch ( container::ElementExistException const & ) + { + // an element with new name already exists in this storage + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return; + } + + commitStorage( xStorage ); +} + + +bool Content::removeData() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + OSL_FAIL( "removeData not supported by root and documents!" ); + return false; + } + + Uri aUri( m_xIdentifier->getContentIdentifier() ); + uno::Reference< embed::XStorage > xStorage + = m_pProvider->queryStorage( + aUri.getParentUri(), READ_WRITE_NOCREATE ); + + if ( !xStorage.is() ) + return false; + + try + { + xStorage->removeElement( aUri.getDecodedName() ); + } + catch ( embed::InvalidStorageException const & ) + { + // this storage is in invalid state for any reason + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with this name in this storage + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + + return commitStorage( xStorage ); +} + + +bool Content::copyData( const Uri & rSourceUri, const OUString & rNewName ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == STREAM ) ) + { + OSL_FAIL( "copyData not supported by root and streams!" ); + return false; + } + + Uri aDestUri( m_xIdentifier->getContentIdentifier() ); + uno::Reference< embed::XStorage > xDestStorage + = m_pProvider->queryStorage( aDestUri.getUri(), READ_WRITE_NOCREATE ); + + if ( !xDestStorage.is() ) + return false; + + uno::Reference< embed::XStorage > xSourceStorage + = m_pProvider->queryStorage( rSourceUri.getParentUri(), READ ); + + if ( !xSourceStorage.is() ) + return false; + + try + { + xSourceStorage->copyElementTo( rSourceUri.getDecodedName(), + xDestStorage, + rNewName ); + } + catch ( embed::InvalidStorageException const & ) + { + // this storage is in invalid state for any reason + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with this name in this storage + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( container::ElementExistException const & ) + { + // there is no element with this name in this storage + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + + return commitStorage( xDestStorage ); +} + + +// static +bool Content::commitStorage( const uno::Reference< embed::XStorage > & xStorage ) +{ + // Commit changes + uno::Reference< embed::XTransactedObject > xTO( xStorage, uno::UNO_QUERY ); + + OSL_ENSURE( xTO.is(), + "Required interface css.embed.XTransactedObject missing!" ); + try + { + xTO->commit(); + } + catch ( io::IOException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + return false; + } + + return true; +} + + +// static +bool Content::closeOutputStream( + const uno::Reference< io::XOutputStream > & xOut ) +{ + if ( xOut.is() ) + { + try + { + xOut->closeOutput(); + return true; + } + catch ( io::NotConnectedException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::BufferSizeExceededException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + } + return false; +} + +/// @throws ucb::CommandFailedException +/// @throws task::DocumentPasswordRequest +static OUString obtainPassword( + const OUString & rName, + task::PasswordRequestMode eMode, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + rtl::Reference< DocumentPasswordRequest > xRequest + = new DocumentPasswordRequest( eMode, rName ); + + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = xEnv->getInteractionHandler(); + if ( xIH.is() ) + { + xIH->handle( xRequest ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( xAbort.is() ) + { + throw ucb::CommandFailedException( + "Abort requested by Interaction Handler.", + uno::Reference< uno::XInterface >(), + xRequest->getRequest() ); + } + + uno::Reference< task::XInteractionPassword > xPassword( + xSelection.get(), uno::UNO_QUERY ); + if ( xPassword.is() ) + { + return xPassword->getPassword(); + } + + // Unknown selection. Should never happen. + throw ucb::CommandFailedException( + "Interaction Handler selected unknown continuation!", + uno::Reference< uno::XInterface >(), + xRequest->getRequest() ); + } + } + } + + // No IH or IH did not handle exception. + task::DocumentPasswordRequest aRequest; + xRequest->getRequest() >>= aRequest; + throw aRequest; +} + + +uno::Reference< io::XInputStream > Content::getInputStream( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + OUString aUri; + OUString aPassword; + bool bPasswordRequested = false; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + OSL_ENSURE( m_aProps.getType() == STREAM, + "Content::getInputStream - content is no stream!" ); + + aUri = Uri( m_xIdentifier->getContentIdentifier() ).getUri(); + } + + for ( ;; ) + { + try + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + return m_pProvider->queryInputStream( aUri, aPassword ); + } + catch ( packages::WrongPasswordException const & ) + { + // Obtain (new) password. + aPassword + = obtainPassword( aUri, /* @@@ find better title */ + bPasswordRequested + ? task::PasswordRequestMode_PASSWORD_REENTER + : task::PasswordRequestMode_PASSWORD_ENTER, + xEnv ); + bPasswordRequested = true; + } + } +} + +/// @throws ucb::CommandFailedException +/// @throws task::DocumentPasswordRequest +/// @throws uno::RuntimeException +static uno::Reference< io::XOutputStream > lcl_getTruncatedOutputStream( + const OUString & rUri, + ContentProvider const * pProvider, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + OUString aPassword; + bool bPasswordRequested = false; + for ( ;; ) + { + try + { + return pProvider->queryOutputStream( + rUri, aPassword, true /* truncate */ ); + } + catch ( packages::WrongPasswordException const & ) + { + // Obtain (new) password. + aPassword + = obtainPassword( rUri, /* @@@ find better title */ + bPasswordRequested + ? task::PasswordRequestMode_PASSWORD_REENTER + : task::PasswordRequestMode_PASSWORD_ENTER, + xEnv ); + bPasswordRequested = true; + } + } +} + + +uno::Reference< io::XOutputStream > Content::getTruncatedOutputStream( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + OSL_ENSURE( m_aProps.getType() == STREAM, + "Content::getTruncatedOutputStream - content is no stream!" ); + + return lcl_getTruncatedOutputStream( + Uri( m_xIdentifier->getContentIdentifier() ).getUri(), + m_pProvider, + xEnv ); +} + + +uno::Reference< io::XStream > Content::getStream( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + OSL_ENSURE( m_aProps.getType() == STREAM, + "Content::getStream - content is no stream!" ); + + OUString aUri( Uri( m_xIdentifier->getContentIdentifier() ).getUri() ); + OUString aPassword; + bool bPasswordRequested = false; + for ( ;; ) + { + try + { + return m_pProvider->queryStream( + aUri, aPassword, false /* no truncate */ ); + } + catch ( packages::WrongPasswordException const & ) + { + // Obtain (new) password. + aPassword + = obtainPassword( aUri, /* @@@ find better title */ + bPasswordRequested + ? task::PasswordRequestMode_PASSWORD_REENTER + : task::PasswordRequestMode_PASSWORD_ENTER, + xEnv ); + bPasswordRequested = true; + } + } +} + + +// ContentProperties Implementation. + + +uno::Sequence< ucb::ContentInfo > +ContentProperties::getCreatableContentsInfo() const +{ + if ( isContentCreator() ) + { + uno::Sequence< beans::Property > aProps( 1 ); + aProps.getArray()[ 0 ] = beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ); + + if ( getType() == DOCUMENT ) + { + // streams cannot be created as direct children of document root + uno::Sequence< ucb::ContentInfo > aSeq( 1 ); + + // Folder. + aSeq.getArray()[ 0 ].Type = TDOC_FOLDER_CONTENT_TYPE; + aSeq.getArray()[ 0 ].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER; + aSeq.getArray()[ 0 ].Properties = aProps; + + return aSeq; + } + else + { + uno::Sequence< ucb::ContentInfo > aSeq( 2 ); + + // Folder. + aSeq.getArray()[ 0 ].Type = TDOC_FOLDER_CONTENT_TYPE; + aSeq.getArray()[ 0 ].Attributes + = ucb::ContentInfoAttribute::KIND_FOLDER; + aSeq.getArray()[ 0 ].Properties = aProps; + + // Stream. + aSeq.getArray()[ 1 ].Type = TDOC_STREAM_CONTENT_TYPE; + aSeq.getArray()[ 1 ].Attributes + = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM + | ucb::ContentInfoAttribute::KIND_DOCUMENT; + aSeq.getArray()[ 1 ].Properties = aProps; + + return aSeq; + } + } + else + { + OSL_FAIL( "getCreatableContentsInfo called on non-contentcreator " + "object!" ); + + return uno::Sequence< ucb::ContentInfo >( 0 ); + } +} + + +bool ContentProperties::isContentCreator() const +{ + return ( getType() == FOLDER ) || ( getType() == DOCUMENT ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_content.hxx b/ucb/source/ucp/tdoc/tdoc_content.hxx new file mode 100644 index 000000000..1412ebd9a --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_content.hxx @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <ucbhelper/contenthelper.hxx> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include "tdoc_provider.hxx" + +namespace com::sun::star { + namespace sdbc { class XRow; } + namespace io { class XInputStream; class XOutputStream; } + namespace beans { struct PropertyValue; } + namespace ucb { struct OpenCommandArgument2; struct TransferInfo; + struct ContentInfo; } +} + +namespace tdoc_ucp +{ + + +enum ContentType { STREAM, FOLDER, DOCUMENT, ROOT }; + +class ContentProperties +{ +public: + ContentProperties() + : m_eType( STREAM ) + {} + + ContentProperties( const ContentType & rType, const OUString & rTitle ) + : m_eType( rType ), + m_aContentType( rType == STREAM + ? OUString( TDOC_STREAM_CONTENT_TYPE ) + : rType == FOLDER + ? OUString( TDOC_FOLDER_CONTENT_TYPE ) + : rType == DOCUMENT + ? OUString( TDOC_DOCUMENT_CONTENT_TYPE ) + : OUString( TDOC_ROOT_CONTENT_TYPE ) ), + m_aTitle( rTitle ) + {} + + ContentType getType() const { return m_eType; } + + // Properties + + const OUString & getContentType() const { return m_aContentType; } + + bool getIsFolder() const { return m_eType > STREAM; } + bool getIsDocument() const { return !getIsFolder(); } + + const OUString & getTitle() const { return m_aTitle; } + void setTitle( const OUString & rTitle ) { m_aTitle = rTitle; } + + css::uno::Sequence< css::ucb::ContentInfo > + getCreatableContentsInfo() const; + + bool isContentCreator() const; + +private: + ContentType m_eType; + OUString m_aContentType; + OUString m_aTitle; +}; + + +class Content : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator +{ + enum ContentState { TRANSIENT, // created via createNewContent, + // but did not process "insert" yet + PERSISTENT, // processed "insert" + DEAD // processed "delete" / document was closed + }; + + ContentProperties m_aProps; + ContentState m_eState; + ContentProvider* m_pProvider; + +private: + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + const ContentProperties & rProps ); + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + const css::ucb::ContentInfo& Info ); + + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual OUString getParentURL() override; + + static bool hasData( ContentProvider const * pProvider, const Uri & rUri ); + bool hasData( const Uri & rUri ) const { return hasData( m_pProvider, rUri ); } + + static bool loadData( ContentProvider const * pProvider, + const Uri & rUri, + ContentProperties& rProps ); + /// @throws css::ucb::CommandFailedException + /// @throws css::task::DocumentPasswordRequest + /// @throws css::uno::RuntimeException + bool storeData( const css::uno::Reference< css::io::XInputStream >& xData, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + void renameData( const css::uno::Reference< css::ucb::XContentIdentifier >& xOldId, + const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId ); + bool removeData(); + + bool copyData( const Uri & rSourceUri, const OUString & rNewName ); + + css::uno::Reference< css::ucb::XContentIdentifier > + makeNewIdentifier( const OUString& rTitle ); + + typedef rtl::Reference< Content > ContentRef; + typedef std::vector< ContentRef > ContentRefList; + void queryChildren( ContentRefList& rChildren ); + + bool exchangeIdentity( + const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId ); + + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties ); + css::uno::Sequence< css::uno::Any > + /// @throws css::uno::Exception + setPropertyValues( + const css::uno::Sequence< css::beans::PropertyValue >& rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + css::uno::Any + open( const css::ucb::OpenCommandArgument2& rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + void insert( const css::uno::Reference< css::io::XInputStream >& xData, + sal_Int32 nNameClashResolve, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + void destroy( bool bDeletePhysical, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + void transfer( const css::ucb::TransferInfo& rInfo, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + static css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::beans::Property >& rProperties, + const ContentProperties& rData, + ContentProvider* pProvider, + const OUString& rContentId ); + + + static bool commitStorage( + const css::uno::Reference< css::embed::XStorage > & xStorage ); + + static bool closeOutputStream( + const css::uno::Reference< css::io::XOutputStream > & xOut ); + + /// @throws css::ucb::CommandFailedException + /// @throws css::task::DocumentPasswordRequest + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XInputStream > + getInputStream( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::ucb::CommandFailedException + /// @throws css::task::DocumentPasswordRequest + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XOutputStream > + getTruncatedOutputStream( + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + css::uno::Reference< css::ucb::XContent > + queryChildContent( std::u16string_view rRelativeChildUri ); + + /// @throws css::ucb::CommandFailedException + /// @throws css::task::DocumentPasswordRequest + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XStream > + getStream( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + +public: + // Create existing content. Fail, if not already exists. + static rtl::Reference<Content> create( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ); + + // Create new content. Fail, if already exists. + static rtl::Reference<Content> create( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + const css::ucb::ContentInfo& Info ); + + virtual ~Content() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL + getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XContent + virtual OUString SAL_CALL + getContentType() override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL + getIdentifier() override; + + // XCommandProcessor + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + virtual void SAL_CALL + abort( sal_Int32 CommandId ) override; + + + // Additional interfaces + + + // XContentCreator + virtual css::uno::Sequence< css::ucb::ContentInfo > SAL_CALL + queryCreatableContentsInfo() override; + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createNewContent( const css::ucb::ContentInfo& Info ) override; + + + // Non-interface methods. + + + static css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::beans::Property >& rProperties, + ContentProvider* pProvider, + const OUString& rContentId ); + + void notifyDocumentClosed(); + void notifyChildRemoved( std::u16string_view rRelativeChildUri ); + void notifyChildInserted( std::u16string_view rRelativeChildUri ); + + rtl::Reference< ContentProvider > getContentProvider() const + { return rtl::Reference< ContentProvider >( m_pProvider ); } +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx b/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx new file mode 100644 index 000000000..8b19f9ec9 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_contentcaps.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 . + */ + + +/************************************************************************** + TODO + ************************************************************************** + + ************************************************************************** + + Props/Commands: + + root document folder folder stream stream + (new) (new) + ---------------------------------------------------------------- + ContentType r r r r r r + IsDocument r r r r r r + IsFolder r r r r r r + Title r r w w w w + CreatableContentsInfo r r r r r r + Storage - - r r - - + DocumentModel - r - - - - + + getCommandInfo x x x x x x + getPropertySetInfo x x x x x x + getPropertyValues x x x x x x + setPropertyValues x x x x x x + insert - - x x x(*) x(*) + delete - - x - x - + open x x x - x - + transfer - x x - - - + createNewContent - x x - - - + + + *************************************************************************/ + +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/ucb/CommandInfo.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <osl/diagnose.h> +#include <sal/macros.h> +#include "tdoc_content.hxx" + +namespace com::sun::star::embed { + class XStorage; +} + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// Content implementation. + + +#define MAKEPROPSEQUENCE( a ) \ + uno::Sequence< beans::Property >( a, SAL_N_ELEMENTS(a) ) + +#define MAKECMDSEQUENCE( a ) \ + uno::Sequence< ucb::CommandInfo >( a, SAL_N_ELEMENTS(a) ) + + +// IMPORTANT: If any property data ( name / type / ... ) are changed, then +// Content::getPropertyValues(...) must be adapted too! + + +// virtual +uno::Sequence< beans::Property > Content::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_aProps.getType() == STREAM ) + { + + + // Stream: Supported properties + + + static const beans::Property aStreamPropertyInfoTable[] = + { + + // Mandatory properties + + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + ), + + // Optional standard properties + + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ) + + // New properties + + }; + return MAKEPROPSEQUENCE( aStreamPropertyInfoTable ); + } + else if ( m_aProps.getType() == FOLDER ) + { + + + // Folder: Supported properties + + + static const beans::Property aFolderPropertyInfoTable[] = + { + + // Mandatory properties + + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + ), + + // Optional standard properties + + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + + // New properties + + beans::Property( + "Storage", + -1, + cppu::UnoType<embed::XStorage>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ) + }; + return MAKEPROPSEQUENCE( aFolderPropertyInfoTable ); + } + else if ( m_aProps.getType() == DOCUMENT ) + { + + + // Document: Supported properties + + + static const beans::Property aDocPropertyInfoTable[] = + { + + // Mandatory properties + + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + + // Optional standard properties + + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + + // New properties + + beans::Property( + "DocumentModel", + -1, + cppu::UnoType<frame::XModel>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ) + }; + return MAKEPROPSEQUENCE( aDocPropertyInfoTable ); + } + else + { + + + // Root: Supported properties + + + OSL_ENSURE( m_aProps.getType() == ROOT, "Wrong content type!" ); + + static const beans::Property aRootPropertyInfoTable[] = + { + + // Mandatory properties + + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + + // Optional standard properties + + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ) + + // New properties + + }; + return MAKEPROPSEQUENCE( aRootPropertyInfoTable ); + } +} + + +// virtual +uno::Sequence< ucb::CommandInfo > Content::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_aProps.getType() == STREAM ) + { + Uri aUri( m_xIdentifier->getContentIdentifier() ); + Uri aParentUri( aUri.getParentUri() ); + + if ( aParentUri.isDocument() ) + { + + + // Stream, that is a child of a document: Supported commands + + + static const ucb::CommandInfo aStreamCommandInfoTable1[] = + { + + // Mandatory commands + + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() + ), + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() + ), + + // Optional standard commands + + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() + ), + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ) + + // New commands + + }; + return MAKECMDSEQUENCE( aStreamCommandInfoTable1 ); + } + + + // Stream: Supported commands + + + static const ucb::CommandInfo aStreamCommandInfoTable[] = + { + + // Mandatory commands + + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType< uno::Sequence< beans::Property >>::get() + ), + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() + ), + + // Optional standard commands + + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() + ), + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ) + + // New commands + + }; + return MAKECMDSEQUENCE( aStreamCommandInfoTable ); + } + else if ( m_aProps.getType() == FOLDER ) + { + + + // Folder: Supported commands + + + static const ucb::CommandInfo aFolderCommandInfoTable[] = + { + + // Mandatory commands + + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() + ), + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() + ), + + // Optional standard commands + + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() + ), + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ), + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() + ), + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() + ) + + // New commands + + }; + return MAKECMDSEQUENCE( aFolderCommandInfoTable ); + } + else if ( m_aProps.getType() == DOCUMENT ) + { + + + // Document: Supported commands + + + static const ucb::CommandInfo aDocCommandInfoTable[] = + { + + // Mandatory commands + + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() + ), + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() + ), + + // Optional standard commands + + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ), + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() + ), + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() + ) + + // New commands + + }; + return MAKECMDSEQUENCE( aDocCommandInfoTable ); + } + else + { + + + // Root: Supported commands + + + OSL_ENSURE( m_aProps.getType() == ROOT, "Wrong content type!" ); + + static const ucb::CommandInfo aRootCommandInfoTable[] = + { + + // Mandatory commands + + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() + ), + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() + ), + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() + ), + + // Optional standard commands + + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ) + + // New commands + + }; + return MAKECMDSEQUENCE( aRootCommandInfoTable ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx b/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx new file mode 100644 index 000000000..8873e1da2 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#include <optional> + +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <osl/diagnose.h> +#include <ucbhelper/contentidentifier.hxx> + +#include "tdoc_datasupplier.hxx" +#include "tdoc_content.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + +namespace tdoc_ucp +{ + + +// struct ResultListEntry. + +namespace { + + +} + +// struct DataSupplier_Impl. + + +} + +// DataSupplier Implementation. +ResultSetDataSupplier::ResultSetDataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ) +: m_xContent( rContent ), m_xContext( rxContext ), + m_bCountFinal( false ), m_bThrowException( false ) +{ +} + +// virtual +ResultSetDataSupplier::~ResultSetDataSupplier() +{ +} + +// virtual +OUString +ResultSetDataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( nIndex < m_aResults.size() ) + { + OUString aId = m_aResults[ nIndex ].aURL; + if ( !aId.isEmpty() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + // Note: getResult fills m_pImpl->m_aResults[ nIndex ]->aURL. + return m_aResults[ nIndex ].aURL; + } + return OUString(); +} + +// virtual +uno::Reference< ucb::XContentIdentifier > +ResultSetDataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( nIndex < m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_aResults[ nIndex ].xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_aResults[ nIndex ].xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + +// virtual +uno::Reference< ucb::XContent > +ResultSetDataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( nIndex < m_aResults.size() ) + { + uno::Reference< ucb::XContent > xContent + = m_aResults[ nIndex ].xContent; + if ( xContent.is() ) + { + // Already cached. + return xContent; + } + } + + uno::Reference< ucb::XContentIdentifier > xId + = queryContentIdentifier( nIndex ); + if ( xId.is() ) + { + try + { + uno::Reference< ucb::XContent > xContent + = m_xContent->getProvider()->queryContent( xId ); + m_aResults[ nIndex ].xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException const & ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + +// virtual +bool ResultSetDataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Result not (yet) present. + + if ( m_bCountFinal ) + return false; + + // Try to obtain result... + + sal_uInt32 nOldCount = m_aResults.size(); + bool bFound = false; + + if ( queryNamesOfChildren() ) + { + for ( sal_uInt32 n = nOldCount; + n < sal::static_int_cast<sal_uInt32>( + m_xNamesOfChildren->getLength()); + ++n ) + { + const OUString & rName + = m_xNamesOfChildren->getConstArray()[ n ]; + + if ( rName.isEmpty() ) + { + OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" ); + break; + } + + // Assemble URL for child. + OUString aURL = assembleChildURL( rName ); + + m_aResults.emplace_back( aURL ); + + if ( n == nIndex ) + { + // Result obtained. + bFound = true; + break; + } + } + } + + if ( !bFound ) + m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_aResults.size() ) + xResultSet->rowCountChanged( nOldCount, m_aResults.size() ); + + if ( m_bCountFinal ) + xResultSet->rowCountFinal(); + } + + return bFound; +} + +// virtual +sal_uInt32 ResultSetDataSupplier::totalCount() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_bCountFinal ) + return m_aResults.size(); + + sal_uInt32 nOldCount = m_aResults.size(); + + if ( queryNamesOfChildren() ) + { + for ( sal_uInt32 n = nOldCount; + n < sal::static_int_cast<sal_uInt32>( + m_xNamesOfChildren->getLength()); + ++n ) + { + const OUString & rName + = m_xNamesOfChildren->getConstArray()[ n ]; + + if ( rName.isEmpty() ) + { + OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" ); + break; + } + + // Assemble URL for child. + OUString aURL = assembleChildURL( rName ); + + m_aResults.emplace_back( aURL ); + } + } + + m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_aResults.size() ) + xResultSet->rowCountChanged( nOldCount, m_aResults.size() ); + + xResultSet->rowCountFinal(); + } + + return m_aResults.size(); +} + +// virtual +sal_uInt32 ResultSetDataSupplier::currentCount() +{ + return m_aResults.size(); +} + +// virtual +bool ResultSetDataSupplier::isCountFinal() +{ + return m_bCountFinal; +} + +// virtual +uno::Reference< sdbc::XRow > +ResultSetDataSupplier::queryPropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( nIndex < m_aResults.size() ) + { + uno::Reference< sdbc::XRow > xRow = m_aResults[ nIndex ].xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow = Content::getPropertyValues( + m_xContext, + getResultSet()->getProperties(), + m_xContent->getContentProvider().get(), + queryContentIdentifierString( nIndex ) ); + m_aResults[ nIndex ].xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + +// virtual +void ResultSetDataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( nIndex < m_aResults.size() ) + m_aResults[ nIndex ].xRow.clear(); +} + +// virtual +void ResultSetDataSupplier::close() +{ +} + +// virtual +void ResultSetDataSupplier::validate() +{ + if ( m_bThrowException ) + throw ucb::ResultSetException(); +} + +bool ResultSetDataSupplier::queryNamesOfChildren() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( !m_xNamesOfChildren ) + { + uno::Sequence< OUString > aNamesOfChildren; + + if ( !m_xContent->getContentProvider()->queryNamesOfChildren( + m_xContent->getIdentifier()->getContentIdentifier(), + aNamesOfChildren ) ) + { + OSL_FAIL( "Got no list of children!" ); + m_bThrowException = true; + return false; + } + else + { + m_xNamesOfChildren = std::move( aNamesOfChildren ); + } + } + return true; +} + +OUString +ResultSetDataSupplier::assembleChildURL( std::u16string_view aName ) +{ + OUString aContURL + = m_xContent->getIdentifier()->getContentIdentifier(); + OUString aURL( aContURL ); + + sal_Int32 nUrlEnd = aURL.lastIndexOf( '/' ); + if ( nUrlEnd != aURL.getLength() - 1 ) + aURL += "/"; + + aURL += aName; + return aURL; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx b/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx new file mode 100644 index 000000000..e9f138002 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.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 <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> +#include <optional> +#include <vector> +#include <string_view> + +namespace tdoc_ucp { + +struct DataSupplier_Impl; +class Content; + +class ResultSetDataSupplier : public ::ucbhelper::ResultSetDataSupplier +{ + struct ResultListEntry + { + OUString aURL; + css::uno::Reference< css::ucb::XContentIdentifier > xId; + css::uno::Reference< css::ucb::XContent > xContent; + css::uno::Reference< css::sdbc::XRow > xRow; + + explicit ResultListEntry( const OUString& rURL ) : aURL( rURL ) {} + }; + + osl::Mutex m_aMutex; + std::vector< ResultListEntry > m_aResults; + rtl::Reference< Content > m_xContent; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::optional<css::uno::Sequence< OUString > > m_xNamesOfChildren; + bool m_bCountFinal; + bool m_bThrowException; + +private: + bool queryNamesOfChildren(); + OUString assembleChildURL( std::u16string_view aName ); + +public: + ResultSetDataSupplier( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ); + virtual ~ResultSetDataSupplier() override; + + virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > + queryContentIdentifier( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContent > + queryContent( sal_uInt32 nIndex ) override; + + virtual bool getResult( sal_uInt32 nIndex ) override; + + virtual sal_uInt32 totalCount() override; + virtual sal_uInt32 currentCount() override; + virtual bool isCountFinal() override; + + virtual css::uno::Reference< css::sdbc::XRow > + queryPropertyValues( sal_uInt32 nIndex ) override; + virtual void releasePropertyValues( sal_uInt32 nIndex ) override; + + virtual void close() override; + + virtual void validate() override; +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_docmgr.cxx b/ucb/source/ucp/tdoc/tdoc_docmgr.cxx new file mode 100644 index 000000000..1ecc77520 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_docmgr.cxx @@ -0,0 +1,657 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <rtl/ref.hxx> +#include <tools/diagnose_ex.h> + +#include <comphelper/documentinfo.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/sequence.hxx> + +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/frame/UnknownModuleException.hpp> +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/NotInitializedException.hpp> +#include <com/sun/star/util/XCloseBroadcaster.hpp> + +#include "tdoc_docmgr.hxx" +#include "tdoc_provider.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + +// OfficeDocumentsCloseListener Implementation. + + +// util::XCloseListener + + +// virtual +void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::queryClosing( + const lang::EventObject& /*Source*/, sal_Bool /*GetsOwnership*/ ) +{ +} + + +void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::notifyClosing( + const lang::EventObject& Source ) +{ + if (!m_pManager) return; // disposed? + + document::DocumentEvent aDocEvent; + aDocEvent.Source = Source.Source; + aDocEvent.EventName = "OfficeDocumentsListener::notifyClosing"; + m_pManager->documentEventOccured( aDocEvent ); +} + + +// lang::XDocumentEventListener (base of util::XCloseListener) + + +// virtual +void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::disposing( + const lang::EventObject& /*Source*/ ) +{ +} + + +// OfficeDocumentsManager Implementation. + + +OfficeDocumentsManager::OfficeDocumentsManager( + const uno::Reference< uno::XComponentContext > & rxContext, + ContentProvider * pDocEventListener ) +: m_xContext( rxContext ), + m_xDocEvtNotifier( frame::theGlobalEventBroadcaster::get( rxContext ) ), + m_pDocEventListener( pDocEventListener ), + m_xDocCloseListener( new OfficeDocumentsCloseListener( this ) ) +{ + // Order is important (multithreaded environment) + uno::Reference< document::XDocumentEventBroadcaster >( + m_xDocEvtNotifier, uno::UNO_QUERY_THROW )->addDocumentEventListener( this ); + buildDocumentsList(); +} + + +// virtual +OfficeDocumentsManager::~OfficeDocumentsManager() +{ + //OSL_ENSURE( m_aDocs.empty(), "document list not empty!" ); + // no need to assert this: Normal shutdown of LibreOffice could already trigger it, since the order + // in which objects are actually released/destroyed upon shutdown is not defined. And when we + // arrive *here*, LibreOffice *is* shutting down currently, since we're held by the TDOC provider, + // which is disposed upon shutdown. + m_xDocCloseListener->Dispose(); +} + + +void OfficeDocumentsManager::destroy() +{ + uno::Reference< document::XDocumentEventBroadcaster >( + m_xDocEvtNotifier, uno::UNO_QUERY_THROW )->removeDocumentEventListener( this ); +} + + +static OUString +getDocumentId( const uno::Reference< uno::XInterface > & xDoc ) +{ + OUString aId; + + // Try to get the UID directly from the document. + uno::Reference< beans::XPropertySet > xPropSet( xDoc, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + uno::Any aValue = xPropSet->getPropertyValue("RuntimeUID"); + aValue >>= aId; + } + catch ( beans::UnknownPropertyException const & ) + { + // Not actually an error. Property is optional. + } + catch ( lang::WrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", "Caught WrappedTargetException!"); + } + } + + if ( aId.isEmpty() ) + { + // fallback: generate UID from document's this pointer. + // normalize the interface pointer first. Else, calls with different + // interfaces to the same object (say, XFoo and XBar) will produce + // different IDs + uno::Reference< uno::XInterface > xNormalizedIFace( xDoc, uno::UNO_QUERY ); + sal_Int64 nId = reinterpret_cast< sal_Int64 >( xNormalizedIFace.get() ); + aId = OUString::number( nId ); + } + + OSL_ENSURE( !aId.isEmpty(), "getDocumentId - Empty id!" ); + return aId; +} + + +// document::XDocumentEventListener + + +// virtual +void SAL_CALL OfficeDocumentsManager::documentEventOccured( + const document::DocumentEvent & Event ) +{ +/* + Events documentation: OOo Developer's Guide / Writing UNO Components / + Integrating Components into OpenOffice.org / Jobs +*/ + + if ( Event.EventName == "OnLoadFinished" // document loaded + || Event.EventName == "OnCreate" ) // document created + { + if ( isOfficeDocument( Event.Source ) ) + { + uno::Reference<frame::XModel> const xModel( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xModel.is(), "Got no frame::XModel!" ); + + bool found(false); + + { + std::scoped_lock aGuard( m_aMtx ); + + found = std::any_of(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + } + + if (!found) + { + // no mutex to avoid deadlocks! + // need no lock to access const members, ContentProvider is safe + + // new document + + uno::Reference< document::XStorageBasedDocument > + xDoc( Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xDoc.is(), "Got no document::XStorageBasedDocument!" ); + + uno::Reference< embed::XStorage > xStorage + = xDoc->getDocumentStorage(); + OSL_ENSURE( xStorage.is(), "Got no document storage!" ); + + rtl:: OUString aDocId = getDocumentId( Event.Source ); + rtl:: OUString aTitle = comphelper::DocumentInfo::getDocumentTitle( + uno::Reference< frame::XModel >( Event.Source, uno::UNO_QUERY ) ); + + { + std::scoped_lock g(m_aMtx); + m_aDocs[ aDocId ] = StorageInfo( aTitle, xStorage, xModel ); + } + + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xCloseBroadcaster.is(), + "OnLoadFinished/OnCreate event: got no close broadcaster!" ); + + if ( xCloseBroadcaster.is() ) + xCloseBroadcaster->addCloseListener(m_xDocCloseListener); + + // Propagate document closure. + OSL_ENSURE( m_pDocEventListener, + "OnLoadFinished/OnCreate event: no owner for insert event propagation!" ); + + if ( m_pDocEventListener ) + m_pDocEventListener->notifyDocumentOpened( aDocId ); + } + } + } + else if ( Event.EventName == "OfficeDocumentsListener::notifyClosing" ) + { + if ( isOfficeDocument( Event.Source ) ) + { + // Document has been closed (unloaded) + + // Official event "OnUnload" does not work here. Event + // gets fired too early. Other OnUnload listeners called after this + // listener may still need TDOC access to the document. Remove the + // document from TDOC docs list on XCloseListener::notifyClosing. + // See OfficeDocumentsManager::OfficeDocumentsListener::notifyClosing. + + uno::Reference< frame::XModel > + xModel( Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xModel.is(), "Got no frame::XModel!" ); + + bool found(false); + OUString aDocId; + + { + std::scoped_lock aGuard( m_aMtx ); + + auto it = std::find_if(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + if ( it != m_aDocs.end() ) + { + aDocId = (*it).first; + found = true; + m_aDocs.erase( it ); + } + } + + OSL_ENSURE( found, + "OnUnload event notified for unknown document!" ); + + if (found) + { + // Propagate document closure. + OSL_ENSURE( m_pDocEventListener, + "OnUnload event: no owner for close event propagation!" ); + if (m_pDocEventListener) + { + m_pDocEventListener->notifyDocumentClosed(aDocId); + } + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xCloseBroadcaster.is(), + "OnUnload event: got no XCloseBroadcaster from XModel" ); + if ( xCloseBroadcaster.is() ) + xCloseBroadcaster->removeCloseListener(m_xDocCloseListener); + } + } + } + else if ( Event.EventName == "OnSaveDone" ) + { + if ( isOfficeDocument( Event.Source ) ) + { + // Storage gets exchanged while saving. + uno::Reference<document::XStorageBasedDocument> const xDoc( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xDoc.is(), + "Got no document::XStorageBasedDocument!" ); + uno::Reference<embed::XStorage> const xStorage( + xDoc->getDocumentStorage()); + OSL_ENSURE( xStorage.is(), "Got no document storage!" ); + + uno::Reference< frame::XModel > + xModel( Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xModel.is(), "Got no frame::XModel!" ); + + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + + OSL_ENSURE( it != m_aDocs.end(), + "OnSaveDone event notified for unknown document!" ); + if ( it != m_aDocs.end() ) + { + (*it).second.xStorage = xStorage; + } + } + } + else if ( Event.EventName == "OnSaveAsDone" ) + { + if ( isOfficeDocument( Event.Source ) ) + { + // Storage gets exchanged while saving. + uno::Reference<document::XStorageBasedDocument> const xDoc( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xDoc.is(), + "Got no document::XStorageBasedDocument!" ); + uno::Reference<embed::XStorage> const xStorage( + xDoc->getDocumentStorage()); + OSL_ENSURE( xStorage.is(), "Got no document storage!" ); + + uno::Reference< frame::XModel > + xModel( Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xModel.is(), "Got no frame::XModel!" ); + + OUString const title(comphelper::DocumentInfo::getDocumentTitle(xModel)); + + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + + OSL_ENSURE( it != m_aDocs.end(), + "OnSaveAsDone event notified for unknown document!" ); + if ( it != m_aDocs.end() ) + { + (*it).second.xStorage = xStorage; + + // Adjust title. + (*it).second.aTitle = title; + } + } + } + else if ( Event.EventName == "OnTitleChanged" + || Event.EventName == "OnStorageChanged" ) + { + if ( isOfficeDocument( Event.Source ) ) + { + // Storage gets exchanged while saving. + uno::Reference<document::XStorageBasedDocument> const xDoc( + Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xDoc.is(), + "Got no document::XStorageBasedDocument!" ); + uno::Reference<embed::XStorage> const xStorage( + xDoc->getDocumentStorage()); + OSL_ENSURE( xStorage.is(), "Got no document storage!" ); + + uno::Reference< frame::XModel > + xModel( Event.Source, uno::UNO_QUERY ); + OSL_ENSURE( xModel.is(), "Got no frame::XModel!" ); + + OUString const aTitle(comphelper::DocumentInfo::getDocumentTitle(xModel)); + + OUString const aDocId(getDocumentId(Event.Source)); + + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + if ( it != m_aDocs.end() ) + { + // Adjust title. + (*it).second.aTitle = aTitle; + + m_aDocs[ aDocId ] = StorageInfo( aTitle, xStorage, xModel ); + } + +// OSL_ENSURE( it != m_aDocs.end(), +// "TitleChanged event notified for unknown document!" ); + // TODO: re-enable this assertion. It has been disabled for now, since it breaks the assertion-free smoketest, + // and the fix is more difficult than what can be done now. + // The problem is that at the moment, when you close a SFX-based document via API, it will first + // fire the notifyClosing event, which will make the OfficeDocumentsManager remove the doc from its list. + // Then, it will notify an OnTitleChanged, then an OnUnload. Documents closed via call the notifyClosing + // *after* OnUnload and all other On* events. + // In agreement with MBA, the implementation for SfxBaseModel::Close should be changed to also send notifyClosing + // as last event. When this happens, the assertion here must be enabled, again. + } + } +} + +// lang::XDocumentEventListener (base of document::XDocumentEventListener) + +// virtual +void SAL_CALL OfficeDocumentsManager::disposing( + const lang::EventObject& /*Source*/ ) +{ +} + +// Non-interface. + +void OfficeDocumentsManager::buildDocumentsList() +{ + uno::Reference< container::XEnumeration > xEnum + = m_xDocEvtNotifier->createEnumeration(); + + while ( xEnum->hasMoreElements() ) + { + uno::Any aValue = xEnum->nextElement(); + // container::NoSuchElementException + // lang::WrappedTargetException + + try + { + uno::Reference< frame::XModel > xModel; + aValue >>= xModel; + + if ( xModel.is() ) + { + if ( isOfficeDocument( xModel ) ) + { + bool found(false); + + { + std::scoped_lock aGuard( m_aMtx ); + + found = std::any_of(m_aDocs.begin(), m_aDocs.end(), + [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; }); + } + + if (!found) + { + // new document + OUString aDocId = getDocumentId( xModel ); + OUString aTitle = comphelper::DocumentInfo::getDocumentTitle( xModel ); + + uno::Reference< document::XStorageBasedDocument > + xDoc( xModel, uno::UNO_QUERY ); + OSL_ENSURE( xDoc.is(), + "Got no document::XStorageBasedDocument!" ); + + uno::Reference< embed::XStorage > xStorage + = xDoc->getDocumentStorage(); + OSL_ENSURE( xStorage.is(), "Got no document storage!" ); + + { + std::scoped_lock aGuard( m_aMtx ); + m_aDocs[ aDocId ] + = StorageInfo( aTitle, xStorage, xModel ); + } + + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( + xModel, uno::UNO_QUERY ); + OSL_ENSURE( xCloseBroadcaster.is(), + "buildDocumentsList: got no close broadcaster!" ); + + if ( xCloseBroadcaster.is() ) + xCloseBroadcaster->addCloseListener(m_xDocCloseListener); + } + } + } + } + catch ( lang::DisposedException const & ) + { + // Note: Due to race conditions the XEnumeration can + // contain docs that have already been closed + } + catch ( lang::NotInitializedException const & ) + { + // Note: Due to race conditions the XEnumeration can + // contain docs that are still uninitialized + } + } +} + +uno::Reference< embed::XStorage > +OfficeDocumentsManager::queryStorage( const OUString & rDocId ) +{ + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::const_iterator it = m_aDocs.find( rDocId ); + if ( it == m_aDocs.end() ) + return uno::Reference< embed::XStorage >(); + + return (*it).second.xStorage; +} + + +OUString OfficeDocumentsManager::queryDocumentId( + const uno::Reference< frame::XModel > & xModel ) +{ + return getDocumentId( xModel ); +} + + +uno::Reference< frame::XModel > +OfficeDocumentsManager::queryDocumentModel( const OUString & rDocId ) +{ + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::const_iterator it = m_aDocs.find( rDocId ); + if ( it == m_aDocs.end() ) + return uno::Reference< frame::XModel >(); + + return (*it).second.xModel; +} + + +uno::Sequence< OUString > OfficeDocumentsManager::queryDocuments() +{ + std::scoped_lock aGuard( m_aMtx ); + + return comphelper::mapKeysToSequence( m_aDocs ); +} + + +OUString +OfficeDocumentsManager::queryStorageTitle( const OUString & rDocId ) +{ + std::scoped_lock aGuard( m_aMtx ); + + DocumentList::const_iterator it = m_aDocs.find( rDocId ); + if ( it == m_aDocs.end() ) + return OUString(); + + return (*it).second.aTitle; +} + + +bool OfficeDocumentsManager::isDocumentPreview( + const uno::Reference< frame::XModel > & xModel ) +{ + if ( !xModel.is() ) + return false; + + bool bIsPreview = ::comphelper::NamedValueCollection::getOrDefault( xModel->getArgs(), u"Preview", false ); + return bIsPreview; +} + + +bool OfficeDocumentsManager::isHelpDocument( + const uno::Reference< frame::XModel > & xModel ) +{ + if ( !xModel.is() ) + return false; + + OUString sURL( xModel->getURL() ); + return sURL.match( "vnd.sun.star.help://" ); +} + + +bool OfficeDocumentsManager::isWithoutOrInTopLevelFrame( + const uno::Reference< frame::XModel > & xModel ) +{ + if ( !xModel.is() ) + return false; + + uno::Reference< frame::XController > xController + = xModel->getCurrentController(); + if ( xController.is() ) + { + uno::Reference< frame::XFrame > xFrame + = xController->getFrame(); + if ( xFrame.is() ) + { + // don't use XFrame::isTop here. This nowadays excludes + // "sub documents" such as forms embedded in database documents + uno::Reference< awt::XTopWindow > xFrameContainer( + xFrame->getContainerWindow(), uno::UNO_QUERY ); + if ( !xFrameContainer.is() ) + return false; + } + } + + return true; +} + + +bool OfficeDocumentsManager::isBasicIDE( + const uno::Reference< frame::XModel > & xModel ) +{ + if ( !m_xModuleMgr.is() ) + { + std::scoped_lock aGuard( m_aMtx ); + if ( !m_xModuleMgr.is() ) + { + try + { + m_xModuleMgr = frame::ModuleManager::create( m_xContext ); + } + catch ( uno::Exception const & ) + { + // handled below. + } + + OSL_ENSURE( m_xModuleMgr .is(), + "Could not instantiate ModuleManager service!" ); + } + } + + if ( m_xModuleMgr.is() ) + { + OUString aModule; + try + { + aModule = m_xModuleMgr->identify( xModel ); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( frame::UnknownModuleException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + + if ( !aModule.isEmpty() ) + { + // Filter unwanted items, that are no real documents. + if ( aModule == "com.sun.star.script.BasicIDE" ) + { + return true; + } + } + } + + return false; +} + + +bool OfficeDocumentsManager::isOfficeDocument( + const uno::Reference< uno::XInterface > & xDoc ) +{ + uno::Reference< frame::XModel > xModel( xDoc, uno::UNO_QUERY ); + uno::Reference< document::XStorageBasedDocument > + xStorageBasedDoc( xModel, uno::UNO_QUERY ); + if ( !xStorageBasedDoc.is() ) + return false; + + if ( !isWithoutOrInTopLevelFrame( xModel ) ) + return false; + + if ( isDocumentPreview( xModel ) ) + return false; + + if ( isHelpDocument( xModel ) ) + return false; + + if ( isBasicIDE( xModel ) ) + return false; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_docmgr.hxx b/ucb/source/ucp/tdoc/tdoc_docmgr.hxx new file mode 100644 index 000000000..37839f985 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_docmgr.hxx @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ref.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/document/XDocumentEventListener.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/frame/XGlobalEventBroadcaster.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XCloseListener.hpp> + +#include <map> +#include <mutex> + +namespace tdoc_ucp { + + class ContentProvider; + + struct StorageInfo + { + OUString aTitle; + css::uno::Reference< css::embed::XStorage > xStorage; + css::uno::Reference< css::frame::XModel > xModel; + + StorageInfo() {}; // needed for STL map only. + + StorageInfo( + const OUString & rTitle, + const css::uno::Reference< css::embed::XStorage > & rxStorage, + const css::uno::Reference< css::frame::XModel > & rxModel ) + : aTitle( rTitle ), xStorage( rxStorage ), xModel( rxModel ) {} + }; + + + typedef std::map< OUString, StorageInfo > DocumentList; + + + class OfficeDocumentsManager : + public cppu::WeakImplHelper< css::document::XDocumentEventListener > + { + class OfficeDocumentsCloseListener : + public cppu::WeakImplHelper< css::util::XCloseListener > + + { + public: + explicit OfficeDocumentsCloseListener( OfficeDocumentsManager * pMgr ) + : m_pManager( pMgr ) {} + + // util::XCloseListener + virtual void SAL_CALL queryClosing( + const css::lang::EventObject& Source, + sal_Bool GetsOwnership ) override; + + virtual void SAL_CALL notifyClosing( + const css::lang::EventObject& Source ) override; + + // lang::XEventListener (base of util::XCloseListener) + virtual void SAL_CALL disposing( + const css::lang::EventObject & Source ) override; + + void Dispose() { m_pManager = nullptr; } + + private: + OfficeDocumentsManager * m_pManager; + }; + + public: + OfficeDocumentsManager( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + ContentProvider * pDocEventListener ); + virtual ~OfficeDocumentsManager() override; + + void destroy(); + + // document::XDocumentEventListener + virtual void SAL_CALL documentEventOccured( + const css::document::DocumentEvent & Event ) override; + + // lang::XEventListener (base of document::XDocumentEventListener) + virtual void SAL_CALL disposing( + const css::lang::EventObject & Source ) override; + + // Non-interface + css::uno::Reference< css::embed::XStorage > + queryStorage( const OUString & rDocId ); + + static OUString queryDocumentId( + const css::uno::Reference< css::frame::XModel > & xModel ); + + css::uno::Reference< css::frame::XModel > + queryDocumentModel( const OUString & rDocId ); + + css::uno::Sequence< OUString > + queryDocuments(); + + OUString + queryStorageTitle( const OUString & rDocId ); + + private: + void buildDocumentsList(); + + bool isOfficeDocument( + const css::uno::Reference< css::uno::XInterface > & xDoc ); + + static bool isDocumentPreview( + const css::uno::Reference< css::frame::XModel > & xModel ); + + static bool isWithoutOrInTopLevelFrame( + const css::uno::Reference< css::frame::XModel > & xModel ); + + bool + isBasicIDE( + const css::uno::Reference< css::frame::XModel > & xModel ); + + static bool isHelpDocument( + const css::uno::Reference< css::frame::XModel > & xModel ); + + std::mutex m_aMtx; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xDocEvtNotifier; + css::uno::Reference< css::frame::XModuleManager2 > m_xModuleMgr; + DocumentList m_aDocs; + ContentProvider * const m_pDocEventListener; + ::rtl::Reference<OfficeDocumentsCloseListener> const m_xDocCloseListener; + }; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx new file mode 100644 index 000000000..cd40d6d0e --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> + +#include "tdoc_documentcontentfactory.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// DocumentContentFactory Implementation. + + +DocumentContentFactory::DocumentContentFactory( + const uno::Reference< uno::XComponentContext >& rxContext ) +: m_xContext( rxContext ) +{ +} + + +// virtual +DocumentContentFactory::~DocumentContentFactory() +{ +} + + +// XServiceInfo methods. + + +// virtual +OUString SAL_CALL DocumentContentFactory::getImplementationName() +{ + return "com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory"; +} + +// virtual +sal_Bool SAL_CALL +DocumentContentFactory::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +// virtual +uno::Sequence< OUString > SAL_CALL +DocumentContentFactory::getSupportedServiceNames() +{ + return { "com.sun.star.frame.TransientDocumentsDocumentContentFactory" }; +} + + +// XTransientDocumentsDocumentContentFactory methods. + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +DocumentContentFactory::createDocumentContent( + const uno::Reference< frame::XModel >& Model ) +{ + uno::Reference< frame::XTransientDocumentsDocumentContentFactory > xDocFac; + try + { + xDocFac.set( m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.ucb.TransientDocumentsContentProvider", m_xContext), + uno::UNO_QUERY ); + } + catch ( uno::Exception const & ) + { + // handled below. + } + + if ( xDocFac.is() ) + return xDocFac->createDocumentContent( Model ); + + throw uno::RuntimeException( + "Unable to obtain document content factory!", + static_cast< cppu::OWeakObject * >( this ) ); +} + + +// Service factory implementation. + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ucb_tdoc_DocumentContentFactory_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new DocumentContentFactory(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx new file mode 100644 index 000000000..bcbeee7d8 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.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/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp> + +#include <cppuhelper/implbase.hxx> + +namespace tdoc_ucp { + +class DocumentContentFactory : + public cppu::WeakImplHelper< + css::frame::XTransientDocumentsDocumentContentFactory, + css::lang::XServiceInfo > +{ +public: + explicit DocumentContentFactory( const css::uno::Reference< css::uno::XComponentContext >& ); + virtual ~DocumentContentFactory() override; + + // 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; + + // XTransientDocumentsDocumentContentFactory + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createDocumentContent( const css::uno::Reference< css::frame::XModel >& Model ) override; + +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx b/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx new file mode 100644 index 000000000..412b39fe8 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/lang/XTypeProvider.hpp> +#include <com/sun/star/task/DocumentPasswordRequest.hpp> +#include <com/sun/star/task/XInteractionPassword.hpp> + +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <ucbhelper/interactionrequest.hxx> + +#include "tdoc_passwordrequest.hxx" + +#include <mutex> + +using namespace com::sun::star; +using namespace tdoc_ucp; + +namespace tdoc_ucp +{ + namespace { + + class InteractionSupplyPassword : + public ucbhelper::InteractionContinuation, + public lang::XTypeProvider, + public task::XInteractionPassword + { + public: + explicit InteractionSupplyPassword( ucbhelper::InteractionRequest * pRequest ) + : InteractionContinuation( pRequest ) {} + + // XInterface + virtual uno::Any SAL_CALL queryInterface( const uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual uno::Sequence< uno::Type > SAL_CALL getTypes() override; + virtual uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + + // XInteractionContinuation + virtual void SAL_CALL select() override; + + // XInteractionPassword + virtual void SAL_CALL setPassword( const OUString & aPasswd ) override; + virtual OUString SAL_CALL getPassword() override; + + private: + std::mutex m_aMutex; + OUString m_aPassword; + }; + + } +} // namespace tdoc_ucp + + +// InteractionSupplyPassword Implementation. + + +// XInterface methods. + + +// virtual +void SAL_CALL InteractionSupplyPassword::acquire() + noexcept +{ + OWeakObject::acquire(); +} + + +// virtual +void SAL_CALL InteractionSupplyPassword::release() + noexcept +{ + OWeakObject::release(); +} + + +// virtual +uno::Any SAL_CALL +InteractionSupplyPassword::queryInterface( const uno::Type & rType ) +{ + uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider * >( this ), + static_cast< task::XInteractionContinuation * >( this ), + static_cast< task::XInteractionPassword * >( this ) ); + + return aRet.hasValue() + ? aRet : InteractionContinuation::queryInterface( rType ); +} + + +// XTypeProvider methods. + + +// virtual +uno::Sequence< sal_Int8 > SAL_CALL +InteractionSupplyPassword::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + +// virtual +uno::Sequence< uno::Type > SAL_CALL InteractionSupplyPassword::getTypes() +{ + static cppu::OTypeCollection s_aCollection( + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<task::XInteractionPassword>::get() ); + + return s_aCollection.getTypes(); +} + + +// XInteractionContinuation methods. + + +// virtual +void SAL_CALL InteractionSupplyPassword::select() +{ + recordSelection(); +} + + +// XInteractionPassword methods. + + +// virtual +void SAL_CALL +InteractionSupplyPassword::setPassword( const OUString& aPasswd ) +{ + std::scoped_lock aGuard( m_aMutex ); + m_aPassword = aPasswd; +} + +// virtual +OUString SAL_CALL InteractionSupplyPassword::getPassword() +{ + std::scoped_lock aGuard( m_aMutex ); + return m_aPassword; +} + + +// DocumentPasswordRequest Implementation. + + +DocumentPasswordRequest::DocumentPasswordRequest( + task::PasswordRequestMode eMode, + const OUString & rDocumentName ) +{ + // Fill request... + task::DocumentPasswordRequest aRequest; +// aRequest.Message = // OUString +// aRequest.Context = // XInterface + aRequest.Classification = task::InteractionClassification_ERROR; + aRequest.Mode = eMode; + aRequest.Name = rDocumentName; + + setRequest( uno::Any( aRequest ) ); + + // Fill continuations... + uno::Sequence< + uno::Reference< task::XInteractionContinuation > > aContinuations{ + new ucbhelper::InteractionAbort( this ), + new ucbhelper::InteractionRetry( this ), + new InteractionSupplyPassword( this ) + }; + + setContinuations( aContinuations ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx b/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx new file mode 100644 index 000000000..d246a5ea4 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/task/PasswordRequestMode.hpp> + +#include <ucbhelper/interactionrequest.hxx> + +namespace tdoc_ucp +{ +/* + @usage: + + uno::Reference< ucb::XCommandEnvironment > Environment = ...; + + if ( Environment.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = Environment->getInteractionHandler(); + if ( xIH.is() ) + { + rtl::Reference< DocumentPasswordRequest > xRequest + = new DocumentPasswordRequest( + task::PasswordRequestMode_PASSWORD_ENTER, + m_xIdentifier->getContentIdentifier() ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( xAbort.is() ) + { + // @@@ + } + + uno::Reference< task::XInteractionRetry > xRetry( + xSelection.get(), uno::UNO_QUERY ); + if ( xRetry.is() ) + { + // @@@ + } + + uno::Reference< task::XInteractionPassword > xPassword( + xSelection.get(), uno::UNO_QUERY ); + if ( xPassword.is() ) + { + OUString aPassword = xPassword->getPassword(); + + // @@@ + } + } + } + } + + */ + +class DocumentPasswordRequest : public ucbhelper::InteractionRequest +{ +public: + DocumentPasswordRequest(css::task::PasswordRequestMode eMode, const OUString& rDocumentName); +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_provider.cxx b/ucb/source/ucp/tdoc/tdoc_provider.cxx new file mode 100644 index 000000000..23fd324a5 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_provider.cxx @@ -0,0 +1,605 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#include <tools/diagnose_ex.h> + +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/macros.hxx> + +#include "tdoc_provider.hxx" +#include "tdoc_content.hxx" +#include "tdoc_uri.hxx" +#include "tdoc_docmgr.hxx" +#include "tdoc_storage.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// ContentProvider Implementation. + + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rxContext ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ), + m_xDocsMgr( new OfficeDocumentsManager( rxContext, this ) ), + m_xStgElemFac( new StorageElementFactory( rxContext, m_xDocsMgr ) ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{ + if ( m_xDocsMgr.is() ) + m_xDocsMgr->destroy(); +} + + +// XInterface methods. +void SAL_CALL ContentProvider::acquire() + noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + noexcept +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL ContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider* >(this), + static_cast< lang::XServiceInfo* >(this), + static_cast< ucb::XContentProvider* >(this), + static_cast< frame::XTransientDocumentsDocumentContentIdentifierFactory* >(this), + static_cast< frame::XTransientDocumentsDocumentContentFactory* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_5( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider, + frame::XTransientDocumentsDocumentContentIdentifierFactory, + frame::XTransientDocumentsDocumentContentFactory ); + + +// XServiceInfo methods. +OUString SAL_CALL ContentProvider::getImplementationName() +{ + return "com.sun.star.comp.ucb.TransientDocumentsContentProvider"; +} + +sal_Bool SAL_CALL ContentProvider::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +css::uno::Sequence< OUString > SAL_CALL ContentProvider::getSupportedServiceNames() +{ + return { "com.sun.star.ucb.TransientDocumentsContentProvider" }; +} + + +// Service factory implementation. + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ucb_tdoc_ContentProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ContentProvider(context)); +} + +// XContentProvider methods. + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +ContentProvider::queryContent( + const uno::Reference< ucb::XContentIdentifier >& Identifier ) +{ + Uri aUri( Identifier->getContentIdentifier() ); + if ( !aUri.isValid() ) + throw ucb::IllegalIdentifierException( + "Invalid URL!", + Identifier ); + + // Normalize URI. + uno::Reference< ucb::XContentIdentifier > xCanonicId + = new ::ucbhelper::ContentIdentifier( aUri.getUri() ); + + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference< ucb::XContent > xContent + = queryExistingContent( xCanonicId ); + + if ( !xContent.is() ) + { + // Create a new content. + xContent = Content::create( m_xContext, this, xCanonicId ); + registerNewContent( xContent ); + } + + return xContent; +} + + +// XTransientDocumentsDocumentContentIdentifierFactory methods. + +uno::Reference<ucb::XContentIdentifier> SAL_CALL +ContentProvider::createDocumentContentIdentifier( + uno::Reference<frame::XModel> const& xModel) +{ + // model -> id -> content identifier -> queryContent + if ( !m_xDocsMgr.is() ) + { + throw lang::IllegalArgumentException( + "No Document Manager!", + static_cast< cppu::OWeakObject * >( this ), + 1 ); + } + + OUString aDocId = tdoc_ucp::OfficeDocumentsManager::queryDocumentId(xModel); + if ( aDocId.isEmpty() ) + { + throw lang::IllegalArgumentException( + "Unable to obtain document id from model!", + static_cast< cppu::OWeakObject * >( this ), + 1 ); + } + + OUString aBuffer = TDOC_URL_SCHEME ":/" + aDocId; + + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aBuffer ); + return xId; +} + +// XTransientDocumentsDocumentContentFactory methods. + +uno::Reference< ucb::XContent > SAL_CALL +ContentProvider::createDocumentContent( + uno::Reference<frame::XModel> const& xModel) +{ + uno::Reference<ucb::XContentIdentifier> const xId( + createDocumentContentIdentifier(xModel)); + + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference< ucb::XContent > xContent + = queryExistingContent( xId ); + + if ( !xContent.is() ) + { + // Create a new content. + xContent = Content::create( m_xContext, this, xId ); + } + + if ( xContent.is() ) + return xContent; + + // no content. + throw lang::IllegalArgumentException( + "Illegal Content Identifier!", + static_cast< cppu::OWeakObject * >( this ), + 1 ); +} + + +// interface OfficeDocumentsEventListener + + +// virtual +void ContentProvider::notifyDocumentClosed( std::u16string_view rDocId ) +{ + osl::MutexGuard aGuard( getContentListMutex() ); + + ::ucbhelper::ContentRefList aAllContents; + queryExistingContents( aAllContents ); + + // Notify all content objects related to the closed doc. + + bool bFoundDocumentContent = false; + rtl::Reference< Content > xRoot; + + for ( const auto& rContent : aAllContents ) + { + Uri aUri( rContent->getIdentifier()->getContentIdentifier() ); + OSL_ENSURE( aUri.isValid(), + "ContentProvider::notifyDocumentClosed - Invalid URI!" ); + + if ( !bFoundDocumentContent ) + { + if ( aUri.isRoot() ) + { + xRoot = static_cast< Content * >( rContent.get() ); + } + else if ( aUri.isDocument() ) + { + if ( aUri.getDocumentId() == rDocId ) + { + bFoundDocumentContent = true; + + // document content will notify removal of child itself; + // no need for the root to propagate this. + xRoot.clear(); + } + } + } + + if ( aUri.getDocumentId() == rDocId ) + { + // Inform content. + rtl::Reference< Content > xContent + = static_cast< Content * >( rContent.get() ); + + xContent->notifyDocumentClosed(); + } + } + + if ( xRoot.is() ) + { + // No document content found for rDocId but root content + // instantiated. Root content must announce document removal + // to content event listeners. + xRoot->notifyChildRemoved( rDocId ); + } +} + + +// virtual +void ContentProvider::notifyDocumentOpened( std::u16string_view rDocId ) +{ + osl::MutexGuard aGuard( getContentListMutex() ); + + ::ucbhelper::ContentRefList aAllContents; + queryExistingContents( aAllContents ); + + // Find root content. If instantiated let it propagate document insertion. + + for ( const auto& rContent : aAllContents ) + { + Uri aUri( rContent->getIdentifier()->getContentIdentifier() ); + OSL_ENSURE( aUri.isValid(), + "ContentProvider::notifyDocumentOpened - Invalid URI!" ); + + if ( aUri.isRoot() ) + { + rtl::Reference< Content > xRoot + = static_cast< Content * >( rContent.get() ); + xRoot->notifyChildInserted( rDocId ); + + // Done. + break; + } + } +} + + +// Non-UNO + + +uno::Reference< embed::XStorage > +ContentProvider::queryStorage( const OUString & rUri, + StorageAccessMode eMode ) const +{ + if ( m_xStgElemFac.is() ) + { + try + { + return m_xStgElemFac->createStorage( rUri, eMode ); + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + } + return uno::Reference< embed::XStorage >(); +} + + +uno::Reference< embed::XStorage > +ContentProvider::queryStorageClone( const OUString & rUri ) const +{ + if ( m_xStgElemFac.is() ) + { + try + { + Uri aUri( rUri ); + uno::Reference< embed::XStorage > xParentStorage + = m_xStgElemFac->createStorage( aUri.getParentUri(), READ ); + uno::Reference< embed::XStorage > xStorage + = m_xStgElemFac->createTemporaryStorage(); + + xParentStorage->copyStorageElementLastCommitTo( + aUri.getDecodedName(), xStorage ); + return xStorage; + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + } + + return uno::Reference< embed::XStorage >(); +} + + +uno::Reference< io::XInputStream > +ContentProvider::queryInputStream( const OUString & rUri, + const OUString & rPassword ) const +{ + if ( m_xStgElemFac.is() ) + { + try + { + return m_xStgElemFac->createInputStream( rUri, rPassword ); + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } +// catch ( packages::WrongPasswordException const & ) +// { +// // the key provided is wrong; rethrow; to be handled by caller. +// throw; +// } + } + return uno::Reference< io::XInputStream >(); +} + + +uno::Reference< io::XOutputStream > +ContentProvider::queryOutputStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) const +{ + if ( m_xStgElemFac.is() ) + { + try + { + return + m_xStgElemFac->createOutputStream( rUri, rPassword, bTruncate ); + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } +// catch ( packages::WrongPasswordException const & ) +// { +// // the key provided is wrong; rethrow; to be handled by caller. +// throw; +// } + } + return uno::Reference< io::XOutputStream >(); +} + + +uno::Reference< io::XStream > +ContentProvider::queryStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) const +{ + if ( m_xStgElemFac.is() ) + { + try + { + return m_xStgElemFac->createStream( rUri, rPassword, bTruncate ); + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } +// catch ( packages::WrongPasswordException const & ) +// { +// // the key provided is wrong; rethrow; to be handled by caller. +// throw; +// } + } + return uno::Reference< io::XStream >(); +} + + +bool ContentProvider::queryNamesOfChildren( + const OUString & rUri, uno::Sequence< OUString > & rNames ) const +{ + Uri aUri( rUri ); + if ( aUri.isRoot() ) + { + // special handling for root, which has no storage, but children. + if ( m_xDocsMgr.is() ) + { + rNames = m_xDocsMgr->queryDocuments(); + return true; + } + } + else + { + if ( m_xStgElemFac.is() ) + { + try + { + uno::Reference< embed::XStorage > xStorage + = m_xStgElemFac->createStorage( rUri, READ ); + + OSL_ENSURE( xStorage.is(), "Got no Storage!" ); + + if ( xStorage.is() ) + { + rNames = xStorage->getElementNames(); + return true; + } + } + catch ( embed::InvalidStorageException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( lang::IllegalArgumentException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance if the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + } + } + return false; +} + + +OUString +ContentProvider::queryStorageTitle( const OUString & rUri ) const +{ + OUString aTitle; + + Uri aUri( rUri ); + if ( aUri.isRoot() ) + { + // always empty. + aTitle.clear(); + } + else if ( aUri.isDocument() ) + { + // for documents, title shall not be derived from URL. It shall + // be something more 'speaking' than just the document UID. + if ( m_xDocsMgr.is() ) + aTitle = m_xDocsMgr->queryStorageTitle( aUri.getDocumentId() ); + } + else + { + // derive title from URL + aTitle = aUri.getDecodedName(); + } + + OSL_ENSURE( !aTitle.isEmpty() || aUri.isRoot(), + "ContentProvider::queryStorageTitle - empty title!" ); + return aTitle; +} + + +uno::Reference< frame::XModel > +ContentProvider::queryDocumentModel( const OUString & rUri ) const +{ + uno::Reference< frame::XModel > xModel; + + if ( m_xDocsMgr.is() ) + { + Uri aUri( rUri ); + xModel = m_xDocsMgr->queryDocumentModel( aUri.getDocumentId() ); + } + + OSL_ENSURE( xModel.is(), + "ContentProvider::queryDocumentModel - no model!" ); + return xModel; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_provider.hxx b/ucb/source/ucp/tdoc/tdoc_provider.hxx new file mode 100644 index 000000000..95ffa789f --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_provider.hxx @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <rtl/ref.hxx> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentIdentifierFactory.hpp> +#include <ucbhelper/providerhelper.hxx> +#include "tdoc_uri.hxx" +#include "tdoc_docmgr.hxx" +#include "tdoc_storage.hxx" + +namespace com::sun::star::embed { + class XStorage; +} + +namespace com::sun::star::frame { + class XModel; +} + +namespace tdoc_ucp { + + +inline constexpr OUStringLiteral TDOC_ROOT_CONTENT_TYPE = + u"application/" TDOC_URL_SCHEME "-root"; +inline constexpr OUStringLiteral TDOC_DOCUMENT_CONTENT_TYPE = + u"application/" TDOC_URL_SCHEME "-document"; +inline constexpr OUStringLiteral TDOC_FOLDER_CONTENT_TYPE = + u"application/" TDOC_URL_SCHEME "-folder"; +inline constexpr OUStringLiteral TDOC_STREAM_CONTENT_TYPE = + u"application/" TDOC_URL_SCHEME "-stream"; + + +class StorageElementFactory; + +class ContentProvider + : public ::ucbhelper::ContentProviderImplHelper + , public css::frame::XTransientDocumentsDocumentContentIdentifierFactory + , public css::frame::XTransientDocumentsDocumentContentFactory +{ +public: + explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~ContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // 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; + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + // XTransientDocumentsDocumentContentIdentifierFactory + virtual css::uno::Reference<css::ucb::XContentIdentifier> SAL_CALL + createDocumentContentIdentifier( + css::uno::Reference<css::frame::XModel> const& xModel) override; + + // XTransientDocumentsDocumentContentFactory + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createDocumentContent( const css::uno::Reference< + css::frame::XModel >& Model ) override; + + // Non-UNO interfaces + css::uno::Reference< css::embed::XStorage > + queryStorage( const OUString & rUri, StorageAccessMode eMode ) const; + + css::uno::Reference< css::embed::XStorage > + queryStorageClone( const OUString & rUri ) const; + + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XInputStream > + queryInputStream( const OUString & rUri, + const OUString & rPassword ) const; + + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XOutputStream > + queryOutputStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) const; + + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XStream > + queryStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) const; + + bool queryNamesOfChildren( + const OUString & rUri, + css::uno::Sequence< OUString > & rNames ) const; + + // storage properties + OUString queryStorageTitle( const OUString & rUri ) const; + + css::uno::Reference< css::frame::XModel > + queryDocumentModel( const OUString & rUri ) const; + + // interface OfficeDocumentsEventListener + void notifyDocumentOpened( std::u16string_view rDocId ); + void notifyDocumentClosed( std::u16string_view rDocId ); + +private: + rtl::Reference< OfficeDocumentsManager > m_xDocsMgr; + rtl::Reference< StorageElementFactory > m_xStgElemFac; +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_resultset.cxx b/ucb/source/ucp/tdoc/tdoc_resultset.cxx new file mode 100644 index 000000000..c94e5191d --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_resultset.cxx @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + - This implementation is not a dynamic result set!!! It only implements + the necessary interfaces, but never recognizes/notifies changes!!! + + *************************************************************************/ + +#include <ucbhelper/resultset.hxx> + +#include "tdoc_datasupplier.hxx" +#include "tdoc_resultset.hxx" +#include "tdoc_content.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const ucb::OpenCommandArgument2& rCommand ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ) +{ +} + + +// Non-interface methods. + + +void DynamicResultSet::initStatic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( + m_xContext, + m_aCommand.Properties, + new ResultSetDataSupplier( m_xContext, + m_xContent ) ); +} + + +void DynamicResultSet::initDynamic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( + m_xContext, + m_aCommand.Properties, + new ResultSetDataSupplier( m_xContext, + m_xContent ) ); + m_xResultSet2 = m_xResultSet1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_resultset.hxx b/ucb/source/ucp/tdoc/tdoc_resultset.hxx new file mode 100644 index 000000000..d0fbe418b --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_resultset.hxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ref.hxx> +#include <ucbhelper/resultsethelper.hxx> +#include "tdoc_content.hxx" + +namespace tdoc_ucp { + +class Content; + +class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper +{ + rtl::Reference< Content > m_xContent; + +private: + virtual void initStatic() override; + virtual void initDynamic() override; + +public: + DynamicResultSet( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_stgelems.cxx b/ucb/source/ucp/tdoc/tdoc_stgelems.cxx new file mode 100644 index 000000000..ad1ac498d --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_stgelems.cxx @@ -0,0 +1,876 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + - remove root storage access workaround + + *************************************************************************/ + +#include <tools/diagnose_ex.h> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/reflection/ProxyFactory.hpp> + +#include "tdoc_uri.hxx" + +#include "tdoc_stgelems.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// ParentStorageHolder Implementation. + + +ParentStorageHolder::ParentStorageHolder( + const uno::Reference< embed::XStorage > & xParentStorage, + const OUString & rUri ) +: m_xParentStorage( xParentStorage ), + m_bParentIsRootStorage( false ) +{ + Uri aUri( rUri ); + if ( aUri.isDocument() ) + m_bParentIsRootStorage = true; +} + + +// Storage Implementation. + + +Storage::Storage( const uno::Reference< uno::XComponentContext > & rxContext, + const rtl::Reference< StorageElementFactory > & xFactory, + const OUString & rUri, + const uno::Reference< embed::XStorage > & xParentStorage, + const uno::Reference< embed::XStorage > & xStorageToWrap ) +: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ), + m_xFactory( xFactory ), + m_xWrappedStorage( xStorageToWrap ), + m_xWrappedTransObj( xStorageToWrap, uno::UNO_QUERY ), // optional interface + m_xWrappedComponent( xStorageToWrap ), + m_xWrappedTypeProv( xStorageToWrap, uno::UNO_QUERY ), + m_bIsDocumentStorage( Uri( rUri ).isDocument() ) +{ + OSL_ENSURE( m_xWrappedStorage.is(), + "Storage::Storage: No storage to wrap!" ); + + OSL_ENSURE( m_xWrappedComponent.is(), + "Storage::Storage: No component to wrap!" ); + + OSL_ENSURE( m_xWrappedTypeProv.is(), + "Storage::Storage: No Type Provider!" ); + + // Use proxy factory service to create aggregatable proxy. + try + { + uno::Reference< reflection::XProxyFactory > xProxyFac = + reflection::ProxyFactory::create( rxContext ); + m_xAggProxy = xProxyFac->createProxy( m_xWrappedStorage ); + } + catch ( uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + + OSL_ENSURE( m_xAggProxy.is(), + "Storage::Storage: Wrapped storage cannot be aggregated!" ); + + if ( !m_xAggProxy.is() ) + return; + + osl_atomic_increment( &m_refCount ); + { + // Solaris compiler problem: + // Extra block to enforce destruction of temporary object created + // in next statement _before_ osl_atomic_decrement is + // called. Otherwise 'this' will destroy itself even before ctor + // is completed (See impl. of XInterface::release())! + + m_xAggProxy->setDelegator( + static_cast< cppu::OWeakObject * >( this ) ); + } + osl_atomic_decrement( &m_refCount ); +} + + +// virtual +Storage::~Storage() +{ + if ( m_xAggProxy.is() ) + m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() ); + + // Never dispose a document storage. Not owner! + if ( m_bIsDocumentStorage ) + return; + + if ( !m_xWrappedComponent.is() ) + return; + + // "Auto-dispose"... + try + { + m_xWrappedComponent->dispose(); + } + catch ( lang::DisposedException const & ) + { + // might happen. + } + catch ( ... ) + { + TOOLS_WARN_EXCEPTION( "ucb", "Storage::~Storage - Caught exception!" ); + } +} + + +// uno::XInterface + + +// virtual +uno::Any SAL_CALL Storage::queryInterface( const uno::Type& aType ) +{ + // First, try to use interfaces implemented by myself and base class(es) + uno::Any aRet = StorageUNOBase::queryInterface( aType ); + + if ( aRet.hasValue() ) + return aRet; + + // Try to use requested interface from aggregated storage + return m_xAggProxy->queryAggregation( aType ); +} + + +// virtual +void SAL_CALL Storage::acquire() + noexcept +{ + osl_atomic_increment( &m_refCount ); +} + + +// virtual +void SAL_CALL Storage::release() + noexcept +{ + //#i120738, Storage::release overrides OWeakObject::release(), + //need call OWeakObject::release() to release OWeakObject::m_pWeakConnectionPoint + + if ( m_refCount == 1 ) + m_xFactory->releaseElement( this ); + + //delete this; + OWeakObject::release(); +} + + +// lang::XTypeProvider + + +// virtual +uno::Sequence< uno::Type > SAL_CALL Storage::getTypes() +{ + return m_xWrappedTypeProv->getTypes(); +} + + +// virtual +uno::Sequence< sal_Int8 > SAL_CALL Storage::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + +// lang::XComponent (base of embed::XStorage) + + +// virtual +void SAL_CALL Storage::dispose() +{ + m_xWrappedStorage->dispose(); + m_xWrappedStorage.clear(); +} + + +// virtual +void SAL_CALL Storage::addEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + m_xWrappedStorage->addEventListener( xListener ); +} + +// virtual +void SAL_CALL Storage::removeEventListener( + const uno::Reference< lang::XEventListener >& aListener ) +{ + m_xWrappedStorage->removeEventListener( aListener ); +} + + +// container::XElementAccess (base of container::XNameAccess) + + +// virtual +uno::Type SAL_CALL Storage::getElementType() +{ + return m_xWrappedStorage->getElementType(); +} + + +// virtual +sal_Bool SAL_CALL Storage::hasElements() +{ + return m_xWrappedStorage->hasElements(); +} + + +// container::XNameAccess (base of embed::XStorage) + + +// virtual +uno::Any SAL_CALL Storage::getByName( const OUString& aName ) +{ + return m_xWrappedStorage->getByName( aName ); +} + + +// virtual +uno::Sequence< OUString > SAL_CALL Storage::getElementNames() +{ + return m_xWrappedStorage->getElementNames(); +} + + +// virtual +sal_Bool SAL_CALL Storage::hasByName( const OUString& aName ) +{ + return m_xWrappedStorage->hasByName( aName ); +} + + +// embed::XStorage + + +// virtual +void SAL_CALL Storage::copyToStorage( + const uno::Reference< embed::XStorage >& xDest ) +{ + m_xWrappedStorage->copyToStorage( xDest ); +} + + +// virtual +uno::Reference< io::XStream > SAL_CALL Storage::openStreamElement( + const OUString& aStreamName, sal_Int32 nOpenMode ) +{ + return m_xWrappedStorage->openStreamElement( aStreamName, nOpenMode ); +} + + +// virtual +uno::Reference< io::XStream > SAL_CALL Storage::openEncryptedStreamElement( + const OUString& aStreamName, + sal_Int32 nOpenMode, + const OUString& aPassword ) +{ + return m_xWrappedStorage->openEncryptedStreamElement( + aStreamName, nOpenMode, aPassword ); +} + + +// virtual +uno::Reference< embed::XStorage > SAL_CALL Storage::openStorageElement( + const OUString& aStorName, sal_Int32 nOpenMode ) +{ + return m_xWrappedStorage->openStorageElement( aStorName, nOpenMode ); +} + + +// virtual +uno::Reference< io::XStream > SAL_CALL Storage::cloneStreamElement( + const OUString& aStreamName ) +{ + return m_xWrappedStorage->cloneStreamElement( aStreamName ); +} + + +// virtual +uno::Reference< io::XStream > SAL_CALL Storage::cloneEncryptedStreamElement( + const OUString& aStreamName, + const OUString& aPassword ) +{ + return m_xWrappedStorage->cloneEncryptedStreamElement( aStreamName, + aPassword ); +} + + +// virtual +void SAL_CALL Storage::copyLastCommitTo( + const uno::Reference< embed::XStorage >& xTargetStorage ) +{ + m_xWrappedStorage->copyLastCommitTo( xTargetStorage ); +} + + +// virtual +void SAL_CALL Storage::copyStorageElementLastCommitTo( + const OUString& aStorName, + const uno::Reference< embed::XStorage >& xTargetStorage ) +{ + m_xWrappedStorage->copyStorageElementLastCommitTo( aStorName, xTargetStorage ); +} + + +// virtual +sal_Bool SAL_CALL Storage::isStreamElement( + const OUString& aElementName ) +{ + return m_xWrappedStorage->isStreamElement( aElementName ); +} + + +// virtual +sal_Bool SAL_CALL Storage::isStorageElement( + const OUString& aElementName ) +{ + return m_xWrappedStorage->isStorageElement( aElementName ); +} + + +// virtual +void SAL_CALL Storage::removeElement( const OUString& aElementName ) +{ + m_xWrappedStorage->removeElement( aElementName ); +} + + +// virtual +void SAL_CALL Storage::renameElement( const OUString& aEleName, + const OUString& aNewName ) +{ + m_xWrappedStorage->renameElement( aEleName, aNewName ); +} + + +// virtual +void SAL_CALL Storage::copyElementTo( + const OUString& aElementName, + const uno::Reference< embed::XStorage >& xDest, + const OUString& aNewName ) +{ + m_xWrappedStorage->copyElementTo( aElementName, xDest, aNewName ); +} + + +// virtual +void SAL_CALL Storage::moveElementTo( + const OUString& aElementName, + const uno::Reference< embed::XStorage >& xDest, + const OUString& rNewName ) +{ + m_xWrappedStorage->moveElementTo( aElementName, xDest, rNewName ); +} + + +// embed::XTransactedObject + + +// virtual +void SAL_CALL Storage::commit() +{ + // Never commit a root storage (-> has no parent)! + // Would lead in writing the whole document to disk. + + uno::Reference< embed::XStorage > xParentStorage = getParentStorage(); + if ( !xParentStorage.is() ) + return; + + OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" ); + + if ( !m_xWrappedTransObj.is() ) + return; + + m_xWrappedTransObj->commit(); + + if ( !isParentARootStorage() ) + { + uno::Reference< embed::XTransactedObject > xParentTA( + xParentStorage, uno::UNO_QUERY ); + OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" ); + + if ( xParentTA.is() ) + xParentTA->commit(); + } +} + + +// virtual +void SAL_CALL Storage::revert() +{ + uno::Reference< embed::XStorage > xParentStorage = getParentStorage(); + if ( !xParentStorage.is() ) + return; + + OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" ); + + if ( !m_xWrappedTransObj.is() ) + return; + + m_xWrappedTransObj->revert(); + + if ( !isParentARootStorage() ) + { + uno::Reference< embed::XTransactedObject > xParentTA( + xParentStorage, uno::UNO_QUERY ); + OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" ); + + if ( xParentTA.is() ) + xParentTA->revert(); + } +} + + +// OutputStream Implementation. + + +OutputStream::OutputStream( + const uno::Reference< uno::XComponentContext > & rxContext, + const OUString & rUri, + const uno::Reference< embed::XStorage > & xParentStorage, + const uno::Reference< io::XOutputStream > & xStreamToWrap ) +: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ), + m_xWrappedStream( xStreamToWrap ), + m_xWrappedComponent( xStreamToWrap, uno::UNO_QUERY ), + m_xWrappedTypeProv( xStreamToWrap, uno::UNO_QUERY ) +{ + OSL_ENSURE( m_xWrappedStream.is(), + "OutputStream::OutputStream: No stream to wrap!" ); + + OSL_ENSURE( m_xWrappedComponent.is(), + "OutputStream::OutputStream: No component to wrap!" ); + + OSL_ENSURE( m_xWrappedTypeProv.is(), + "OutputStream::OutputStream: No Type Provider!" ); + + // Use proxy factory service to create aggregatable proxy. + try + { + uno::Reference< reflection::XProxyFactory > xProxyFac = + reflection::ProxyFactory::create( rxContext ); + m_xAggProxy = xProxyFac->createProxy( m_xWrappedStream ); + } + catch ( uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + + OSL_ENSURE( m_xAggProxy.is(), + "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" ); + + if ( !m_xAggProxy.is() ) + return; + + osl_atomic_increment( &m_refCount ); + { + // Solaris compiler problem: + // Extra block to enforce destruction of temporary object created + // in next statement _before_ osl_atomic_decrement is + // called. Otherwise 'this' will destroy itself even before ctor + // is completed (See impl. of XInterface::release())! + + m_xAggProxy->setDelegator( + static_cast< cppu::OWeakObject * >( this ) ); + } + osl_atomic_decrement( &m_refCount ); +} + + +// virtual +OutputStream::~OutputStream() +{ + if ( m_xAggProxy.is() ) + m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() ); +} + + +// uno::XInterface + + +// virtual +uno::Any SAL_CALL OutputStream::queryInterface( const uno::Type& aType ) +{ + uno::Any aRet = OutputStreamUNOBase::queryInterface( aType ); + + if ( aRet.hasValue() ) + return aRet; + + if ( m_xAggProxy.is() ) + return m_xAggProxy->queryAggregation( aType ); + else + return uno::Any(); +} + + +// lang::XTypeProvider + + +// virtual +uno::Sequence< uno::Type > SAL_CALL OutputStream::getTypes() +{ + return m_xWrappedTypeProv->getTypes(); +} + + +// virtual +uno::Sequence< sal_Int8 > SAL_CALL OutputStream::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + +// io::XOutputStream + + +// virtual +void SAL_CALL +OutputStream::writeBytes( const uno::Sequence< sal_Int8 >& aData ) +{ + m_xWrappedStream->writeBytes( aData ); +} + + +// virtual +void SAL_CALL +OutputStream::flush() +{ + m_xWrappedStream->flush(); +} + + +// virtual +void SAL_CALL +OutputStream::closeOutput( ) +{ + m_xWrappedStream->closeOutput(); + + // Release parent storage. + // Now, that the stream is closed/disposed it is not needed any longer. + setParentStorage( uno::Reference< embed::XStorage >() ); +} + + +// lang::XComponent + + +// virtual +void SAL_CALL +OutputStream::dispose() +{ + m_xWrappedComponent->dispose(); + + // Release parent storage. + // Now, that the stream is closed/disposed it is not needed any longer. + setParentStorage( uno::Reference< embed::XStorage >() ); +} + + +// virtual +void SAL_CALL +OutputStream::addEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + m_xWrappedComponent->addEventListener( xListener ); +} + + +// virtual +void SAL_CALL +OutputStream::removeEventListener( + const uno::Reference< lang::XEventListener >& aListener ) +{ + m_xWrappedComponent->removeEventListener( aListener ); +} + + +// Stream Implementation. + + +Stream::Stream( + const uno::Reference< uno::XComponentContext > & rxContext, + const OUString & rUri, + const uno::Reference< embed::XStorage > & xParentStorage, + const uno::Reference< io::XStream > & xStreamToWrap ) +: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ), + m_xWrappedStream( xStreamToWrap ), + m_xWrappedOutputStream( xStreamToWrap->getOutputStream() ), // might be empty + m_xWrappedTruncate( m_xWrappedOutputStream, uno::UNO_QUERY ), // might be empty + m_xWrappedInputStream( xStreamToWrap->getInputStream() ), + m_xWrappedComponent( xStreamToWrap, uno::UNO_QUERY ), + m_xWrappedTypeProv( xStreamToWrap, uno::UNO_QUERY ) +{ + OSL_ENSURE( m_xWrappedStream.is(), + "OutputStream::OutputStream: No stream to wrap!" ); + + OSL_ENSURE( m_xWrappedComponent.is(), + "OutputStream::OutputStream: No component to wrap!" ); + + OSL_ENSURE( m_xWrappedTypeProv.is(), + "OutputStream::OutputStream: No Type Provider!" ); + + // Use proxy factory service to create aggregatable proxy. + try + { + uno::Reference< reflection::XProxyFactory > xProxyFac = + reflection::ProxyFactory::create( rxContext ); + m_xAggProxy = xProxyFac->createProxy( m_xWrappedStream ); + } + catch ( uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("ucb.ucp", ""); + } + + OSL_ENSURE( m_xAggProxy.is(), + "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" ); + + if ( !m_xAggProxy.is() ) + return; + + osl_atomic_increment( &m_refCount ); + { + // Solaris compiler problem: + // Extra block to enforce destruction of temporary object created + // in next statement _before_ osl_atomic_decrement is + // called. Otherwise 'this' will destroy itself even before ctor + // is completed (See impl. of XInterface::release())! + + m_xAggProxy->setDelegator( + static_cast< cppu::OWeakObject * >( this ) ); + } + osl_atomic_decrement( &m_refCount ); +} + + +// virtual +Stream::~Stream() +{ + if ( m_xAggProxy.is() ) + m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() ); +} + + +// uno::XInterface + + +// virtual +uno::Any SAL_CALL Stream::queryInterface( const uno::Type& aType ) +{ + uno::Any aRet = StreamUNOBase::queryInterface( aType ); + + if ( aRet.hasValue() ) + return aRet; + + if ( m_xAggProxy.is() ) + return m_xAggProxy->queryAggregation( aType ); + else + return uno::Any(); +} + + +// lang::XTypeProvider + + +// virtual +uno::Sequence< uno::Type > SAL_CALL Stream::getTypes() +{ + return m_xWrappedTypeProv->getTypes(); +} + + +// virtual +uno::Sequence< sal_Int8 > SAL_CALL Stream::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + +// io::XStream. + + +// virtual +uno::Reference< io::XInputStream > SAL_CALL Stream::getInputStream() +{ + return uno::Reference< io::XInputStream >( this ); +} + + +// virtual +uno::Reference< io::XOutputStream > SAL_CALL Stream::getOutputStream() +{ + return uno::Reference< io::XOutputStream >( this ); +} + + +// io::XOutputStream. + + +// virtual +void SAL_CALL Stream::writeBytes( const uno::Sequence< sal_Int8 >& aData ) +{ + if ( m_xWrappedOutputStream.is() ) + { + m_xWrappedOutputStream->writeBytes( aData ); + commitChanges(); + } +} + + +// virtual +void SAL_CALL Stream::flush() +{ + if ( m_xWrappedOutputStream.is() ) + { + m_xWrappedOutputStream->flush(); + commitChanges(); + } +} + + +// virtual +void SAL_CALL Stream::closeOutput() +{ + if ( m_xWrappedOutputStream.is() ) + { + m_xWrappedOutputStream->closeOutput(); + commitChanges(); + } + + // Release parent storage. + // Now, that the stream is closed/disposed it is not needed any longer. + setParentStorage( uno::Reference< embed::XStorage >() ); +} + + +// io::XTruncate. + + +// virtual +void SAL_CALL Stream::truncate() +{ + if ( m_xWrappedTruncate.is() ) + { + m_xWrappedTruncate->truncate(); + commitChanges(); + } +} + + +// io::XInputStream. + + +// virtual +sal_Int32 SAL_CALL Stream::readBytes( uno::Sequence< sal_Int8 >& aData, + sal_Int32 nBytesToRead ) +{ + return m_xWrappedInputStream->readBytes( aData, nBytesToRead ); +} + + +// virtual +sal_Int32 SAL_CALL Stream::readSomeBytes( uno::Sequence< sal_Int8 >& aData, + sal_Int32 nMaxBytesToRead ) +{ + return m_xWrappedInputStream->readSomeBytes( aData, nMaxBytesToRead ); +} + + +// virtual +void SAL_CALL Stream::skipBytes( sal_Int32 nBytesToSkip ) +{ + m_xWrappedInputStream->skipBytes( nBytesToSkip ); +} + + +// virtual +sal_Int32 SAL_CALL Stream::available() +{ + return m_xWrappedInputStream->available(); +} + + +// virtual +void SAL_CALL Stream::closeInput() +{ + m_xWrappedInputStream->closeInput(); +} + + +// lang::XComponent + + +// virtual +void SAL_CALL Stream::dispose() +{ + m_xWrappedComponent->dispose(); + + // Release parent storage. + // Now, that the stream is closed/disposed it is not needed any longer. + setParentStorage( uno::Reference< embed::XStorage >() ); +} + + +// virtual +void SAL_CALL Stream::addEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + m_xWrappedComponent->addEventListener( xListener ); +} + + +// virtual +void SAL_CALL Stream::removeEventListener( + const uno::Reference< lang::XEventListener >& aListener ) +{ + m_xWrappedComponent->removeEventListener( aListener ); +} + + +// Non-UNO + + +void Stream::commitChanges() +{ + uno::Reference< embed::XTransactedObject > + xParentTA( getParentStorage(), uno::UNO_QUERY ); + OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" ); + + if ( xParentTA.is() ) + { + try + { + xParentTA->commit(); + } + catch ( lang::WrappedTargetException const & ) + { + throw io::IOException(); // @@@ + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_stgelems.hxx b/ucb/source/ucp/tdoc/tdoc_stgelems.hxx new file mode 100644 index 000000000..4842a3711 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_stgelems.hxx @@ -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 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/uno/XAggregation.hpp> + +#include "tdoc_storage.hxx" + +#include <mutex> + +namespace tdoc_ucp { + +class ParentStorageHolder +{ +public: + ParentStorageHolder( + const css::uno::Reference< css::embed::XStorage > & xParentStorage, + const OUString & rUri ); + + bool isParentARootStorage() const + { return m_bParentIsRootStorage; } + const css::uno::Reference< css::embed::XStorage >& + getParentStorage() const + { return m_xParentStorage; } + void setParentStorage( const css::uno::Reference< css::embed::XStorage > & xStg ) + { + std::scoped_lock aGuard( m_aMutex ); + m_xParentStorage = xStg; + } + +private: + std::mutex m_aMutex; + css::uno::Reference< css::embed::XStorage > m_xParentStorage; + bool m_bParentIsRootStorage; +}; + + +typedef + cppu::WeakImplHelper< + css::embed::XStorage, + css::embed::XTransactedObject > StorageUNOBase; + +class Storage : public StorageUNOBase, public ParentStorageHolder +{ +public: + Storage( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const rtl::Reference< StorageElementFactory > & xFactory, + const OUString & rUri, + const css::uno::Reference< css::embed::XStorage > & xParentStorage, + const css::uno::Reference< css::embed::XStorage > & xStorageToWrap ); + virtual ~Storage() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider (implemented by base, but needs to be overridden for + // delegating to aggregate) + virtual css::uno::Sequence< css::uno::Type > SAL_CALL + getTypes() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + // XComponent ( one of XStorage bases ) + virtual void SAL_CALL + dispose() override; + virtual void SAL_CALL + addEventListener( const css::uno::Reference< css::lang::XEventListener > & xListener ) override; + virtual void SAL_CALL + removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XNameAccess ( one of XStorage bases ) + virtual css::uno::Any SAL_CALL + getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getElementNames() override; + virtual sal_Bool SAL_CALL + hasByName( const OUString& aName ) override; + + // XElementAccess (base of XNameAccess) + virtual css::uno::Type SAL_CALL + getElementType() override; + virtual sal_Bool SAL_CALL + hasElements() override; + + // XStorage + virtual void SAL_CALL + copyToStorage( const css::uno::Reference< css::embed::XStorage >& xDest ) override; + virtual css::uno::Reference< css::io::XStream > SAL_CALL + openStreamElement( const OUString& aStreamName, + sal_Int32 nOpenMode ) override; + virtual css::uno::Reference< css::io::XStream > SAL_CALL + openEncryptedStreamElement( const OUString& aStreamName, + sal_Int32 nOpenMode, + const OUString& aPassword ) override; + virtual css::uno::Reference< css::embed::XStorage > SAL_CALL + openStorageElement( const OUString& aStorName, + sal_Int32 nOpenMode ) override; + virtual css::uno::Reference< css::io::XStream > SAL_CALL + cloneStreamElement( const OUString& aStreamName ) override; + virtual css::uno::Reference< css::io::XStream > SAL_CALL + cloneEncryptedStreamElement( const OUString& aStreamName, + const OUString& aPassword ) override; + virtual void SAL_CALL + copyLastCommitTo( const css::uno::Reference< + css::embed::XStorage >& xTargetStorage ) override; + virtual void SAL_CALL + copyStorageElementLastCommitTo( const OUString& aStorName, + const css::uno::Reference< + css::embed::XStorage > & + xTargetStorage ) override; + virtual sal_Bool SAL_CALL + isStreamElement( const OUString& aElementName ) override; + virtual sal_Bool SAL_CALL + isStorageElement( const OUString& aElementName ) override; + virtual void SAL_CALL + removeElement( const OUString& aElementName ) override; + virtual void SAL_CALL + renameElement( const OUString& aEleName, + const OUString& aNewName ) override; + virtual void SAL_CALL + copyElementTo( const OUString& aElementName, + const css::uno::Reference< css::embed::XStorage >& xDest, + const OUString& aNewName ) override; + virtual void SAL_CALL + moveElementTo( const OUString& aElementName, + const css::uno::Reference< css::embed::XStorage >& xDest, + const OUString& rNewName ) override; + + // XTransactedObject + virtual void SAL_CALL commit() override; + virtual void SAL_CALL revert() override; + +private: + rtl::Reference< StorageElementFactory > m_xFactory; + css::uno::Reference< css::uno::XAggregation > m_xAggProxy; + css::uno::Reference< css::embed::XStorage > m_xWrappedStorage; + css::uno::Reference< css::embed::XTransactedObject > m_xWrappedTransObj; + css::uno::Reference< css::lang::XComponent > m_xWrappedComponent; + css::uno::Reference< css::lang::XTypeProvider > m_xWrappedTypeProv; + bool m_bIsDocumentStorage; + + StorageElementFactory::StorageMap::iterator m_aContainerIt; + + friend class StorageElementFactory; +}; + + +typedef + cppu::WeakImplHelper< + css::io::XOutputStream, + css::lang::XComponent > OutputStreamUNOBase; + +class OutputStream : public OutputStreamUNOBase, public ParentStorageHolder +{ +public: + OutputStream( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const OUString & rUri, + const css::uno::Reference< css::embed::XStorage > & xParentStorage, + const css::uno::Reference< css::io::XOutputStream > & xStreamToWrap ); + virtual ~OutputStream() override; + + // XInterface + virtual css::uno::Any SAL_CALL + queryInterface( const css::uno::Type& aType ) override; + + // XTypeProvider (implemented by base, but needs to be overridden for + // delegating to aggregate) + virtual css::uno::Sequence< css::uno::Type > SAL_CALL + getTypes() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + // XOutputStream + virtual void SAL_CALL + writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override; + virtual void SAL_CALL + flush( ) override; + // Note: We need to intercept this one. + virtual void SAL_CALL + closeOutput( ) override; + + // XComponent + // Note: We need to intercept this one. + virtual void SAL_CALL + dispose() override; + virtual void SAL_CALL + addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL + removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + +private: + css::uno::Reference< + css::uno::XAggregation > m_xAggProxy; + css::uno::Reference< + css::io::XOutputStream > m_xWrappedStream; + css::uno::Reference< + css::lang::XComponent > m_xWrappedComponent; + css::uno::Reference< + css::lang::XTypeProvider > m_xWrappedTypeProv; +}; + + +typedef cppu::WeakImplHelper< css::io::XStream, + css::io::XOutputStream, + css::io::XTruncate, + css::io::XInputStream, + css::lang::XComponent > + StreamUNOBase; + +class Stream : public StreamUNOBase, public ParentStorageHolder +{ +public: + Stream( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const OUString & rUri, + const css::uno::Reference< css::embed::XStorage > & xParentStorage, + const css::uno::Reference< css::io::XStream > & xStreamToWrap ); + + virtual ~Stream() override; + + // XInterface + virtual css::uno::Any SAL_CALL + queryInterface( const css::uno::Type& aType ) override; + + // XTypeProvider (implemented by base, but needs to be overridden for + // delegating to aggregate) + virtual css::uno::Sequence< css::uno::Type > SAL_CALL + getTypes() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + // XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL + getInputStream() override; + + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL + getOutputStream() override; + + // XOutputStream + virtual void SAL_CALL + writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override; + + virtual void SAL_CALL + flush() override; + + virtual void SAL_CALL + closeOutput() override; + + // XTruncate + virtual void SAL_CALL + truncate() override; + + // XInputStream + virtual sal_Int32 SAL_CALL + readBytes( css::uno::Sequence< sal_Int8 >& aData, + sal_Int32 nBytesToRead ) override; + + virtual sal_Int32 SAL_CALL + readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, + sal_Int32 nMaxBytesToRead ) override; + + virtual void SAL_CALL + skipBytes( sal_Int32 nBytesToSkip ) override; + + virtual sal_Int32 SAL_CALL + available() override; + + virtual void SAL_CALL + closeInput() override; + + // XComponent + // Note: We need to intercept this one. + virtual void SAL_CALL + dispose() override; + virtual void SAL_CALL + addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL + removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + +private: + /// @throws css::io::IOException + void commitChanges(); + + css::uno::Reference< + css::uno::XAggregation > m_xAggProxy; + css::uno::Reference< + css::io::XStream > m_xWrappedStream; + css::uno::Reference< + css::io::XOutputStream > m_xWrappedOutputStream; + css::uno::Reference< + css::io::XTruncate > m_xWrappedTruncate; + css::uno::Reference< + css::io::XInputStream > m_xWrappedInputStream; + css::uno::Reference< + css::lang::XComponent > m_xWrappedComponent; + css::uno::Reference< + css::lang::XTypeProvider > m_xWrappedTypeProv; +}; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_storage.cxx b/ucb/source/ucp/tdoc/tdoc_storage.cxx new file mode 100644 index 000000000..7fc0f247a --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_storage.cxx @@ -0,0 +1,617 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/packages/NoEncryptionException.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> + +#include "tdoc_uri.hxx" +#include "tdoc_docmgr.hxx" +#include "tdoc_stgelems.hxx" + +#include "tdoc_storage.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// StorageElementFactory Implementation. + + +StorageElementFactory::StorageElementFactory( + const uno::Reference< uno::XComponentContext > & rxContext, + const rtl::Reference< OfficeDocumentsManager > & xDocsMgr ) +: m_xDocsMgr( xDocsMgr ), + m_xContext( rxContext ) +{ +} + + +StorageElementFactory::~StorageElementFactory() +{ + OSL_ENSURE( m_aMap.empty(), + "StorageElementFactory::~StorageElementFactory - Dangling storages!" ); +} + + +uno::Reference< embed::XStorage > +StorageElementFactory::createTemporaryStorage() +{ + uno::Reference< embed::XStorage > xStorage; + uno::Reference< lang::XSingleServiceFactory > xStorageFac; + if ( m_xContext.is() ) + { + xStorageFac = embed::StorageFactory::create( m_xContext ); + } + + OSL_ENSURE( xStorageFac.is(), "Can't create storage factory!" ); + if ( xStorageFac.is() ) + xStorage.set( xStorageFac->createInstance(), uno::UNO_QUERY ); + + if ( !xStorage.is() ) + throw uno::RuntimeException(); + + return xStorage; +} + + +uno::Reference< embed::XStorage > +StorageElementFactory::createStorage( const OUString & rUri, + StorageAccessMode eMode ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( ( eMode != READ ) && + ( eMode != READ_WRITE_NOCREATE ) && + ( eMode != READ_WRITE_CREATE ) ) + throw lang::IllegalArgumentException( + "Invalid open mode!", + uno::Reference< uno::XInterface >(), + sal_Int16( 2 ) ); + + Uri aUri( rUri ); + if ( aUri.isRoot() ) + { + throw lang::IllegalArgumentException( + "Root never has a storage!", + uno::Reference< uno::XInterface >(), + sal_Int16( 1 ) ); + } + + OUString aUriKey + ( rUri.endsWith("/") + ? rUri.copy( 0, rUri.getLength() - 1 ) + : rUri ); + + StorageMap::iterator aIt ( m_aMap.begin() ); + StorageMap::iterator aEnd( m_aMap.end() ); + + while ( aIt != aEnd ) + { + if ( (*aIt).first.first == aUriKey ) + { + // URI matches. Now, check open mode. + bool bMatch = true; + switch ( eMode ) + { + case READ: + // No need to check; storage is at least readable. + bMatch = true; + break; + + case READ_WRITE_NOCREATE: + case READ_WRITE_CREATE: + // If found storage is writable, it can be used. + // If not, a new one must be created. + bMatch = (*aIt).first.second; + break; + } + + if ( bMatch ) + break; + } + ++aIt; + } + + if ( aIt == aEnd ) + { + uno::Reference< embed::XStorage > xParentStorage; + + // documents never have a parent storage. + if ( !aUri.isDocument() ) + { + xParentStorage = queryParentStorage( aUriKey, eMode ); + + if ( !xParentStorage.is() ) + { + // requested to create new storage, but failed? + OSL_ENSURE( eMode != READ_WRITE_CREATE, + "Unable to create parent storage!" ); + return xParentStorage; + } + } + + uno::Reference< embed::XStorage > xStorage + = queryStorage( xParentStorage, aUriKey, eMode ); + + if ( !xStorage.is() ) + { + // requested to create new storage, but failed? + OSL_ENSURE( eMode != READ_WRITE_CREATE, + "Unable to create storage!" ); + return xStorage; + } + + bool bWritable = ( ( eMode == READ_WRITE_NOCREATE ) + || ( eMode == READ_WRITE_CREATE ) ); + + rtl::Reference< Storage > xElement( + new Storage( m_xContext, this, aUriKey, xParentStorage, xStorage ) ); + + aIt = m_aMap.emplace( + std::pair< OUString, bool >( aUriKey, bWritable ), + xElement.get() ).first; + + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } + else if ( osl_atomic_increment( &aIt->second->m_refCount ) > 1 ) + { + uno::Reference< embed::XStorage > xElement( aIt->second ); + osl_atomic_decrement( &aIt->second->m_refCount ); + return xElement; + } + else + { + osl_atomic_decrement( &aIt->second->m_refCount ); + aIt->second->m_aContainerIt = m_aMap.end(); + + uno::Reference< embed::XStorage > xParentStorage; + + // documents never have a parent storage. + if ( !aUri.isDocument() ) + { + xParentStorage = queryParentStorage( aUriKey, eMode ); + + if ( !xParentStorage.is() ) + { + // requested to create new storage, but failed? + OSL_ENSURE( eMode != READ_WRITE_CREATE, + "Unable to create parent storage!" ); + return xParentStorage; + } + } + + uno::Reference< embed::XStorage > xStorage + = queryStorage( xParentStorage, aUriKey, eMode ); + + if ( !xStorage.is() ) + { + // requested to create new storage, but failed? + OSL_ENSURE( eMode != READ_WRITE_CREATE, + "Unable to create storage!" ); + return xStorage; + } + + rtl::Reference<Storage> pNewStorage = new Storage( m_xContext, this, aUriKey, xParentStorage, xStorage ); + aIt->second = pNewStorage.get(); + aIt->second->m_aContainerIt = aIt; + return pNewStorage; + } +} + + +uno::Reference< io::XInputStream > +StorageElementFactory::createInputStream( const OUString & rUri, + const OUString & rPassword ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< embed::XStorage > xParentStorage + = queryParentStorage( rUri, READ ); + + // Each stream must have a parent storage. + if ( !xParentStorage.is() ) + return uno::Reference< io::XInputStream >(); + + uno::Reference< io::XStream > xStream + = queryStream( xParentStorage, rUri, rPassword, READ, false ); + + if ( !xStream.is() ) + return uno::Reference< io::XInputStream >(); + + return xStream->getInputStream(); +} + + +uno::Reference< io::XOutputStream > +StorageElementFactory::createOutputStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< embed::XStorage > xParentStorage + = queryParentStorage( rUri, READ_WRITE_CREATE ); + + // Each stream must have a parent storage. + if ( !xParentStorage.is() ) + { + OSL_FAIL( "StorageElementFactory::createOutputStream - " + "Unable to create parent storage!" ); + return uno::Reference< io::XOutputStream >(); + } + + uno::Reference< io::XStream > xStream + = queryStream( + xParentStorage, rUri, rPassword, READ_WRITE_CREATE, bTruncate ); + + if ( !xStream.is() ) + { + OSL_FAIL( "StorageElementFactory::createOutputStream - " + "Unable to create stream!" ); + return uno::Reference< io::XOutputStream >(); + } + + // Note: We need a wrapper to hold a reference to the parent storage to + // ensure that nobody else owns it at the moment we want to commit + // our changes. (There can be only one writable instance at a time + // and even no writable instance if there is already another + // read-only instance!) + return uno::Reference< io::XOutputStream >( + new OutputStream( m_xContext, rUri, xParentStorage, xStream->getOutputStream() ) ); +} + + +uno::Reference< io::XStream > +StorageElementFactory::createStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< embed::XStorage > xParentStorage + = queryParentStorage( rUri, READ_WRITE_CREATE ); + + // Each stream must have a parent storage. + if ( !xParentStorage.is() ) + { + OSL_FAIL( "StorageElementFactory::createStream - " + "Unable to create parent storage!" ); + return uno::Reference< io::XStream >(); + } + + uno::Reference< io::XStream > xStream + = queryStream( + xParentStorage, rUri, rPassword, READ_WRITE_NOCREATE, bTruncate ); + + if ( !xStream.is() ) + { + OSL_FAIL( "StorageElementFactory::createStream - " + "Unable to create stream!" ); + return uno::Reference< io::XStream >(); + } + + return uno::Reference< io::XStream >( + new Stream( m_xContext, rUri, xParentStorage, xStream ) ); +} + + +void StorageElementFactory::releaseElement( Storage const * pElement ) +{ + OSL_ASSERT( pElement ); + osl::MutexGuard aGuard( m_aMutex ); + if ( pElement->m_aContainerIt != m_aMap.end() ) + m_aMap.erase( pElement->m_aContainerIt ); +} + + +// Non-UNO interface + + +uno::Reference< embed::XStorage > StorageElementFactory::queryParentStorage( + const OUString & rUri, StorageAccessMode eMode ) +{ + uno::Reference< embed::XStorage > xParentStorage; + + Uri aUri( rUri ); + Uri aParentUri( aUri.getParentUri() ); + if ( !aParentUri.isRoot() ) + { + xParentStorage = createStorage( aUri.getParentUri(), eMode ); + OSL_ENSURE( xParentStorage.is() + // requested to create new storage, but failed? + || ( eMode != READ_WRITE_CREATE ), + "StorageElementFactory::queryParentStorage - No storage!" ); + } + return xParentStorage; +} + + +uno::Reference< embed::XStorage > StorageElementFactory::queryStorage( + const uno::Reference< embed::XStorage > & xParentStorage, + const OUString & rUri, + StorageAccessMode eMode ) +{ + uno::Reference< embed::XStorage > xStorage; + + Uri aUri( rUri ); + + if ( !xParentStorage.is() ) + { + // document storage + + xStorage = m_xDocsMgr->queryStorage( aUri.getDocumentId() ); + + if ( !xStorage.is() ) + { + if ( eMode == READ_WRITE_CREATE ) + throw lang::IllegalArgumentException( + "Invalid open mode: document storages cannot be created!", + uno::Reference< uno::XInterface >(), + sal_Int16( 2 ) ); + else + throw embed::InvalidStorageException( + "Invalid document id!", + uno::Reference< uno::XInterface >() ); + } + + // match xStorage's open mode against requested open mode + + uno::Reference< beans::XPropertySet > xPropSet( + xStorage, uno::UNO_QUERY ); + OSL_ENSURE( xPropSet.is(), + "StorageElementFactory::queryStorage - " + "No XPropertySet interface!" ); + try + { + uno::Any aPropValue = xPropSet->getPropertyValue("OpenMode"); + + sal_Int32 nOpenMode = 0; + if ( aPropValue >>= nOpenMode ) + { + switch ( eMode ) + { + case READ: + if ( !( nOpenMode & embed::ElementModes::READ ) ) + { + // document opened, but not readable. + throw embed::InvalidStorageException( + "Storage is open, but not readable!" ); + } + // storage okay + break; + + case READ_WRITE_NOCREATE: + case READ_WRITE_CREATE: + if ( !( nOpenMode & embed::ElementModes::WRITE ) ) + { + // document opened, but not writable. + throw embed::InvalidStorageException( + "Storage is open, but not writable!" ); + } + // storage okay + break; + } + } + else + { + OSL_FAIL( + "Bug! Value of property OpenMode has wrong type!" ); + + throw uno::RuntimeException( + "Bug! Value of property OpenMode has wrong type!" ); + } + } + catch ( beans::UnknownPropertyException const & ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + OSL_FAIL( "Property OpenMode not supported!" ); + + throw embed::StorageWrappedTargetException( + "Bug! Value of property OpenMode has wrong type!", + uno::Reference< uno::XInterface >(), + anyEx ); + } + catch ( lang::WrappedTargetException const & ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + OSL_FAIL( "Caught WrappedTargetException!" ); + + throw embed::StorageWrappedTargetException( + "WrappedTargetException during getPropertyValue!", + uno::Reference< uno::XInterface >(), + anyEx ); + } + } + else + { + // sub storage + + const OUString & rName = aUri.getDecodedName(); + + if ( eMode == READ ) + { + try + { + sal_Int32 const nOpenMode = embed::ElementModes::READ + | embed::ElementModes::NOCREATE; + xStorage + = xParentStorage->openStorageElement( rName, nOpenMode ); + } + catch ( io::IOException const & ) + { + // Another chance: Try to clone storage. + xStorage = createTemporaryStorage(); + xParentStorage->copyStorageElementLastCommitTo( rName, + xStorage ); + } + } + else + { + sal_Int32 nOpenMode = embed::ElementModes::READWRITE; + if ( eMode == READ_WRITE_NOCREATE ) + nOpenMode |= embed::ElementModes::NOCREATE; + + xStorage = xParentStorage->openStorageElement( rName, nOpenMode ); + } + } + + OSL_ENSURE( xStorage.is() || ( eMode != READ_WRITE_CREATE ), + "StorageElementFactory::queryStorage - No storage!" ); + return xStorage; +} + + +uno::Reference< io::XStream > +StorageElementFactory::queryStream( + const uno::Reference< embed::XStorage > & xParentStorage, + const OUString & rUri, + const OUString & rPassword, + StorageAccessMode eMode, + bool bTruncate ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !xParentStorage.is() ) + { + throw lang::IllegalArgumentException( + "No parent storage!", + uno::Reference< uno::XInterface >(), + sal_Int16( 2 ) ); + } + + Uri aUri( rUri ); + if ( aUri.isRoot() ) + { + throw lang::IllegalArgumentException( + "Root never is a stream!", + uno::Reference< uno::XInterface >(), + sal_Int16( 2 ) ); + } + else if ( aUri.isDocument() ) + { + throw lang::IllegalArgumentException( + "A document never is a stream!", + uno::Reference< uno::XInterface >(), + sal_Int16( 2 ) ); + } + + sal_Int32 nOpenMode; + switch ( eMode ) + { + case READ: + nOpenMode = embed::ElementModes::READ + | embed::ElementModes::NOCREATE + | embed::ElementModes::SEEKABLE; + break; + + case READ_WRITE_NOCREATE: + nOpenMode = embed::ElementModes::READWRITE + | embed::ElementModes::NOCREATE + | embed::ElementModes::SEEKABLE; + + if ( bTruncate ) + nOpenMode |= embed::ElementModes::TRUNCATE; + + break; + + case READ_WRITE_CREATE: + nOpenMode = embed::ElementModes::READWRITE + | embed::ElementModes::SEEKABLE; + + if ( bTruncate ) + nOpenMode |= embed::ElementModes::TRUNCATE; + + break; + + default: + OSL_FAIL( "StorageElementFactory::queryStream : Unknown open mode!" ); + + throw embed::InvalidStorageException( + "Unknown open mode!", + uno::Reference< uno::XInterface >() ); + } + + // No object re-usage mechanism; streams are seekable => not stateless. + + uno::Reference< io::XStream > xStream; + if ( !rPassword.isEmpty() ) + { + if ( eMode == READ ) + { + try + { + xStream = xParentStorage->cloneEncryptedStreamElement( + aUri.getDecodedName(), + rPassword ); + } + catch ( packages::NoEncryptionException const & ) + { + xStream + = xParentStorage->cloneStreamElement( aUri.getDecodedName() ); + } + } + else + { + try + { + xStream = xParentStorage->openEncryptedStreamElement( + aUri.getDecodedName(), + nOpenMode, + rPassword ); + } + catch ( packages::NoEncryptionException const & ) + { + xStream + = xParentStorage->openStreamElement( aUri.getDecodedName(), + nOpenMode ); + } + } + } + else + { + if ( eMode == READ ) + { + xStream = xParentStorage->cloneStreamElement( aUri.getDecodedName() ); + } + else + { + xStream = xParentStorage->openStreamElement( aUri.getDecodedName(), + nOpenMode ); + } + } + + if ( !xStream.is() ) + { + throw embed::InvalidStorageException( + "No stream!", + uno::Reference< uno::XInterface >() ); + } + + return xStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_storage.hxx b/ucb/source/ucp/tdoc/tdoc_storage.hxx new file mode 100644 index 000000000..c7067aca7 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_storage.hxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <map> + +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace tdoc_ucp { + + enum StorageAccessMode + { + READ, // Note: might be writable as well + READ_WRITE_NOCREATE, + READ_WRITE_CREATE + }; + + class Storage; + class OfficeDocumentsManager; + + class StorageElementFactory : public salhelper::SimpleReferenceObject + { + public: + StorageElementFactory( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const rtl::Reference< OfficeDocumentsManager > & xDocsMgr ); + virtual ~StorageElementFactory() override; + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + css::uno::Reference< css::embed::XStorage > + createTemporaryStorage(); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::embed::XStorage > + createStorage( const OUString & rUri, StorageAccessMode eMode ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XInputStream > + createInputStream( const OUString & rUri, + const OUString & rPassword ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XOutputStream > + createOutputStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XStream > + createStream( const OUString & rUri, + const OUString & rPassword, + bool bTruncate ); + + private: + friend class Storage; + + void releaseElement( Storage const * pElement ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::embed::XStorage > + queryParentStorage( const OUString & rUri, + StorageAccessMode eMode ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::embed::XStorage > + queryStorage( const css::uno::Reference< + css::embed::XStorage > & xParentStorage, + const OUString & rUri, + StorageAccessMode eMode ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::packages::WrongPasswordException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XStream > + queryStream( const css::uno::Reference< + css::embed::XStorage > & xParentStorage, + const OUString & rUri, + const OUString & rPassword, + StorageAccessMode eMode, + bool bTruncate /* ignored for read-only streams */ ); + + struct ltstrbool + { + bool operator()( + const std::pair< OUString, bool > & s1, + const std::pair< OUString, bool > & s2 ) const + { + if ( s1.first < s2.first ) + return true; + else if ( s1.first == s2.first ) + return ( !s1.second && s2.second ); + else + return false; + } + }; + + // key: pair< storageuri, iswritable > + typedef std::map< + std::pair< OUString, bool >, Storage *, ltstrbool > StorageMap; + + StorageMap m_aMap; + osl::Mutex m_aMutex; + rtl::Reference< OfficeDocumentsManager > m_xDocsMgr; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + }; + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_uri.cxx b/ucb/source/ucp/tdoc/tdoc_uri.cxx new file mode 100644 index 000000000..592977ea0 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_uri.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 . + */ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include "../inc/urihelper.hxx" + +#include "tdoc_uri.hxx" + +using namespace tdoc_ucp; + + +// Uri Implementation. + + +void Uri::init() const +{ + // Already inited? + if ( m_eState != UNKNOWN ) + return; + + m_eState = INVALID; + + // Check for proper length: must be at least length of <scheme>:/ + if ( m_aUri.getLength() < TDOC_URL_SCHEME_LENGTH + 2 ) + { + // Invalid length (to short). + return; + } + + // Check for proper scheme. (Scheme is case insensitive.) + OUString aScheme + = m_aUri.copy( 0, TDOC_URL_SCHEME_LENGTH ).toAsciiLowerCase(); + if ( aScheme != TDOC_URL_SCHEME ) + { + // Invalid scheme. + return; + } + + // Remember normalized scheme string. + m_aUri = m_aUri.replaceAt( 0, aScheme.getLength(), aScheme ); + + if ( m_aUri[ TDOC_URL_SCHEME_LENGTH ] != ':' ) + { + // Invalid (no ':' after <scheme>). + return; + } + + if ( m_aUri[ TDOC_URL_SCHEME_LENGTH + 1 ] != '/' ) + { + // Invalid (no '/' after <scheme>:). + return; + } + + m_aPath = m_aUri.copy( TDOC_URL_SCHEME_LENGTH + 1 ); + + // Note: There must be at least one slash; see above. + sal_Int32 nLastSlash = m_aUri.lastIndexOf( '/' ); + bool bTrailingSlash = false; + if ( nLastSlash == m_aUri.getLength() - 1 ) + { + // ignore trailing slash + bTrailingSlash = true; + nLastSlash = m_aUri.lastIndexOf( '/', nLastSlash ); + } + + if ( nLastSlash != -1 ) // -1 is valid for the root folder + { + m_aParentUri = m_aUri.copy( 0, nLastSlash + 1 ); + + if ( bTrailingSlash ) + m_aName = m_aUri.copy( nLastSlash + 1, + m_aUri.getLength() - nLastSlash - 2 ); + else + m_aName = m_aUri.copy( nLastSlash + 1 ); + + m_aDecodedName = ::ucb_impl::urihelper::decodeSegment( m_aName ); + + sal_Int32 nSlash = m_aPath.indexOf( '/', 1 ); + if ( nSlash == -1 ) + m_aDocId = m_aPath.copy( 1 ); + else + m_aDocId = m_aPath.copy( 1, nSlash - 1 ); + } + + m_eState = VALID; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_uri.hxx b/ucb/source/ucp/tdoc/tdoc_uri.hxx new file mode 100644 index 000000000..8a459c0ae --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_uri.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 <rtl/ustring.hxx> + +namespace tdoc_ucp { + + +#define TDOC_URL_SCHEME "vnd.sun.star.tdoc" +#define TDOC_URL_SCHEME_LENGTH 17 + + +class Uri +{ + enum State { UNKNOWN, INVALID, VALID }; + + mutable OUString m_aUri; + mutable OUString m_aParentUri; + mutable OUString m_aPath; + mutable OUString m_aDocId; + mutable OUString m_aName; + mutable OUString m_aDecodedName; + mutable State m_eState; + +private: + void init() const; + +public: + explicit Uri( const OUString & rUri ) + : m_aUri( rUri ), m_eState( UNKNOWN ) {} + + bool operator== ( const Uri & rOther ) const + { init(); return m_aUri == rOther.m_aUri; } + + bool operator!= ( const Uri & rOther ) const + { return !operator==( rOther ); } + + bool isValid() const + { init(); return m_eState == VALID; } + + const OUString & getUri() const + { init(); return m_aUri; } + + inline void setUri( const OUString & rUri ); + + const OUString & getParentUri() const + { init(); return m_aParentUri; } + + const OUString & getDocumentId() const + { init(); return m_aDocId; } + + const OUString & getName() const + { init(); return m_aName; } + + const OUString & getDecodedName() const + { init(); return m_aDecodedName; } + + inline bool isRoot() const; + + inline bool isDocument() const; +}; + +inline void Uri::setUri( const OUString & rUri ) +{ + m_eState = UNKNOWN; + m_aUri = rUri; + m_aParentUri.clear(); + m_aDocId.clear(); + m_aPath.clear(); + m_aName.clear(); + m_aDecodedName.clear(); +} + +inline bool Uri::isRoot() const +{ + init(); + return ( m_aPath.getLength() == 1 ); +} + +inline bool Uri::isDocument() const +{ + init(); + return ( ( !m_aDocId.isEmpty() ) /* not root */ + && ( m_aPath.subView( m_aDocId.getLength() + 1 ).size() < 2 ) ); +} + +} // namespace tdoc_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/ucptdoc1.component b/ucb/source/ucp/tdoc/ucptdoc1.component new file mode 100644 index 000000000..0ba43e266 --- /dev/null +++ b/ucb/source/ucp/tdoc/ucptdoc1.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.ucb.TransientDocumentsContentProvider" + constructor="ucb_tdoc_ContentProvider_get_implementation"> + <service name="com.sun.star.ucb.TransientDocumentsContentProvider"/> + </implementation> + <implementation name="com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory" + constructor="ucb_tdoc_DocumentContentFactory_get_implementation" single-instance="true"> + <service name="com.sun.star.frame.TransientDocumentsDocumentContentFactory"/> + </implementation> +</component> |