/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ucbcmds.hxx" #include "ucb.hxx" using namespace com::sun::star; namespace { // Helper to provide defaults for type and attributes (save some typing) beans::Property makeProperty(const OUString& n, sal_Int32 h, uno::Type t = {}, sal_Int16 a = {}) { return { n, h, t, a }; } // struct TransferCommandContext. struct TransferCommandContext { uno::Reference< uno::XComponentContext > m_xContext; uno::Reference< ucb::XCommandProcessor > xProcessor; uno::Reference< ucb::XCommandEnvironment > xEnv; uno::Reference< ucb::XCommandEnvironment > xOrigEnv; ucb::GlobalTransferCommandArgument2 aArg; TransferCommandContext( uno::Reference< uno::XComponentContext > xContext, uno::Reference< ucb::XCommandProcessor > _xProcessor, uno::Reference< ucb::XCommandEnvironment > _xEnv, uno::Reference< ucb::XCommandEnvironment > _xOrigEnv, ucb::GlobalTransferCommandArgument2 _aArg ) : m_xContext(std::move( xContext )), xProcessor(std::move( _xProcessor )), xEnv(std::move( _xEnv )), xOrigEnv(std::move( _xOrigEnv )), aArg(std::move( _aArg )) {} }; class InteractionHandlerProxy : public cppu::WeakImplHelper< task::XInteractionHandler > { uno::Reference< task::XInteractionHandler > m_xOrig; public: explicit InteractionHandlerProxy( uno::Reference< task::XInteractionHandler > xOrig ) : m_xOrig(std::move( xOrig )) {} // XInteractionHandler methods. virtual void SAL_CALL handle( const uno::Reference< task::XInteractionRequest >& Request ) override; }; // virtual void SAL_CALL InteractionHandlerProxy::handle( const uno::Reference< task::XInteractionRequest >& Request ) { if ( !m_xOrig.is() ) return; // Filter unwanted requests by just not handling them. uno::Any aRequest = Request->getRequest(); // "transfer" ucb::InteractiveBadTransferURLException aBadTransferURLEx; if ( aRequest >>= aBadTransferURLEx ) { return; } else { // "transfer" ucb::UnsupportedNameClashException aUnsupportedNameClashEx; if ( aRequest >>= aUnsupportedNameClashEx ) { if ( aUnsupportedNameClashEx.NameClash != ucb::NameClash::ERROR ) return; } else { // "insert" ucb::NameClashException aNameClashEx; if ( aRequest >>= aNameClashEx ) { return; } else { // "transfer" ucb::UnsupportedCommandException aUnsupportedCommandEx; if ( aRequest >>= aUnsupportedCommandEx ) { return; } } } } // not filtered; let the original handler do the work. m_xOrig->handle( Request ); } class ActiveDataSink : public cppu::WeakImplHelper< io::XActiveDataSink > { uno::Reference< io::XInputStream > m_xStream; public: // XActiveDataSink methods. virtual void SAL_CALL setInputStream( const uno::Reference< io::XInputStream >& aStream ) override; virtual uno::Reference< io::XInputStream > SAL_CALL getInputStream() override; }; // virtual void SAL_CALL ActiveDataSink::setInputStream( const uno::Reference< io::XInputStream >& aStream ) { m_xStream = aStream; } // virtual uno::Reference< io::XInputStream > SAL_CALL ActiveDataSink::getInputStream() { return m_xStream; } class CommandProcessorInfo : public cppu::WeakImplHelper< ucb::XCommandInfo > { uno::Sequence< ucb::CommandInfo > m_xInfo; public: CommandProcessorInfo(); // XCommandInfo methods virtual uno::Sequence< ucb::CommandInfo > SAL_CALL getCommands() override; virtual ucb::CommandInfo SAL_CALL getCommandInfoByName( const OUString& Name ) override; virtual ucb::CommandInfo SAL_CALL getCommandInfoByHandle( sal_Int32 Handle ) override; virtual sal_Bool SAL_CALL hasCommandByName( const OUString& Name ) override; virtual sal_Bool SAL_CALL hasCommandByHandle( sal_Int32 Handle ) override; }; CommandProcessorInfo::CommandProcessorInfo() : m_xInfo{ ucb::CommandInfo( GETCOMMANDINFO_NAME, // Name GETCOMMANDINFO_HANDLE, // Handle cppu::UnoType::get() ), // ArgType ucb::CommandInfo( GLOBALTRANSFER_NAME, // Name GLOBALTRANSFER_HANDLE, // Handle cppu::UnoType::get() ), // ArgType ucb::CommandInfo( CHECKIN_NAME, // Name CHECKIN_HANDLE, // Handle cppu::UnoType::get() ) } // ArgType { } // virtual uno::Sequence< ucb::CommandInfo > SAL_CALL CommandProcessorInfo::getCommands() { return m_xInfo; } // virtual ucb::CommandInfo SAL_CALL CommandProcessorInfo::getCommandInfoByName( const OUString& Name ) { auto pInfo = std::find_if(std::cbegin(m_xInfo), std::cend(m_xInfo), [&Name](const ucb::CommandInfo& rInfo) { return rInfo.Name == Name; }); if (pInfo != std::cend(m_xInfo)) return *pInfo; throw ucb::UnsupportedCommandException(); } // virtual ucb::CommandInfo SAL_CALL CommandProcessorInfo::getCommandInfoByHandle( sal_Int32 Handle ) { auto pInfo = std::find_if(std::cbegin(m_xInfo), std::cend(m_xInfo), [&Handle](const ucb::CommandInfo& rInfo) { return rInfo.Handle == Handle; }); if (pInfo != std::cend(m_xInfo)) return *pInfo; throw ucb::UnsupportedCommandException(); } // virtual sal_Bool SAL_CALL CommandProcessorInfo::hasCommandByName( const OUString& Name ) { return std::any_of(std::cbegin(m_xInfo), std::cend(m_xInfo), [&Name](const ucb::CommandInfo& rInfo) { return rInfo.Name == Name; }); } // virtual sal_Bool SAL_CALL CommandProcessorInfo::hasCommandByHandle( sal_Int32 Handle ) { return std::any_of(std::cbegin(m_xInfo), std::cend(m_xInfo), [&Handle](const ucb::CommandInfo& rInfo) { return rInfo.Handle == Handle; }); } OUString createDesiredName( const OUString & rSourceURL, const OUString & rNewTitle ) { OUString aName( rNewTitle ); if ( aName.isEmpty() ) { // calculate name using source URL // @@@ It's not guaranteed that slashes contained in the URL are // actually path separators. This depends on the fact whether the // URL is hierarchical. Only then the slashes are path separators. // Therefore this algorithm is not guaranteed to work! But, ATM // I don't know a better solution. It would have been better to // have a member for the clashing name in // UnsupportedNameClashException... sal_Int32 nLastSlash = rSourceURL.lastIndexOf( '/' ); bool bTrailingSlash = false; if ( nLastSlash == rSourceURL.getLength() - 1 ) { nLastSlash = rSourceURL.lastIndexOf( '/', nLastSlash ); bTrailingSlash = true; } if ( nLastSlash != -1 ) { if ( bTrailingSlash ) aName = rSourceURL.copy( nLastSlash + 1, rSourceURL.getLength() - nLastSlash - 2 ); else aName = rSourceURL.copy( nLastSlash + 1 ); } else { aName = rSourceURL; } // query, fragment present? sal_Int32 nPos = aName.indexOf( '?' ); if ( nPos == -1 ) nPos = aName.indexOf( '#' ); if ( nPos != -1 ) aName = aName.copy( 0, nPos ); } return aName; } OUString createDesiredName( const ucb::GlobalTransferCommandArgument & rArg ) { return createDesiredName( rArg.SourceURL, rArg.NewTitle ); } OUString createDesiredName( const ucb::TransferInfo & rArg ) { return createDesiredName( rArg.SourceURL, rArg.NewTitle ); } enum NameClashContinuation { NOT_HANDLED, ABORT, OVERWRITE, NEW_NAME, UNKNOWN }; NameClashContinuation interactiveNameClashResolve( const uno::Reference< ucb::XCommandEnvironment > & xEnv, const OUString & rTargetURL, const OUString & rClashingName, /* [out] */ uno::Any & rException, /* [out] */ OUString & rNewName ) { rtl::Reference< ucbhelper::SimpleNameClashResolveRequest > xRequest( new ucbhelper::SimpleNameClashResolveRequest( rTargetURL, // target folder URL rClashingName ) ); rException = xRequest->getRequest(); 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() ) { // Abort. return ABORT; } else { uno::Reference< ucb::XInteractionReplaceExistingData > xReplace( xSelection.get(), uno::UNO_QUERY ); if ( xReplace.is() ) { // Try again: Replace existing data. return OVERWRITE; } else { uno::Reference< ucb::XInteractionSupplyName > xSupplyName( xSelection.get(), uno::UNO_QUERY ); if ( xSupplyName.is() ) { // Try again: Use new name. rNewName = xRequest->getNewName(); return NEW_NAME; } else { OSL_FAIL( "Unknown interaction continuation!" ); return UNKNOWN; } } } } } } return NOT_HANDLED; } /// @throws uno::RuntimeException bool setTitle( const uno::Reference< ucb::XCommandProcessor > & xCommandProcessor, const uno::Reference< ucb::XCommandEnvironment > & xEnv, const OUString & rNewTitle ) { try { uno::Sequence< beans::PropertyValue > aPropValues{ { /* Name */ "Title", /* Handle */ -1, /* Value */ uno::Any(rNewTitle), /* State */ {} } }; ucb::Command aSetPropsCommand( "setPropertyValues", -1, uno::Any( aPropValues ) ); uno::Any aResult = xCommandProcessor->execute( aSetPropsCommand, 0, xEnv ); uno::Sequence< uno::Any > aErrors; aResult >>= aErrors; OSL_ENSURE( aErrors.getLength() == 1, "getPropertyValues return value invalid!" ); if ( aErrors[ 0 ].hasValue() ) { // error occurred. OSL_FAIL( "error setting Title property!" ); return false; } } catch ( uno::RuntimeException const & ) { throw; } catch ( uno::Exception const & ) { return false; } return true; } /// @throws uno::Exception uno::Reference< ucb::XContent > createNew( const TransferCommandContext & rContext, const uno::Reference< ucb::XContent > & xTarget, bool bSourceIsFolder, bool bSourceIsDocument, bool bSourceIsLink ) { // (1) Obtain creatable types from target. // First, try it using "CreatabeleContentsInfo" property and // "createNewContent" command -> the "new" way. uno::Reference< ucb::XCommandProcessor > xCommandProcessorT( xTarget, uno::UNO_QUERY ); if ( !xCommandProcessorT.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(rContext.aArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, rContext.xOrigEnv, "Target is no XCommandProcessor!", rContext.xProcessor ); // Unreachable } uno::Sequence< beans::Property > aPropsToObtain{ makeProperty("CreatableContentsInfo", -1) }; ucb::Command aGetPropsCommand( "getPropertyValues", -1, uno::Any( aPropsToObtain ) ); uno::Reference< sdbc::XRow > xRow; xCommandProcessorT->execute( aGetPropsCommand, 0, rContext.xEnv ) >>= xRow; uno::Sequence< ucb::ContentInfo > aTypesInfo; bool bGotTypesInfo = false; if ( xRow.is() ) { uno::Any aValue = xRow->getObject( 1, uno::Reference< container::XNameAccess >() ); if ( aValue.hasValue() && ( aValue >>= aTypesInfo ) ) { bGotTypesInfo = true; } } uno::Reference< ucb::XContentCreator > xCreator; if ( !bGotTypesInfo ) { // Second, try it using XContentCreator interface -> the "old" way (not // providing the chance to supply an XCommandEnvironment. xCreator.set( xTarget, uno::UNO_QUERY ); if ( !xCreator.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(rContext.aArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, rContext.xOrigEnv, "Target is no XContentCreator!", rContext.xProcessor ); // Unreachable } aTypesInfo = xCreator->queryCreatableContentsInfo(); } if ( !aTypesInfo.hasElements() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(rContext.aArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, rContext.xOrigEnv, "No types creatable!", rContext.xProcessor ); // Unreachable } // (2) Try to find a matching target type for the source object. std::function lCompare; if ( rContext.aArg.Operation == ucb::TransferCommandOperation_LINK ) { // Create link lCompare = [](const sal_Int32 nAttribs) { return !!( nAttribs & ucb::ContentInfoAttribute::KIND_LINK ); }; } else if ( ( rContext.aArg.Operation == ucb::TransferCommandOperation_COPY ) || ( rContext.aArg.Operation == ucb::TransferCommandOperation_MOVE ) ) { // Copy / Move // Is source a link? Create link in target folder then. if ( bSourceIsLink ) { lCompare = [](const sal_Int32 nAttribs) { return !!( nAttribs & ucb::ContentInfoAttribute::KIND_LINK ); }; } else { // (not a and not b) or (a and b) // not( a or b) or (a and b) lCompare = [bSourceIsFolder, bSourceIsDocument](const sal_Int32 nAttribs) { return ( bSourceIsFolder == !!( nAttribs & ucb::ContentInfoAttribute::KIND_FOLDER ) ) && ( bSourceIsDocument == !!( nAttribs & ucb::ContentInfoAttribute::KIND_DOCUMENT ) ) ; }; } } else { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Unknown transfer operation!", rContext.xProcessor, -1 ) ), rContext.xOrigEnv ); // Unreachable } uno::Reference< ucb::XContent > xNew; auto pTypeInfo = std::find_if(std::cbegin(aTypesInfo), std::cend(aTypesInfo), [&lCompare](const ucb::ContentInfo& rTypeInfo) { return lCompare(rTypeInfo.Attributes); }); if (pTypeInfo != std::cend(aTypesInfo)) { // (3) Create a new, empty object of matched type. if ( !xCreator.is() ) { // First, try it using "CreatabeleContentsInfo" property and // "createNewContent" command -> the "new" way. ucb::Command aCreateNewCommand( "createNewContent", -1, uno::Any( *pTypeInfo ) ); xCommandProcessorT->execute( aCreateNewCommand, 0, rContext.xEnv ) >>= xNew; } else { // Second, try it using XContentCreator interface -> the "old" // way (not providing the chance to supply an XCommandEnvironment. xNew = xCreator->createNewContent( *pTypeInfo ); } if ( !xNew.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(rContext.aArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, rContext.xOrigEnv, "createNewContent failed!", rContext.xProcessor ); // Unreachable } } return xNew; } /// @throws uno::Exception void transferProperties( const TransferCommandContext & rContext, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorS, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorN ) { ucb::Command aGetPropertySetInfoCommand( "getPropertySetInfo", -1, uno::Any() ); uno::Reference< beans::XPropertySetInfo > xInfo; xCommandProcessorS->execute( aGetPropertySetInfoCommand, 0, rContext.xEnv ) >>= xInfo; if ( !xInfo.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rContext.aArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Unable to get propertyset info from source object!", rContext.xProcessor ); // Unreachable } uno::Sequence< beans::Property > aAllProps = xInfo->getProperties(); ucb::Command aGetPropsCommand1( "getPropertyValues", -1, uno::Any( aAllProps ) ); uno::Reference< sdbc::XRow > xRow1; xCommandProcessorS->execute( aGetPropsCommand1, 0, rContext.xEnv ) >>= xRow1; if ( !xRow1.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rContext.aArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Unable to get properties from source object!", rContext.xProcessor ); // Unreachable } // Assemble data structure for setPropertyValues command. // Note: Make room for additional Title and TargetURL too. -> + 2 uno::Sequence< beans::PropertyValue > aPropValues( aAllProps.getLength() + 2 ); auto pPropValues = aPropValues.getArray(); bool bHasTitle = rContext.aArg.NewTitle.isEmpty(); bool bHasTargetURL = ( rContext.aArg.Operation != ucb::TransferCommandOperation_LINK ); sal_Int32 nWritePos = 0; for ( sal_Int32 m = 0; m < aAllProps.getLength(); ++m ) { const beans::Property & rCurrProp = aAllProps[ m ]; beans::PropertyValue & rCurrValue = pPropValues[ nWritePos ]; uno::Any aValue; if ( rCurrProp.Name == "Title" ) { // Supply new title, if given. if ( !bHasTitle ) { bHasTitle = true; aValue <<= rContext.aArg.NewTitle; } } else if ( rCurrProp.Name == "TargetURL" ) { // Supply source URL as link target for the new link to create. if ( !bHasTargetURL ) { bHasTargetURL = true; aValue <<= rContext.aArg.SourceURL; } } if ( !aValue.hasValue() ) { try { aValue = xRow1->getObject( m + 1, uno::Reference< container::XNameAccess >() ); } catch ( sdbc::SQLException const & ) { // Argh! But try to bring things to an end. Perhaps the // mad property is not really important... } } if ( aValue.hasValue() ) { rCurrValue.Name = rCurrProp.Name; rCurrValue.Handle = rCurrProp.Handle; rCurrValue.Value = aValue; // rCurrValue.State = nWritePos++; } } // Title needed, but not set yet? if ( !bHasTitle && !rContext.aArg.NewTitle.isEmpty() ) { pPropValues[ nWritePos ].Name = "Title"; pPropValues[ nWritePos ].Handle = -1; pPropValues[ nWritePos ].Value <<= rContext.aArg.NewTitle; nWritePos++; } // TargetURL needed, but not set yet? if ( !bHasTargetURL && ( rContext.aArg.Operation == ucb::TransferCommandOperation_LINK ) ) { pPropValues[ nWritePos ].Name = "TargetURL"; pPropValues[ nWritePos ].Handle = -1; pPropValues[ nWritePos ].Value <<= rContext.aArg.SourceURL; nWritePos++; } aPropValues.realloc( nWritePos ); // Set properties at new object. ucb::Command aSetPropsCommand( "setPropertyValues", -1, uno::Any( aPropValues ) ); xCommandProcessorN->execute( aSetPropsCommand, 0, rContext.xEnv ); // @@@ What to do with source props that are not supported by the // new object? addProperty ??? } /// @throws uno::Exception uno::Reference< io::XInputStream > getInputStream( const TransferCommandContext & rContext, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorS ) { uno::Reference< io::XInputStream > xInputStream; // (1) Try to get data as XInputStream via XActiveDataSink. try { uno::Reference< io::XActiveDataSink > xSink = new ActiveDataSink; ucb::OpenCommandArgument2 aArg; aArg.Mode = ucb::OpenMode::DOCUMENT; aArg.Priority = 0; // unused aArg.Sink = xSink; aArg.Properties = uno::Sequence< beans::Property >( 0 ); // unused ucb::Command aOpenCommand( "open", -1, uno::Any( aArg ) ); xCommandProcessorS->execute( aOpenCommand, 0, rContext.xEnv ); xInputStream = xSink->getInputStream(); } catch ( uno::RuntimeException const & ) { throw; } catch ( uno::Exception const & ) { // will be handled below. } if ( !xInputStream.is() ) { // (2) Try to get data via XOutputStream. try { uno::Reference< io::XOutputStream > xOutputStream( io::Pipe::create(rContext.m_xContext), uno::UNO_QUERY_THROW ); ucb::OpenCommandArgument2 aArg; aArg.Mode = ucb::OpenMode::DOCUMENT; aArg.Priority = 0; // unused aArg.Sink = xOutputStream; aArg.Properties = uno::Sequence< beans::Property >( 0 ); ucb::Command aOpenCommand( "open", -1, uno::Any( aArg ) ); xCommandProcessorS->execute( aOpenCommand, 0, rContext.xEnv ); xInputStream.set( xOutputStream, uno::UNO_QUERY ); } catch ( uno::RuntimeException const & ) { throw; } catch ( uno::Exception const & ) { OSL_FAIL( "unable to get input stream from document!" ); } } return xInputStream; } /// @throws uno::Exception uno::Reference< sdbc::XResultSet > getResultSet( const TransferCommandContext & rContext, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorS ) { uno::Reference< sdbc::XResultSet > xResultSet; ucb::OpenCommandArgument2 aArg; aArg.Mode = ucb::OpenMode::ALL; aArg.Priority = 0; // unused aArg.Sink = nullptr; aArg.Properties = { makeProperty("IsFolder", -1 /* unknown */), makeProperty("IsDocument", -1 /* unknown */), makeProperty("TargetURL", -1 /* unknown */) }; ucb::Command aOpenCommand( "open", -1, uno::Any( aArg ) ); try { uno::Reference< ucb::XDynamicResultSet > xSet; xCommandProcessorS->execute( aOpenCommand, 0, rContext.xEnv ) >>= xSet; if ( xSet.is() ) xResultSet = xSet->getStaticResultSet(); } catch ( uno::RuntimeException const & ) { throw; } catch ( uno::Exception const & ) { OSL_FAIL( "unable to get result set from folder!" ); } return xResultSet; } /// @throws uno::Exception void handleNameClashRename( const TransferCommandContext & rContext, const uno::Reference< ucb::XContent > & xNew, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorN, const uno::Reference< ucb::XCommandProcessor > & xCommandProcessorS, /* [inout] */ uno::Reference< io::XInputStream > & xInputStream ) { sal_Int32 nTry = 0; // Obtain old title. uno::Sequence< beans::Property > aProps{ makeProperty("Title", -1) }; ucb::Command aGetPropsCommand( "getPropertyValues", -1, uno::Any( aProps ) ); uno::Reference< sdbc::XRow > xRow; xCommandProcessorN->execute( aGetPropsCommand, 0, rContext.xEnv ) >>= xRow; if ( !xRow.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(xNew->getIdentifier()->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Unable to get properties from new object!", rContext.xProcessor ); // Unreachable } OUString aOldTitle = xRow->getString( 1 ); if ( aOldTitle.isEmpty() ) { ucbhelper::cancelCommandExecution( uno::Any( beans::UnknownPropertyException( "Unable to get property 'Title' from new object!", rContext.xProcessor ) ), rContext.xOrigEnv ); // Unreachable } // Some pseudo-intelligence for not destroying file extensions. OUString aOldTitlePre; OUString aOldTitlePost; sal_Int32 nPos = aOldTitle.lastIndexOf( '.' ); if ( nPos != -1 ) { aOldTitlePre = aOldTitle.copy( 0, nPos ); aOldTitlePost = aOldTitle.copy( nPos ); } else aOldTitlePre = aOldTitle; if ( nPos > 0 ) aOldTitlePre += "_"; bool bContinue = true; do { nTry++; OUString aNewTitle = aOldTitlePre + OUString::number( nTry ) + aOldTitlePost; // Set new title setTitle( xCommandProcessorN, rContext.xEnv, aNewTitle ); // Retry inserting the content. try { // Previous try may have read from stream. Seek to begin (if // optional interface XSeekable is supported) or get a new stream. if ( xInputStream.is() ) { uno::Reference< io::XSeekable > xSeekable( xInputStream, uno::UNO_QUERY ); if ( xSeekable.is() ) { try { xSeekable->seek( 0 ); } catch ( lang::IllegalArgumentException const & ) { xInputStream.clear(); } catch ( io::IOException const & ) { xInputStream.clear(); } } else xInputStream.clear(); if ( !xInputStream.is() ) { xInputStream = getInputStream( rContext, xCommandProcessorS ); if ( !xInputStream.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(xNew->getIdentifier()->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Got no data stream from source!", rContext.xProcessor ); // Unreachable } } } ucb::InsertCommandArgument2 aArg; aArg.Data = xInputStream; aArg.ReplaceExisting = false; ucb::Command aInsertCommand( "insert", -1, uno::Any( aArg ) ); xCommandProcessorN->execute( aInsertCommand, 0, rContext.xEnv ); // Success! bContinue = false; } catch ( uno::RuntimeException const & ) { throw; } catch ( uno::Exception const & ) { } } while ( bContinue && ( nTry < 50 ) ); if ( nTry == 50 ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedNameClashException( "Unable to resolve name clash!", rContext.xProcessor, ucb::NameClash::RENAME ) ), rContext.xOrigEnv ); // Unreachable } } /// @throws uno::Exception void globalTransfer_( const TransferCommandContext & rContext, const uno::Reference< ucb::XContent > & xSource, const uno::Reference< ucb::XContent > & xTarget, const uno::Reference< sdbc::XRow > & xSourceProps ) { // IsFolder: property is required. bool bSourceIsFolder = xSourceProps->getBoolean( 1 ); if ( !bSourceIsFolder && xSourceProps->wasNull() ) { ucbhelper::cancelCommandExecution( uno::Any( beans::UnknownPropertyException( "Unable to get property 'IsFolder' from source object!", rContext.xProcessor ) ), rContext.xOrigEnv ); // Unreachable } // IsDocument: property is required. bool bSourceIsDocument = xSourceProps->getBoolean( 2 ); if ( !bSourceIsDocument && xSourceProps->wasNull() ) { ucbhelper::cancelCommandExecution( uno::Any( beans::UnknownPropertyException( "Unable to get property 'IsDocument' from source object!", rContext.xProcessor ) ), rContext.xOrigEnv ); // Unreachable } // TargetURL: property is optional. bool bSourceIsLink = !xSourceProps->getString( 3 ).isEmpty(); // (1) Try to find a matching target type for the source object and // create a new, empty object of that type. uno::Reference< ucb::XContent > xNew = createNew( rContext, xTarget, bSourceIsFolder, bSourceIsDocument, bSourceIsLink ); if ( !xNew.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(rContext.aArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, rContext.xOrigEnv, "No matching content type at target!", rContext.xProcessor ); // Unreachable } // (2) Transfer property values from source to new object. uno::Reference< ucb::XCommandProcessor > xCommandProcessorN( xNew, uno::UNO_QUERY ); if ( !xCommandProcessorN.is() ) { uno::Any aProps(beans::PropertyValue( "Uri", -1, uno::Any( xNew->getIdentifier()-> getContentIdentifier()), beans::PropertyState_DIRECT_VALUE)); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_WRITE, uno::Sequence< uno::Any >(&aProps, 1), rContext.xOrigEnv, "New content is not a XCommandProcessor!", rContext.xProcessor ); // Unreachable } // Obtain all properties from source. uno::Reference< ucb::XCommandProcessor > xCommandProcessorS( xSource, uno::UNO_QUERY ); if ( !xCommandProcessorS.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rContext.aArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Source content is not a XCommandProcessor!", rContext.xProcessor ); // Unreachable } transferProperties( rContext, xCommandProcessorS, xCommandProcessorN ); // (3) Try to obtain a data stream from source. uno::Reference< io::XInputStream > xInputStream; if ( bSourceIsDocument && ( rContext.aArg.Operation != ucb::TransferCommandOperation_LINK ) ) xInputStream = getInputStream( rContext, xCommandProcessorS ); // (4) Try to obtain a resultset (children) from source. uno::Reference< sdbc::XResultSet > xResultSet; if ( bSourceIsFolder && ( rContext.aArg.Operation != ucb::TransferCommandOperation_LINK ) ) xResultSet = getResultSet( rContext, xCommandProcessorS ); // (5) Insert (store) new content. ucb::InsertCommandArgument2 aArg; aArg.Data = xInputStream; aArg.MimeType = rContext.aArg.MimeType; aArg.DocumentId = rContext.aArg.DocumentId; switch ( rContext.aArg.NameClash ) { case ucb::NameClash::OVERWRITE: aArg.ReplaceExisting = true; break; case ucb::NameClash::ERROR: case ucb::NameClash::RENAME: case ucb::NameClash::KEEP: // deprecated case ucb::NameClash::ASK: aArg.ReplaceExisting = false; break; default: aArg.ReplaceExisting = false; OSL_FAIL( "Unknown nameclash directive!" ); break; } OUString aDesiredName = createDesiredName( rContext.aArg ); bool bRetry; do { bRetry = false; try { ucb::Command aInsertCommand( "insert", -1, uno::Any( aArg ) ); xCommandProcessorN->execute( aInsertCommand, 0, rContext.xEnv ); } catch ( ucb::UnsupportedNameClashException const & exc ) { OSL_ENSURE( !aArg.ReplaceExisting, "BUG: UnsupportedNameClashException not allowed here!" ); if (exc.NameClash != ucb::NameClash::ERROR) { OSL_FAIL( "BUG: NameClash::ERROR expected!" ); } // No chance to solve name clashes, because I'm not able to detect // whether there is one. throw ucb::UnsupportedNameClashException( "Unable to resolve name clashes, no chance to detect " "that there is one!", rContext.xProcessor, rContext.aArg.NameClash ); } catch ( ucb::NameClashException const & ) { // The 'insert' command throws a NameClashException if the parameter // ReplaceExisting of the command's argument was set to false and // there exists a resource with a clashing name in the target folder // of the operation. // 'insert' command has no direct support for name clashes other // than ERROR ( ReplaceExisting == false ) and OVERWRITE // ( ReplaceExisting == true ). So we have to implement the // other name clash handling directives on top of the content. // @@@ 'insert' command should be extended that it accepts a // name clash handling directive, exactly like 'transfer' command. switch ( rContext.aArg.NameClash ) { case ucb::NameClash::OVERWRITE: { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedNameClashException( "BUG: insert + replace == true MUST NOT " "throw NameClashException.", rContext.xProcessor, rContext.aArg.NameClash ) ), rContext.xOrigEnv ); [[fallthrough]]; // Unreachable } case ucb::NameClash::ERROR: throw; case ucb::NameClash::RENAME: { // "invent" a new valid title. handleNameClashRename( rContext, xNew, xCommandProcessorN, xCommandProcessorS, xInputStream ); break; } case ucb::NameClash::ASK: { uno::Any aExc; OUString aNewTitle; NameClashContinuation eCont = interactiveNameClashResolve( rContext.xOrigEnv, // always use original environment! rContext.aArg.TargetURL, // target folder URL aDesiredName, aExc, aNewTitle ); switch ( eCont ) { case NOT_HANDLED: // Not handled. cppu::throwException( aExc ); [[fallthrough]]; // break; case UNKNOWN: // Handled, but not clear, how... // fall through intended. case ABORT: throw ucb::CommandFailedException( "abort requested via interaction " "handler", uno::Reference< uno::XInterface >(), aExc ); // break; case OVERWRITE: OSL_ENSURE( !aArg.ReplaceExisting, "Hu? ReplaceExisting already true?" ); aArg.ReplaceExisting = true; bRetry = true; break; case NEW_NAME: { // set new name -> set "Title" property... if ( setTitle( xCommandProcessorN, rContext.xEnv, aNewTitle ) ) { // remember suggested title... aDesiredName = aNewTitle; // ... and try again. bRetry = true; } else { // error setting title. Abort. throw ucb::CommandFailedException( "error setting Title property!", uno::Reference< uno::XInterface >(), aExc ); } break; } } OSL_ENSURE( bRetry, "bRetry must be true here!!!" ); } break; case ucb::NameClash::KEEP: // deprecated default: { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedNameClashException( "default action, don't know how to " "handle name clash", rContext.xProcessor, rContext.aArg.NameClash ) ), rContext.xOrigEnv ); // Unreachable } } } } while ( bRetry ); // (6) Process children of source. if ( xResultSet.is() ) { try { // Iterate over children... uno::Reference< sdbc::XRow > xChildRow( xResultSet, uno::UNO_QUERY ); if ( !xChildRow.is() ) { uno::Any aProps( beans::PropertyValue( "Uri", -1, uno::Any(rContext.aArg.SourceURL), beans::PropertyState_DIRECT_VALUE)); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, uno::Sequence< uno::Any >(&aProps, 1), rContext.xOrigEnv, "Unable to get properties from children of source!", rContext.xProcessor ); // Unreachable } uno::Reference< ucb::XContentAccess > xChildAccess( xResultSet, uno::UNO_QUERY ); if ( !xChildAccess.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rContext.aArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, rContext.xOrigEnv, "Unable to get children of source!", rContext.xProcessor ); // Unreachable } if ( xResultSet->first() ) { ucb::GlobalTransferCommandArgument2 aTransArg( rContext.aArg.Operation, OUString(), // SourceURL; filled later xNew->getIdentifier() ->getContentIdentifier(), // TargetURL OUString(), // NewTitle; rContext.aArg.NameClash, rContext.aArg.MimeType, rContext.aArg.DocumentId); TransferCommandContext aSubCtx( rContext.m_xContext, rContext.xProcessor, rContext.xEnv, rContext.xOrigEnv, std::move(aTransArg) ); do { uno::Reference< ucb::XContent > xChild = xChildAccess->queryContent(); if ( xChild.is() ) { // Recursion! aSubCtx.aArg.SourceURL = xChild->getIdentifier()->getContentIdentifier(); globalTransfer_( aSubCtx, xChild, xNew, xChildRow ); } } while ( xResultSet->next() ); } } catch ( sdbc::SQLException const & ) { } } try { uno::Reference< ucb::XCommandProcessor > xcp( xTarget, uno::UNO_QUERY ); uno::Any aAny; uno::Reference< ucb::XCommandInfo > xci; if(xcp.is()) aAny = xcp->execute( ucb::Command( "getCommandInfo", -1, uno::Any()), 0, rContext.xEnv ); static constexpr OUString cmdName(u"flush"_ustr); if((aAny >>= xci) && xci->hasCommandByName(cmdName)) xcp->execute( ucb::Command( cmdName, -1, uno::Any()) , 0, rContext.xEnv ); } catch( uno::Exception const & ) { } } } /* namespace */ // UniversalContentBroker implementation ( XCommandProcessor commands ). uno::Reference< ucb::XCommandInfo > UniversalContentBroker::getCommandInfo() { return uno::Reference< ucb::XCommandInfo >( new CommandProcessorInfo() ); } void UniversalContentBroker::globalTransfer( const ucb::GlobalTransferCommandArgument2 & rArg, const uno::Reference< ucb::XCommandEnvironment > & xEnv ) { // Use own command environment with own interaction handler intercepting // some interaction requests that shall not be handled by the user-supplied // interaction handler. uno::Reference< ucb::XCommandEnvironment > xLocalEnv; if (xEnv.is()) { xLocalEnv.set( ucb::CommandEnvironment::create( m_xContext, new InteractionHandlerProxy( xEnv->getInteractionHandler() ), xEnv->getProgressHandler() ) ); } // (1) Try to transfer the content using 'transfer' command. uno::Reference< ucb::XContent > xTarget; uno::Reference< ucb::XContentIdentifier > xId = createContentIdentifier( rArg.TargetURL ); if ( xId.is() ) { try { xTarget = queryContent( xId ); } catch ( ucb::IllegalIdentifierException const & ) { } } if ( !xTarget.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Can't instantiate target object!", this ); // Unreachable } if ( ( rArg.Operation == ucb::TransferCommandOperation_COPY ) || ( rArg.Operation == ucb::TransferCommandOperation_MOVE ) ) { uno::Reference< ucb::XCommandProcessor > xCommandProcessor( xTarget, uno::UNO_QUERY ); if ( !xCommandProcessor.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Target content is not a XCommandProcessor!", this ); // Unreachable } ucb::TransferInfo2 aTransferArg( ( rArg.Operation == ucb::TransferCommandOperation_MOVE ), // MoveData rArg.SourceURL, rArg.NewTitle, rArg.NameClash, rArg.MimeType ); bool bRetry; do { bRetry = false; try { ucb::Command aCommand( "transfer", // Name -1, // Handle uno::Any( aTransferArg ) ); // Argument xCommandProcessor->execute( aCommand, 0, xLocalEnv ); // Command succeeded. We're done. return; } catch ( ucb::InteractiveBadTransferURLException const & ) { // Source URL is not supported by target. Try to transfer // the content "manually". } catch ( ucb::UnsupportedCommandException const & ) { // 'transfer' command is not supported by commandprocessor. // Try to transfer manually. } catch ( ucb::UnsupportedNameClashException const & exc ) { OSL_ENSURE( aTransferArg.NameClash == exc.NameClash, "nameclash mismatch!" ); if ( exc.NameClash == ucb::NameClash::ASK ) { // Try to detect a name clash by invoking "transfer" with // NameClash::ERROR. try { ucb::TransferInfo2 aTransferArg1( aTransferArg.MoveData, aTransferArg.SourceURL, aTransferArg.NewTitle, ucb::NameClash::ERROR, aTransferArg.MimeType ); ucb::Command aCommand1( "transfer", -1, uno::Any( aTransferArg1 ) ); xCommandProcessor->execute( aCommand1, 0, xLocalEnv ); // Command succeeded. We're done. return; } catch ( ucb::UnsupportedNameClashException const & ) { // No chance to solve name clashes, because I'm not // able to detect whether there is one. throw exc; // Not just 'throw;'! } catch ( ucb::NameClashException const & ) { // There's a clash. Use interaction handler to solve it. uno::Any aExc; OUString aNewTitle; NameClashContinuation eCont = interactiveNameClashResolve( xEnv, // always use original environment! rArg.TargetURL, // target folder URL createDesiredName( aTransferArg ), // clashing name aExc, aNewTitle ); switch ( eCont ) { case NOT_HANDLED: // Not handled. cppu::throwException( aExc ); [[fallthrough]]; // break; case UNKNOWN: // Handled, but not clear, how... // fall through intended. case ABORT: throw ucb::CommandFailedException( "abort requested via interaction " "handler", uno::Reference< uno::XInterface >(), aExc ); // break; case OVERWRITE: aTransferArg.NameClash = ucb::NameClash::OVERWRITE; bRetry = true; break; case NEW_NAME: aTransferArg.NewTitle = aNewTitle; bRetry = true; break; } OSL_ENSURE( bRetry, "bRetry must be true here!!!" ); } } else { throw; } } } while ( bRetry ); } // (2) Try to transfer the content "manually". uno::Reference< ucb::XContent > xSource; try { uno::Reference< ucb::XContentIdentifier > xId2 = createContentIdentifier( rArg.SourceURL ); if ( xId2.is() ) xSource = queryContent( xId2 ); } catch ( ucb::IllegalIdentifierException const & ) { // Error handling via "if ( !xSource.is() )" below. } if ( !xSource.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Can't instantiate source object!", this ); // Unreachable } uno::Reference< ucb::XCommandProcessor > xCommandProcessor( xSource, uno::UNO_QUERY ); if ( !xCommandProcessor.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Source content is not a XCommandProcessor!", this ); // Unreachable } // Obtain interesting property values from source... uno::Sequence< beans::Property > aProps{ makeProperty("IsFolder", -1 /* unknown */), makeProperty("IsDocument", -1 /* unknown */), makeProperty("TargetURL", -1 /* unknown */), makeProperty("BaseURI", -1 /* unknown */) }; ucb::Command aGetPropsCommand( "getPropertyValues", -1, uno::Any( aProps ) ); uno::Reference< sdbc::XRow > xRow; xCommandProcessor->execute( aGetPropsCommand, 0, xLocalEnv ) >>= xRow; if ( !xRow.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Unable to get properties from source object!", this ); // Unreachable } TransferCommandContext aTransferCtx( m_xContext, this, xLocalEnv, xEnv, rArg ); if ( rArg.NewTitle.isEmpty() ) { // BaseURI: property is optional. OUString aBaseURI( xRow->getString( 4 ) ); if ( !aBaseURI.isEmpty() ) { aTransferCtx.aArg.NewTitle = createDesiredName( aBaseURI, OUString() ); } } // Do it! globalTransfer_( aTransferCtx, xSource, xTarget, xRow ); // (3) Delete source, if operation is MOVE. if ( rArg.Operation != ucb::TransferCommandOperation_MOVE ) return; try { ucb::Command aCommand( "delete", // Name -1, // Handle uno::Any( true ) ); // Argument xCommandProcessor->execute( aCommand, 0, xLocalEnv ); } catch ( uno::Exception const & ) { OSL_FAIL( "Cannot delete source object!" ); throw; } } uno::Any UniversalContentBroker::checkIn( const ucb::CheckinArgument& rArg, const uno::Reference< ucb::XCommandEnvironment >& xEnv ) { uno::Any aRet; // Use own command environment with own interaction handler intercepting // some interaction requests that shall not be handled by the user-supplied // interaction handler. uno::Reference< ucb::XCommandEnvironment > xLocalEnv; if (xEnv.is()) { xLocalEnv.set( ucb::CommandEnvironment::create( m_xContext, new InteractionHandlerProxy( xEnv->getInteractionHandler() ), xEnv->getProgressHandler() ) ); } uno::Reference< ucb::XContent > xTarget; uno::Reference< ucb::XContentIdentifier > xId = createContentIdentifier( rArg.TargetURL ); if ( xId.is() ) { try { xTarget = queryContent( xId ); } catch ( ucb::IllegalIdentifierException const & ) { } } if ( !xTarget.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Can't instantiate target object!", this ); // Unreachable } uno::Reference< ucb::XCommandProcessor > xCommandProcessor( xTarget, uno::UNO_QUERY ); if ( !xCommandProcessor.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rArg.TargetURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Target content is not a XCommandProcessor!", this ); // Unreachable } try { ucb::Command aCommand( "checkin", -1, uno::Any( rArg ) ); aRet = xCommandProcessor->execute( aCommand, 0, xLocalEnv ); } catch ( ucb::UnsupportedCommandException const & ) { // 'checkin' command is not supported by commandprocessor: // ignore. } return aRet; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */