1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <config_features.h>
23 #include <strings.hrc>
24 #include <dp_backend.h>
26 #include "dp_helpbackenddb.hxx"
28 #include <rtl/uri.hxx>
29 #include <osl/file.hxx>
30 #include <ucbhelper/content.hxx>
31 #include <svl/inettype.hxx>
32 #include <unotools/pathoptions.hxx>
33 #include <cppuhelper/supportsservice.hxx>
34 #include <o3tl/string_view.hxx>
36 #if HAVE_FEATURE_XMLHELP
37 #include <helpcompiler/compilehelp.hxx>
38 #include <helpcompiler/HelpIndexer.hxx>
40 #include <com/sun/star/deployment/DeploymentException.hpp>
41 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
42 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
43 #include <com/sun/star/util/XMacroExpander.hpp>
45 #include <string_view>
47 using namespace ::dp_misc
;
48 using namespace ::com::sun::star
;
49 using namespace ::com::sun::star::uno
;
50 using namespace ::com::sun::star::ucb
;
52 namespace dp_registry::backend::help
{
56 class BackendImpl
: public ::dp_registry::backend::PackageRegistryBackend
58 class PackageImpl
: public ::dp_registry::backend::Package
60 BackendImpl
* getMyBackend() const;
63 virtual beans::Optional
< beans::Ambiguous
<sal_Bool
> > isRegistered_(
64 ::osl::ResettableMutexGuard
& guard
,
65 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
66 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
67 virtual void processPackage_(
68 ::osl::ResettableMutexGuard
& guard
,
71 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
72 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
77 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
78 OUString
const & url
, OUString
const & name
,
79 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
80 bool bRemoved
, OUString
const & identifier
);
82 bool extensionContainsCompiledHelp();
85 virtual css::beans::Optional
< OUString
> SAL_CALL
getRegistrationDataURL() override
;
87 friend class PackageImpl
;
89 // PackageRegistryBackend
90 virtual Reference
<deployment::XPackage
> bindPackage_(
91 OUString
const & url
, OUString
const & mediaType
,
92 bool bRemoved
, OUString
const & identifier
,
93 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
95 void implProcessHelp( PackageImpl
* package
, bool doRegisterPackage
,
96 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
);
97 void implCollectXhpFiles( const OUString
& aDir
,
98 std::vector
< OUString
>& o_rXhpFileVector
);
100 ::std::optional
<HelpBackendDb::Data
> readDataFromDb(std::u16string_view url
);
101 bool hasActiveEntry(std::u16string_view url
);
102 bool activateEntry(std::u16string_view url
);
104 Reference
< ucb::XSimpleFileAccess3
> const & getFileAccess();
105 Reference
< ucb::XSimpleFileAccess3
> m_xSFA
;
107 const Reference
<deployment::XPackageTypeInfo
> m_xHelpTypeInfo
;
108 Sequence
< Reference
<deployment::XPackageTypeInfo
> > m_typeInfos
;
109 std::unique_ptr
<HelpBackendDb
> m_backendDb
;
112 BackendImpl( Sequence
<Any
> const & args
,
113 Reference
<XComponentContext
> const & xComponentContext
);
116 virtual OUString SAL_CALL
getImplementationName() override
;
117 virtual sal_Bool SAL_CALL
supportsService( const OUString
& ServiceName
) override
;
118 virtual css::uno::Sequence
< OUString
> SAL_CALL
getSupportedServiceNames() override
;
121 virtual Sequence
< Reference
<deployment::XPackageTypeInfo
> > SAL_CALL
122 getSupportedPackageTypes() override
;
123 virtual void SAL_CALL
packageRemoved(OUString
const & url
, OUString
const & mediaType
) override
;
128 BackendImpl::BackendImpl(
129 Sequence
<Any
> const & args
,
130 Reference
<XComponentContext
> const & xComponentContext
)
131 : PackageRegistryBackend( args
, xComponentContext
),
132 m_xHelpTypeInfo( new Package::TypeInfo(u
"application/vnd.sun.star.help"_ustr
,
134 DpResId(RID_STR_HELP
)
136 m_typeInfos
{ m_xHelpTypeInfo
}
141 OUString dbFile
= makeURL(getCachePath(), u
"backenddb.xml"_ustr
);
143 new HelpBackendDb(getComponentContext(), dbFile
));
145 //clean up data folders which are no longer used.
146 //This must not be done in the same process where the help files
147 //are still registers. Only after revoking and restarting OOo the folders
148 //can be removed. This works now, because the extension manager is a singleton
149 //and the backends are only create once per process.
150 std::vector
<OUString
> folders
= m_backendDb
->getAllDataUrls();
151 deleteUnusedFolders(folders
);
155 OUString
BackendImpl::getImplementationName()
157 return u
"com.sun.star.comp.deployment.help.PackageRegistryBackend"_ustr
;
160 sal_Bool
BackendImpl::supportsService( const OUString
& ServiceName
)
162 return cppu::supportsService(this, ServiceName
);
165 css::uno::Sequence
< OUString
> BackendImpl::getSupportedServiceNames()
167 return { BACKEND_SERVICE_NAME
};
172 Sequence
< Reference
<deployment::XPackageTypeInfo
> >
173 BackendImpl::getSupportedPackageTypes()
178 void BackendImpl::packageRemoved(OUString
const & url
, OUString
const & /*mediaType*/)
181 m_backendDb
->removeEntry(url
);
184 // PackageRegistryBackend
186 Reference
<deployment::XPackage
> BackendImpl::bindPackage_(
187 OUString
const & url
, OUString
const & mediaType_
,
188 bool bRemoved
, OUString
const & identifier
,
189 Reference
<XCommandEnvironment
> const & xCmdEnv
)
191 // we don't support auto detection:
192 if (mediaType_
.isEmpty())
193 throw lang::IllegalArgumentException(
194 StrCannotDetectMediaType() + url
,
195 static_cast<OWeakObject
*>(this), static_cast<sal_Int16
>(-1) );
197 OUString type
, subType
;
198 INetContentTypeParameterList params
;
199 if (INetContentTypes::parse( mediaType_
, type
, subType
, ¶ms
))
201 if (type
.equalsIgnoreAsciiCase("application"))
206 ::ucbhelper::Content
ucbContent(
207 url
, xCmdEnv
, getComponentContext() );
208 name
= StrTitle::getTitle( ucbContent
);
211 if (subType
.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
213 return new PackageImpl(
214 this, url
, name
, m_xHelpTypeInfo
, bRemoved
,
219 throw lang::IllegalArgumentException(
220 StrUnsupportedMediaType() + mediaType_
,
221 static_cast<OWeakObject
*>(this),
222 static_cast<sal_Int16
>(-1) );
225 ::std::optional
<HelpBackendDb::Data
> BackendImpl::readDataFromDb(
226 std::u16string_view url
)
228 ::std::optional
<HelpBackendDb::Data
> data
;
230 data
= m_backendDb
->getEntry(url
);
234 bool BackendImpl::hasActiveEntry(std::u16string_view url
)
237 return m_backendDb
->hasActiveEntry(url
);
241 bool BackendImpl::activateEntry(std::u16string_view url
)
244 return m_backendDb
->activateEntry(url
);
249 BackendImpl::PackageImpl::PackageImpl(
250 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
251 OUString
const & url
, OUString
const & name
,
252 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
253 bool bRemoved
, OUString
const & identifier
)
254 : Package( myBackend
, url
, name
, name
, xPackageType
, bRemoved
,
260 BackendImpl
* BackendImpl::PackageImpl::getMyBackend() const
262 BackendImpl
* pBackend
= static_cast<BackendImpl
*>(m_myBackend
.get());
263 if (nullptr == pBackend
)
265 //May throw a DisposedException
267 //We should never get here...
268 throw RuntimeException(u
"Failed to get the BackendImpl"_ustr
,
269 static_cast<OWeakObject
*>(const_cast<PackageImpl
*>(this)));
274 bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
276 bool bCompiled
= true;
277 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl(getURL());
279 ::osl::Directory
helpFolder(aExpandedHelpURL
);
280 if ( helpFolder
.open() == ::osl::File::E_None
)
282 //iterate over the contents of the help folder
283 //We assume that all folders within the help folder contain language specific
284 //help files. If just one of them does not contain compiled help then this
285 //function returns false.
286 ::osl::DirectoryItem item
;
287 ::osl::File::RC errorNext
= ::osl::File::E_None
;
288 while ((errorNext
= helpFolder
.getNextItem(item
)) == ::osl::File::E_None
)
290 //No find the language folders
291 ::osl::FileStatus
stat(osl_FileStatus_Mask_Type
| osl_FileStatus_Mask_FileName
|osl_FileStatus_Mask_FileURL
);
292 if (item
.getFileStatus(stat
) == ::osl::File::E_None
)
294 if (stat
.getFileType() != ::osl::FileStatus::Directory
)
297 //look if there is the folder help.idxl in the language folder
298 OUString
compUrl(stat
.getFileURL() + "/help.idxl");
299 ::osl::Directory
compiledFolder(compUrl
);
300 if (compiledFolder
.open() != ::osl::File::E_None
)
314 if (errorNext
!= ::osl::File::E_NOENT
315 && errorNext
!= ::osl::File::E_None
)
326 beans::Optional
< beans::Ambiguous
<sal_Bool
> >
327 BackendImpl::PackageImpl::isRegistered_(
328 ::osl::ResettableMutexGuard
&,
329 ::rtl::Reference
<AbortChannel
> const &,
330 Reference
<XCommandEnvironment
> const & )
332 BackendImpl
* that
= getMyBackend();
335 if (that
->hasActiveEntry(getURL()))
338 return beans::Optional
< beans::Ambiguous
<sal_Bool
> >( true, beans::Ambiguous
<sal_Bool
>( bReg
, false ) );
342 void BackendImpl::PackageImpl::processPackage_(
343 ::osl::ResettableMutexGuard
&,
344 bool doRegisterPackage
,
346 ::rtl::Reference
<AbortChannel
> const &,
347 Reference
<XCommandEnvironment
> const & xCmdEnv
)
349 BackendImpl
* that
= getMyBackend();
350 that
->implProcessHelp( this, doRegisterPackage
, xCmdEnv
);
353 beans::Optional
< OUString
> BackendImpl::PackageImpl::getRegistrationDataURL()
356 throw deployment::ExtensionRemovedException();
358 ::std::optional
<HelpBackendDb::Data
> data
=
359 getMyBackend()->readDataFromDb(getURL());
361 if (data
&& getMyBackend()->hasActiveEntry(getURL()))
362 return beans::Optional
<OUString
>(true, data
->dataUrl
);
364 return beans::Optional
<OUString
>(true, OUString());
367 void BackendImpl::implProcessHelp(
368 PackageImpl
* package
, bool doRegisterPackage
,
369 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
)
371 Reference
< deployment::XPackage
> xPackage(package
);
372 OSL_ASSERT(xPackage
.is());
373 if (doRegisterPackage
)
375 //revive already processed help if possible
376 if ( !activateEntry(xPackage
->getURL()))
378 HelpBackendDb::Data data
;
379 data
.dataUrl
= xPackage
->getURL();
380 if (!package
->extensionContainsCompiledHelp())
382 #if HAVE_FEATURE_XMLHELP
383 const OUString sHelpFolder
= createFolder(xCmdEnv
);
384 data
.dataUrl
= sHelpFolder
;
386 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
387 OUString aHelpURL
= xPackage
->getURL();
388 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl( aHelpURL
);
389 if( !xSFA
->isFolder( aExpandedHelpURL
) )
391 OUString aErrStr
= DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR
) +
393 OWeakObject
* oWeakThis
= this;
394 throw deployment::DeploymentException( OUString(), oWeakThis
,
395 Any( uno::Exception( aErrStr
, oWeakThis
) ) );
399 for (auto& aLangURL
: xSFA
->getFolderContents(aExpandedHelpURL
, true))
401 if( xSFA
->isFolder( aLangURL
) )
403 std::vector
< OUString
> aXhpFileVector
;
405 // calculate jar file URL
406 sal_Int32 indexStartSegment
= aLangURL
.lastIndexOf('/');
408 OUString
langFolderURLSegment(
410 indexStartSegment
+ 1, aLangURL
.getLength() - indexStartSegment
- 1));
412 //create the folder in the "temporary folder"
413 ::ucbhelper::Content langFolderContent
;
414 const OUString langFolderDest
= makeURL(sHelpFolder
, langFolderURLSegment
);
415 const OUString langFolderDestExpanded
= ::dp_misc::expandUnoRcUrl(langFolderDest
);
416 ::dp_misc::create_folder(
418 langFolderDest
, xCmdEnv
);
420 static constexpr OUString
aHelpStr(u
"help"_ustr
);
423 makeURL(sHelpFolder
, langFolderURLSegment
+ "/" + aHelpStr
+ ".jar"));
424 aJarFile
= ::dp_misc::expandUnoRcUrl(aJarFile
);
426 OUString aEncodedJarFilePath
= rtl::Uri::encode(
427 aJarFile
, rtl_UriCharClassPchar
,
428 rtl_UriEncodeIgnoreEscapes
,
429 RTL_TEXTENCODING_UTF8
);
430 OUString aDestBasePath
= "vnd.sun.star.zip://" +
431 aEncodedJarFilePath
+ "/" ;
433 sal_Int32 nLenLangFolderURL
= aLangURL
.getLength() + 1;
435 for (auto& aSubFolderURL
: xSFA
->getFolderContents(aLangURL
, true))
437 if( !xSFA
->isFolder( aSubFolderURL
) )
440 implCollectXhpFiles( aSubFolderURL
, aXhpFileVector
);
442 // Copy to package (later: move?)
443 std::u16string_view aPureFolderName
= aSubFolderURL
.subView( nLenLangFolderURL
);
444 OUString aDestPath
= aDestBasePath
+ aPureFolderName
;
445 xSFA
->copy( aSubFolderURL
, aDestPath
);
449 sal_Int32 nXhpFileCount
= aXhpFileVector
.size();
450 std::unique_ptr
<OUString
[]> pXhpFiles(new OUString
[nXhpFileCount
]);
451 for( sal_Int32 iXhp
= 0 ; iXhp
< nXhpFileCount
; ++iXhp
)
453 const OUString
& aXhpFile
= aXhpFileVector
[iXhp
];
454 pXhpFiles
[iXhp
] = aXhpFile
.copy( nLenLangFolderURL
);
457 OUString
aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
458 OUString aOfficeHelpPathFileURL
;
459 ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath
, aOfficeHelpPathFileURL
);
461 HelpProcessingErrorInfo aErrorInfo
;
462 bool bSuccess
= compileExtensionHelp(
463 aOfficeHelpPathFileURL
, aHelpStr
, aLangURL
,
464 nXhpFileCount
, pXhpFiles
.get(),
465 langFolderDestExpanded
, aErrorInfo
);
472 sal_Int32 nLastSlash
= aLangURL
.lastIndexOf( '/' );
473 if( nLastSlash
!= -1 )
474 aLang
= aLangURL
.copy( nLastSlash
+ 1 );
478 HelpIndexer
aIndexer(aLang
, u
"help"_ustr
, langFolderDestExpanded
, langFolderDestExpanded
);
479 aIndexer
.indexDocuments();
484 TranslateId pErrStrId
;
485 switch( aErrorInfo
.m_eErrorClass
)
487 case HelpProcessingErrorClass::General
: pErrStrId
= RID_STR_HELPPROCESSING_GENERAL_ERROR
; break;
488 case HelpProcessingErrorClass::XmlParsing
: pErrStrId
= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
; break;
495 aErrStr
= DpResId(pErrStrId
);
498 OUString
aErrMsg( aErrorInfo
.m_aErrorMsg
);
499 sal_Unicode
const nCR
= 13, nLF
= 10;
500 sal_Int32 nSearchCR
= aErrMsg
.indexOf( nCR
);
501 sal_Int32 nSearchLF
= aErrMsg
.indexOf( nLF
);
502 if( nSearchCR
!= -1 || nSearchLF
!= -1 )
505 if( nSearchCR
== -1 )
507 else if( nSearchLF
== -1 )
510 nCopy
= ( nSearchCR
< nSearchLF
) ? nSearchCR
: nSearchLF
;
512 aErrMsg
= aErrMsg
.copy( 0, nCopy
);
515 if (pErrStrId
!= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
&& !aErrorInfo
.m_aXMLParsingFile
.isEmpty() )
519 OUString aDecodedFile
= rtl::Uri::decode( aErrorInfo
.m_aXMLParsingFile
,
520 rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
521 aErrStr
+= aDecodedFile
;
522 if( aErrorInfo
.m_nXMLParsingLine
!= -1 )
524 aErrStr
+= ", line " +
525 OUString::number( aErrorInfo
.m_nXMLParsingLine
);
530 OWeakObject
* oWeakThis
= this;
531 throw deployment::DeploymentException( OUString(), oWeakThis
,
532 Any( uno::Exception( aErrStr
, oWeakThis
) ) );
540 // Writing the data entry replaces writing the flag file. If we got to this
541 // point the registration was successful.
543 m_backendDb
->addEntry(xPackage
->getURL(), data
);
545 } //if (doRegisterPackage)
549 m_backendDb
->revokeEntry(xPackage
->getURL());
553 void BackendImpl::implCollectXhpFiles( const OUString
& aDir
,
554 std::vector
< OUString
>& o_rXhpFileVector
)
556 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
558 // Scan xhp files recursively
559 for (auto& aURL
: xSFA
->getFolderContents(aDir
, true))
561 if( xSFA
->isFolder( aURL
) )
563 implCollectXhpFiles( aURL
, o_rXhpFileVector
);
567 sal_Int32 nLastDot
= aURL
.lastIndexOf( '.' );
570 std::u16string_view aExt
= aURL
.subView( nLastDot
+ 1 );
571 if( o3tl::equalsIgnoreAsciiCase( aExt
, u
"xhp" ) )
572 o_rXhpFileVector
.push_back( aURL
);
578 Reference
< ucb::XSimpleFileAccess3
> const & BackendImpl::getFileAccess()
582 Reference
<XComponentContext
> const & xContext
= getComponentContext();
585 m_xSFA
= ucb::SimpleFileAccess::create(xContext
);
589 throw RuntimeException(
590 u
"dp_registry::backend::help::BackendImpl::getFileAccess(), "
591 "could not instantiate SimpleFileAccess."_ustr
);
599 } // namespace dp_registry
601 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
602 com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation(
603 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const& args
)
605 return cppu::acquire(new dp_registry::backend::help::BackendImpl(args
, context
));
608 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */