singleprov/ScriptFile: Implement XCopyableBrowseNode

Implements XCopyableBrowseNode on the ScriptFile class so that all
script providers based on the single-file provider can support copying.

Change-Id: I14aa057ac432ff80e4a4841fbe5c19be082aa099
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208161
Tested-by: Jenkins
Reviewed-by: Neil Roberts <nroberts@libreoffice.org>
This commit is contained in:
Neil Roberts
2026-07-23 15:18:33 +02:00
parent 54ba86b578
commit 6a4bf34e5a
8 changed files with 274 additions and 9 deletions
+126 -4
View File
@@ -12,10 +12,12 @@
#include <com/sun/star/lang/XSingleComponentFactory.hpp>
#include <com/sun/star/script/browse/BrowseNodeTypes.hpp>
#include <com/sun/star/script/browse/XBrowseNode.hpp>
#include <com/sun/star/script/browse/XCopyableBrowseNode.hpp>
#include <com/sun/star/script/browse/XCreatableBrowseNode.hpp>
#include <com/sun/star/script/browse/XEditableBrowseNode.hpp>
#include <com/sun/star/script/provider/ScriptURIHelper.hpp>
#include <com/sun/star/script/provider/XScriptProvider.hpp>
#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
@@ -41,12 +43,14 @@ private:
css::uno::Reference<css::script::provider::XScriptProvider> m_xScriptProvider;
css::uno::Reference<css::script::provider::XScriptProvider> createScriptProvider();
void createDummyMacro(const OUString& sDirectoryUri);
void testEditableCreatable();
void testCreate();
void testRun();
void testUnrelatedFile();
void testFolder();
void testCopyable();
CPPUNIT_TEST_SUITE(JsProvTest);
CPPUNIT_TEST(testEditableCreatable);
@@ -54,6 +58,7 @@ private:
CPPUNIT_TEST(testRun);
CPPUNIT_TEST(testUnrelatedFile);
CPPUNIT_TEST(testFolder);
CPPUNIT_TEST(testCopyable);
CPPUNIT_TEST_SUITE_END();
};
@@ -144,7 +149,7 @@ bool isCreatable(const css::uno::Reference<css::script::browse::XBrowseNode>& xN
void JsProvTest::testEditableCreatable()
{
// Create a dummy macro in the script directory
m_xFileAccess->openFileWrite(m_xUriHelper->getRootStorageURI() + "/MyScript.js");
createDummyMacro(m_xUriHelper->getRootStorageURI() + "/MyScript.js");
// The root node should be creatable but not editable
css::uno::Reference<css::script::browse::XBrowseNode> xRootNode(m_xScriptProvider,
@@ -229,11 +234,11 @@ void JsProvTest::testCreate()
CPPUNIT_ASSERT_EQUAL(u"My Script"_ustr, xBrowseNode->getChildNodes()[0]->getName());
}
void JsProvTest::testRun()
void JsProvTest::createDummyMacro(const OUString& sDirectoryUri)
{
// Create a dummy macro in the script directory
// Create a dummy macro in the given directory
css::uno::Reference<css::io::XOutputStream> xOutput
= m_xFileAccess->openFileWrite(m_xUriHelper->getRootStorageURI() + "/MyScript.js");
= m_xFileAccess->openFileWrite(sDirectoryUri);
// Make the script insert some text in all of the writer documents
static constexpr OString sSource
@@ -251,6 +256,11 @@ void JsProvTest::testRun()
xOutput->writeBytes(aSource);
xOutput->closeOutput();
}
void JsProvTest::testRun()
{
createDummyMacro(m_xUriHelper->getRootStorageURI() + "/MyScript.js");
// Get the macro as a property set
css::uno::Reference<css::script::browse::XBrowseNode> xBrowseNode(m_xScriptProvider,
@@ -326,6 +336,118 @@ void JsProvTest::testFolder()
CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xBrowseNode->getName());
}
bool isCopyableNode(const css::uno::Reference<css::script::browse::XBrowseNode>& xNode)
{
css::uno::Reference<css::script::browse::XCopyableBrowseNode> xCopyable(xNode,
css::uno::UNO_QUERY);
return xCopyable.is() && xCopyable->isCopyableNode();
}
void JsProvTest::testCopyable()
{
// Create a dummy macro in a subfolder of the script directory
m_xFileAccess->createFolder(m_xUriHelper->getRootStorageURI() + "/SrcDir");
createDummyMacro(m_xUriHelper->getRootStorageURI() + "/SrcDir/MyScript.js");
css::uno::Reference<css::script::browse::XBrowseNode> xRootNode(m_xScriptProvider,
css::uno::UNO_QUERY_THROW);
// The root node shouldn’t be copyable
CPPUNIT_ASSERT(!isCopyableNode(xRootNode));
css::uno::Reference<css::script::browse::XBrowseNode> xLibrary = xRootNode->getChildNodes()[0];
CPPUNIT_ASSERT_EQUAL(u"SrcDir"_ustr, xLibrary->getName());
// The library shouldn’t be copyable
CPPUNIT_ASSERT(!isCopyableNode(xLibrary));
css::uno::Reference<css::script::browse::XBrowseNode> xFile = xLibrary->getChildNodes()[0];
CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xFile->getName());
// File nodes are copyable
CPPUNIT_ASSERT(isCopyableNode(xFile));
css::uno::Reference<css::script::browse::XBrowseNode> xMacro = xFile->getChildNodes()[0];
CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xMacro->getName());
// Macro nodes are not copyable
CPPUNIT_ASSERT(!isCopyableNode(xMacro));
css::uno::Reference<css::script::browse::XCopyableBrowseNode> xCopyable(
xFile, css::uno::UNO_QUERY_THROW);
// We can copy to the root level of the JavaScript hierarchy
CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xRootNode));
// We can’t copy to the same directory that the file is already in
CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xLibrary));
// We can’t copy to a file
CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xFile));
// or a macro
CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xMacro));
// We shouldn’t be able to copy to any other language providers
css::uno::Reference<css::script::provider::XScriptProviderFactory> xMasterProviderFactory
= css::script::provider::theMasterScriptProviderFactory::get(m_xContext);
css::uno::Reference<css::script::browse::XBrowseNode> xMasterProvider(
xMasterProviderFactory->createScriptProvider(css::uno::Any(u"user"_ustr)),
css::uno::UNO_QUERY_THROW);
for (const auto& xChild : xMasterProvider->getChildNodes())
{
if (xChild->getName() == u"JavaScript"_ustr)
continue;
CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xChild));
}
// Make a new library that we can copy to
m_xFileAccess->createFolder(m_xUriHelper->getRootStorageURI() + "/DestDir");
css::uno::Reference<css::script::browse::XBrowseNode> xOtherLibrary;
for (const auto& xChild : xRootNode->getChildNodes())
{
if (xChild->getName() == u"DestDir"_ustr)
{
xOtherLibrary = xChild;
break;
}
}
CPPUNIT_ASSERT(xOtherLibrary.is());
// We should be able to copy into the new library
CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xOtherLibrary));
css::uno::Reference<css::script::browse::XBrowseNode> xCopiedNode
= xCopyable->copyNode(xOtherLibrary);
CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xCopiedNode->getName());
css::uno::Reference<css::script::browse::XBrowseNode> xCopiedMacro
= xCopiedNode->getChildNodes()[0];
CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xCopiedMacro->getName());
// Get the URI of the copied macro
css::uno::Reference<css::beans::XPropertySet> xPropertySet(xCopiedMacro,
css::uno::UNO_QUERY_THROW);
css::uno::Any xURI = xPropertySet->getPropertyValue(u"URI"_ustr);
OUString sURI;
CPPUNIT_ASSERT(xURI >>= sURI);
// The URI should contain the new library name
CPPUNIT_ASSERT(sURI.indexOf(":DestDir|") != -1);
// Create a document to run the script on
loadFromURL(u"private:factory/swriter"_ustr);
// Execute it
css::uno::Sequence<css::uno::Any> aScriptParams;
css::uno::Sequence<sal_Int16> aOutParamIndex;
css::uno::Sequence<css::uno::Any> aOutParam;
m_xScriptProvider->getScript(sURI)->invoke(aScriptParams, aOutParamIndex, aOutParam);
// Check that the text was inserted into our test document
css::uno::Reference<css::text::XTextDocument> xTextDocument(mxComponent,
css::uno::UNO_QUERY_THROW);
CPPUNIT_ASSERT_EQUAL(u"jsprovtest"_ustr, xTextDocument->getText()->getString());
}
CPPUNIT_TEST_SUITE_REGISTRATION(JsProvTest);
} // namespace
@@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <rtl/ustring.hxx>
#include <memory>
#include <optional>
namespace singleprovider
{
class SingleScriptFactory;
class SAL_LOPLUGIN_ANNOTATE("crosscast") DirectoryNode
{
public:
virtual std::shared_ptr<SingleScriptFactory> getScriptFactory() const = 0;
virtual std::optional<OUString> getDirectoryUri() const = 0;
};
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
@@ -9,6 +9,8 @@
#pragma once
#include <singleprov/directorynode.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/script/browse/XBrowseNode.hpp>
#include <com/sun/star/script/browse/XCreatableBrowseNode.hpp>
@@ -27,10 +29,12 @@ class ScriptBrowser;
class SingleScriptFactory;
class ScriptProvider
: public cppu::WeakImplHelper<
css::lang::XInitialization, css::script::browse::XBrowseNode,
css::script::browse::XCreatableBrowseNode, css::script::browse::XEditableBrowseNode,
css::beans::XPropertySet, css::script::provider::XScriptProvider, css::lang::XServiceInfo>
: public cppu::WeakImplHelper<css::lang::XInitialization, css::script::browse::XBrowseNode,
css::script::browse::XCreatableBrowseNode,
css::script::browse::XEditableBrowseNode,
css::beans::XPropertySet, css::script::provider::XScriptProvider,
css::lang::XServiceInfo>,
public DirectoryNode
{
public:
// XInitialization
@@ -79,6 +83,10 @@ public:
const OUString& PropertyName,
const css::uno::Reference<css::beans::XVetoableChangeListener>& aListener) override;
// DirectoryNode
std::shared_ptr<SingleScriptFactory> getScriptFactory() const override;
std::optional<OUString> getDirectoryUri() const override;
static SAL_DLLPUBLIC_EXPORT OUString
getImplementationNameStatic(std::u16string_view sLanguageName);
static SAL_DLLPUBLIC_EXPORT css::uno::Sequence<OUString>
+10
View File
@@ -134,6 +134,16 @@ css::uno::Reference<css::script::browse::XBrowseNode>
return new ScriptFile(m_pProviderContext, sMacroName, sUrl);
}
std::shared_ptr<SingleScriptFactory> ScriptDir::getScriptFactory() const
{
return m_pProviderContext->m_pSingleScriptFactory;
}
std::optional<OUString> ScriptDir::getDirectoryUri() const
{
return std::make_optional(m_sBaseUri);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
+8 -1
View File
@@ -9,6 +9,8 @@
#pragma once
#include <singleprov/directorynode.hxx>
#include <com/sun/star/script/browse/XBrowseNode.hpp>
#include <com/sun/star/script/browse/XCreatableBrowseNode.hpp>
#include <cppuhelper/implbase.hxx>
@@ -19,7 +21,8 @@ namespace singleprovider
class ProviderContext;
class ScriptDir : public cppu::WeakImplHelper<css::script::browse::XBrowseNode,
css::script::browse::XCreatableBrowseNode>
css::script::browse::XCreatableBrowseNode>,
public DirectoryNode
{
public:
ScriptDir(const std::shared_ptr<ProviderContext>& pProviderContext);
@@ -38,6 +41,10 @@ public:
css::uno::Reference<css::script::browse::XBrowseNode>
SAL_CALL createNode(const OUString& sName) override;
// DirectoryNode
std::shared_ptr<SingleScriptFactory> getScriptFactory() const override;
std::optional<OUString> getDirectoryUri() const override;
private:
OUString nameFromUrl(const OUString& sUrl);
@@ -12,6 +12,7 @@
#include <com/sun/star/script/browse/BrowseNodeTypes.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <singleprov/directorynode.hxx>
#include <singleprov/singlescriptfactory.hxx>
#include "externaledit.hxx"
@@ -45,6 +46,67 @@ sal_Bool SAL_CALL ScriptFile::hasChildNodes() { return true; }
sal_Int16 SAL_CALL ScriptFile::getType() { return css::script::browse::BrowseNodeTypes::CONTAINER; }
sal_Bool SAL_CALL ScriptFile::isCopyableNode() { return true; }
std::optional<OUString> ScriptFile::getCopyDestinationUri(
const css::uno::Reference<css::script::browse::XBrowseNode>& xDest) const
{
const DirectoryNode* pDirectoryNode = dynamic_cast<const DirectoryNode*>(xDest.get());
if (!pDirectoryNode)
return std::nullopt;
// Don’t allow copying into providers for other languages
if (pDirectoryNode->getScriptFactory()->getLanguageName()
!= m_pProviderContext->m_pSingleScriptFactory->getLanguageName())
{
return std::nullopt;
}
std::optional<OUString> sDirectoryUri = pDirectoryNode->getDirectoryUri();
// Don’t allow copying into the same directory
if (sDirectoryUri)
{
sal_Int32 nDirEnd = m_sBaseUri.lastIndexOf(u'/');
if (sDirectoryUri.value() == (nDirEnd == -1 ? m_sBaseUri : m_sBaseUri.copy(0, nDirEnd)))
return std::nullopt;
}
return sDirectoryUri;
}
sal_Bool SAL_CALL ScriptFile::nodeCanBeCopiedTo(
const css::uno::Reference<css::script::browse::XBrowseNode>& xParentNode)
{
return getCopyDestinationUri(xParentNode).has_value();
}
css::uno::Reference<css::script::browse::XBrowseNode> SAL_CALL
ScriptFile::copyNode(const css::uno::Reference<css::script::browse::XBrowseNode>& xParentNode)
{
std::optional<OUString> sDirectoryUri = getCopyDestinationUri(xParentNode);
if (!sDirectoryUri)
{
throw css::lang::IllegalArgumentException(u"Invalid parent node passed to copyNode"_ustr,
getXWeak(), 0);
}
const css::uno::Reference<css::ucb::XSimpleFileAccess3>& xFileAccess
= m_pProviderContext->m_xFileAccess;
xFileAccess->createFolder(sDirectoryUri.value());
std::u16string_view sBaseName = m_sBaseUri.subView(m_sBaseUri.lastIndexOf(u'/') + 1);
OUString sTargetUri = sDirectoryUri.value() + OUStringChar('/') + sBaseName;
xFileAccess->copy(m_sBaseUri, sTargetUri);
return new ScriptFile(m_pProviderContext, m_sName, sTargetUri);
}
sal_Bool SAL_CALL ScriptFile::isEditableNode()
{
return isEditable(m_pProviderContext, m_sBaseUri);
@@ -10,15 +10,18 @@
#pragma once
#include <com/sun/star/script/browse/XBrowseNode.hpp>
#include <com/sun/star/script/browse/XCopyableBrowseNode.hpp>
#include <com/sun/star/script/browse/XEditableBrowseNode.hpp>
#include <cppuhelper/implbase.hxx>
#include <memory>
#include <optional>
namespace singleprovider
{
class ProviderContext;
class ScriptFile : public cppu::WeakImplHelper<css::script::browse::XBrowseNode,
css::script::browse::XCopyableBrowseNode,
css::script::browse::XEditableBrowseNode>
{
public:
@@ -32,6 +35,13 @@ public:
sal_Bool SAL_CALL hasChildNodes() override;
sal_Int16 SAL_CALL getType() override;
// XCopyableBrowseNode
sal_Bool SAL_CALL isCopyableNode() override;
sal_Bool SAL_CALL nodeCanBeCopiedTo(
const css::uno::Reference<css::script::browse::XBrowseNode>& xParentNode) override;
css::uno::Reference<css::script::browse::XBrowseNode> SAL_CALL
copyNode(const css::uno::Reference<css::script::browse::XBrowseNode>& xParentNode) override;
// XEditableBrowseNode
sal_Bool SAL_CALL isEditableNode() override;
sal_Bool SAL_CALL editNode() override;
@@ -41,6 +51,9 @@ private:
OUString m_sName;
OUString m_sBaseUri;
std::optional<OUString>
getCopyDestinationUri(const css::uno::Reference<css::script::browse::XBrowseNode>& xDest) const;
};
}
@@ -203,6 +203,20 @@ sal_Bool SAL_CALL ScriptProvider::editNode()
return false;
}
std::shared_ptr<SingleScriptFactory> ScriptProvider::getScriptFactory() const
{
return m_pProviderContext->m_pSingleScriptFactory;
}
std::optional<OUString> ScriptProvider::getDirectoryUri() const
{
if (const DirectoryNode* pDirectoryNode
= dynamic_cast<const DirectoryNode*>(m_xRootBrowser.get()))
return pDirectoryNode->getDirectoryUri();
else
return std::nullopt;
}
OUString SAL_CALL ScriptProvider::getImplementationName()
{
return getImplementationNameStatic(