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_feature_desktop.h>
23 #include <strings.hrc>
24 #include <dp_backend.h>
25 #include "dp_helpbackenddb.hxx"
26 #include <dp_services.hxx>
28 #include <rtl/uri.hxx>
29 #include <osl/file.hxx>
30 #include <rtl/bootstrap.hxx>
31 #include <ucbhelper/content.hxx>
32 #include <comphelper/servicedecl.hxx>
33 #include <svl/inettype.hxx>
34 #include <uno/current_context.hxx>
35 #include <unotools/pathoptions.hxx>
37 #if HAVE_FEATURE_DESKTOP
38 #include <helpcompiler/compilehelp.hxx>
39 #include <helpcompiler/HelpIndexer.hxx>
41 #include <com/sun/star/deployment/DeploymentException.hpp>
42 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
43 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
44 #include <com/sun/star/util/XMacroExpander.hpp>
45 #include <boost/optional.hpp>
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
{
58 class BackendImpl
: public ::dp_registry::backend::PackageRegistryBackend
60 class PackageImpl
: public ::dp_registry::backend::Package
62 BackendImpl
* getMyBackend() const;
65 virtual beans::Optional
< beans::Ambiguous
<sal_Bool
> > isRegistered_(
66 ::osl::ResettableMutexGuard
& guard
,
67 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
68 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
69 virtual void processPackage_(
70 ::osl::ResettableMutexGuard
& guard
,
73 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
74 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
79 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
80 OUString
const & url
, OUString
const & name
,
81 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
82 bool bRemoved
, OUString
const & identifier
);
84 bool extensionContainsCompiledHelp();
87 virtual css::beans::Optional
< OUString
> SAL_CALL
getRegistrationDataURL() override
;
89 friend class PackageImpl
;
91 // PackageRegistryBackend
92 virtual Reference
<deployment::XPackage
> bindPackage_(
93 OUString
const & url
, OUString
const & mediaType
,
94 bool bRemoved
, OUString
const & identifier
,
95 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
97 void implProcessHelp( PackageImpl
* package
, bool doRegisterPackage
,
98 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
);
99 void implCollectXhpFiles( const OUString
& aDir
,
100 std::vector
< OUString
>& o_rXhpFileVector
);
102 ::boost::optional
<HelpBackendDb::Data
> readDataFromDb(OUString
const & url
);
103 bool hasActiveEntry(OUString
const & url
);
104 bool activateEntry(OUString
const & url
);
106 Reference
< ucb::XSimpleFileAccess3
> const & getFileAccess();
107 Reference
< ucb::XSimpleFileAccess3
> m_xSFA
;
109 const Reference
<deployment::XPackageTypeInfo
> m_xHelpTypeInfo
;
110 Sequence
< Reference
<deployment::XPackageTypeInfo
> > m_typeInfos
;
111 std::unique_ptr
<HelpBackendDb
> m_backendDb
;
114 BackendImpl( Sequence
<Any
> const & args
,
115 Reference
<XComponentContext
> const & xComponentContext
);
118 virtual Sequence
< Reference
<deployment::XPackageTypeInfo
> > SAL_CALL
119 getSupportedPackageTypes() override
;
120 virtual void SAL_CALL
packageRemoved(OUString
const & url
, OUString
const & mediaType
) override
;
125 BackendImpl::BackendImpl(
126 Sequence
<Any
> const & args
,
127 Reference
<XComponentContext
> const & xComponentContext
)
128 : PackageRegistryBackend( args
, xComponentContext
),
129 m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help",
131 DpResId(RID_STR_HELP
)
135 m_typeInfos
[ 0 ] = m_xHelpTypeInfo
;
136 if (!transientMode())
138 OUString dbFile
= makeURL(getCachePath(), "backenddb.xml");
140 new HelpBackendDb(getComponentContext(), dbFile
));
142 //clean up data folders which are no longer used.
143 //This must not be done in the same process where the help files
144 //are still registers. Only after revoking and restarting OOo the folders
145 //can be removed. This works now, because the extension manager is a singleton
146 //and the backends are only create once per process.
147 std::vector
<OUString
> folders
= m_backendDb
->getAllDataUrls();
148 deleteUnusedFolders(folders
);
154 Sequence
< Reference
<deployment::XPackageTypeInfo
> >
155 BackendImpl::getSupportedPackageTypes()
160 void BackendImpl::packageRemoved(OUString
const & url
, OUString
const & /*mediaType*/)
163 m_backendDb
->removeEntry(url
);
166 // PackageRegistryBackend
168 Reference
<deployment::XPackage
> BackendImpl::bindPackage_(
169 OUString
const & url
, OUString
const & mediaType_
,
170 bool bRemoved
, OUString
const & identifier
,
171 Reference
<XCommandEnvironment
> const & xCmdEnv
)
173 // we don't support auto detection:
174 if (mediaType_
.isEmpty())
175 throw lang::IllegalArgumentException(
176 StrCannotDetectMediaType() + url
,
177 static_cast<OWeakObject
*>(this), static_cast<sal_Int16
>(-1) );
179 OUString type
, subType
;
180 INetContentTypeParameterList params
;
181 if (INetContentTypes::parse( mediaType_
, type
, subType
, ¶ms
))
183 if (type
.equalsIgnoreAsciiCase("application"))
188 ::ucbhelper::Content
ucbContent(
189 url
, xCmdEnv
, getComponentContext() );
190 name
= StrTitle::getTitle( ucbContent
);
193 if (subType
.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
195 return new PackageImpl(
196 this, url
, name
, m_xHelpTypeInfo
, bRemoved
,
201 throw lang::IllegalArgumentException(
202 StrUnsupportedMediaType() + mediaType_
,
203 static_cast<OWeakObject
*>(this),
204 static_cast<sal_Int16
>(-1) );
207 ::boost::optional
<HelpBackendDb::Data
> BackendImpl::readDataFromDb(
208 OUString
const & url
)
210 ::boost::optional
<HelpBackendDb::Data
> data
;
212 data
= m_backendDb
->getEntry(url
);
216 bool BackendImpl::hasActiveEntry(OUString
const & url
)
219 return m_backendDb
->hasActiveEntry(url
);
223 bool BackendImpl::activateEntry(OUString
const & url
)
226 return m_backendDb
->activateEntry(url
);
231 BackendImpl::PackageImpl::PackageImpl(
232 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
233 OUString
const & url
, OUString
const & name
,
234 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
235 bool bRemoved
, OUString
const & identifier
)
236 : Package( myBackend
, url
, name
, name
, xPackageType
, bRemoved
,
242 BackendImpl
* BackendImpl::PackageImpl::getMyBackend() const
244 BackendImpl
* pBackend
= static_cast<BackendImpl
*>(m_myBackend
.get());
245 if (nullptr == pBackend
)
247 //May throw a DisposedException
249 //We should never get here...
250 throw RuntimeException("Failed to get the BackendImpl",
251 static_cast<OWeakObject
*>(const_cast<PackageImpl
*>(this)));
256 bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
258 bool bCompiled
= true;
259 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl(getURL());
261 ::osl::Directory
helpFolder(aExpandedHelpURL
);
262 if ( helpFolder
.open() == ::osl::File::E_None
)
264 //iterate over the contents of the help folder
265 //We assume that all folders within the help folder contain language specific
266 //help files. If just one of them does not contain compiled help then this
267 //function returns false.
268 ::osl::DirectoryItem item
;
269 ::osl::File::RC errorNext
= ::osl::File::E_None
;
270 while ((errorNext
= helpFolder
.getNextItem(item
)) == ::osl::File::E_None
)
272 //No find the language folders
273 ::osl::FileStatus
stat(osl_FileStatus_Mask_Type
| osl_FileStatus_Mask_FileName
|osl_FileStatus_Mask_FileURL
);
274 if (item
.getFileStatus(stat
) == ::osl::File::E_None
)
276 if (stat
.getFileType() != ::osl::FileStatus::Directory
)
279 //look if there is the folder help.idxl in the language folder
280 OUString
compUrl(stat
.getFileURL() + "/help.idxl");
281 ::osl::Directory
compiledFolder(compUrl
);
282 if (compiledFolder
.open() != ::osl::File::E_None
)
296 if (errorNext
!= ::osl::File::E_NOENT
297 && errorNext
!= ::osl::File::E_None
)
308 beans::Optional
< beans::Ambiguous
<sal_Bool
> >
309 BackendImpl::PackageImpl::isRegistered_(
310 ::osl::ResettableMutexGuard
&,
311 ::rtl::Reference
<AbortChannel
> const &,
312 Reference
<XCommandEnvironment
> const & )
314 BackendImpl
* that
= getMyBackend();
317 if (that
->hasActiveEntry(getURL()))
320 return beans::Optional
< beans::Ambiguous
<sal_Bool
> >( true, beans::Ambiguous
<sal_Bool
>( bReg
, false ) );
324 void BackendImpl::PackageImpl::processPackage_(
325 ::osl::ResettableMutexGuard
&,
326 bool doRegisterPackage
,
328 ::rtl::Reference
<AbortChannel
> const &,
329 Reference
<XCommandEnvironment
> const & xCmdEnv
)
331 BackendImpl
* that
= getMyBackend();
332 that
->implProcessHelp( this, doRegisterPackage
, xCmdEnv
);
335 beans::Optional
< OUString
> BackendImpl::PackageImpl::getRegistrationDataURL()
338 throw deployment::ExtensionRemovedException();
340 ::boost::optional
<HelpBackendDb::Data
> data
=
341 getMyBackend()->readDataFromDb(getURL());
343 if (data
&& getMyBackend()->hasActiveEntry(getURL()))
344 return beans::Optional
<OUString
>(true, data
->dataUrl
);
346 return beans::Optional
<OUString
>(true, OUString());
349 void BackendImpl::implProcessHelp(
350 PackageImpl
* package
, bool doRegisterPackage
,
351 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
)
353 Reference
< deployment::XPackage
> xPackage(package
);
354 OSL_ASSERT(xPackage
.is());
355 if (doRegisterPackage
)
357 //revive already processed help if possible
358 if ( !activateEntry(xPackage
->getURL()))
360 HelpBackendDb::Data data
;
361 data
.dataUrl
= xPackage
->getURL();
362 if (!package
->extensionContainsCompiledHelp())
364 #if HAVE_FEATURE_DESKTOP
365 const OUString sHelpFolder
= createFolder(xCmdEnv
);
366 data
.dataUrl
= sHelpFolder
;
368 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
369 OUString aHelpURL
= xPackage
->getURL();
370 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl( aHelpURL
);
371 if( !xSFA
->isFolder( aExpandedHelpURL
) )
373 OUString aErrStr
= DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR
) +
375 OWeakObject
* oWeakThis
= static_cast<OWeakObject
*>(this);
376 throw deployment::DeploymentException( OUString(), oWeakThis
,
377 makeAny( uno::Exception( aErrStr
, oWeakThis
) ) );
381 Sequence
< OUString
> aLanguageFolderSeq
= xSFA
->getFolderContents( aExpandedHelpURL
, true );
382 sal_Int32 nLangCount
= aLanguageFolderSeq
.getLength();
383 const OUString
* pSeq
= aLanguageFolderSeq
.getConstArray();
384 for( sal_Int32 iLang
= 0 ; iLang
< nLangCount
; ++iLang
)
386 OUString aLangURL
= pSeq
[iLang
];
387 if( xSFA
->isFolder( aLangURL
) )
389 std::vector
< OUString
> aXhpFileVector
;
391 // calculate jar file URL
392 sal_Int32 indexStartSegment
= aLangURL
.lastIndexOf('/');
394 OUString
langFolderURLSegment(
396 indexStartSegment
+ 1, aLangURL
.getLength() - indexStartSegment
- 1));
398 //create the folder in the "temporary folder"
399 ::ucbhelper::Content langFolderContent
;
400 const OUString langFolderDest
= makeURL(sHelpFolder
, langFolderURLSegment
);
401 const OUString langFolderDestExpanded
= ::dp_misc::expandUnoRcUrl(langFolderDest
);
402 ::dp_misc::create_folder(
404 langFolderDest
, xCmdEnv
);
406 const OUString
aHelpStr("help");
407 const OUString
aSlash("/");
410 makeURL(sHelpFolder
, langFolderURLSegment
+ aSlash
+ aHelpStr
+ ".jar"));
411 aJarFile
= ::dp_misc::expandUnoRcUrl(aJarFile
);
413 OUString aEncodedJarFilePath
= rtl::Uri::encode(
414 aJarFile
, rtl_UriCharClassPchar
,
415 rtl_UriEncodeIgnoreEscapes
,
416 RTL_TEXTENCODING_UTF8
);
417 OUString aDestBasePath
= "vnd.sun.star.zip://" +
418 aEncodedJarFilePath
+ "/" ;
420 sal_Int32 nLenLangFolderURL
= aLangURL
.getLength() + 1;
422 Sequence
< OUString
> aSubLangSeq
= xSFA
->getFolderContents( aLangURL
, true );
423 sal_Int32 nSubLangCount
= aSubLangSeq
.getLength();
424 const OUString
* pSubLangSeq
= aSubLangSeq
.getConstArray();
425 for( sal_Int32 iSubLang
= 0 ; iSubLang
< nSubLangCount
; ++iSubLang
)
427 OUString aSubFolderURL
= pSubLangSeq
[iSubLang
];
428 if( !xSFA
->isFolder( aSubFolderURL
) )
431 implCollectXhpFiles( aSubFolderURL
, aXhpFileVector
);
433 // Copy to package (later: move?)
434 OUString aDestPath
= aDestBasePath
;
435 OUString aPureFolderName
= aSubFolderURL
.copy( nLenLangFolderURL
);
436 aDestPath
+= aPureFolderName
;
437 xSFA
->copy( aSubFolderURL
, aDestPath
);
441 sal_Int32 nXhpFileCount
= aXhpFileVector
.size();
442 std::unique_ptr
<OUString
[]> pXhpFiles(new OUString
[nXhpFileCount
]);
443 for( sal_Int32 iXhp
= 0 ; iXhp
< nXhpFileCount
; ++iXhp
)
445 OUString aXhpFile
= aXhpFileVector
[iXhp
];
446 OUString aXhpRelFile
= aXhpFile
.copy( nLenLangFolderURL
);
447 pXhpFiles
[iXhp
] = aXhpRelFile
;
450 OUString
aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
451 OUString aOfficeHelpPathFileURL
;
452 ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath
, aOfficeHelpPathFileURL
);
454 HelpProcessingErrorInfo aErrorInfo
;
455 bool bSuccess
= compileExtensionHelp(
456 aOfficeHelpPathFileURL
, aHelpStr
, aLangURL
,
457 nXhpFileCount
, pXhpFiles
.get(),
458 langFolderDestExpanded
, aErrorInfo
);
465 sal_Int32 nLastSlash
= aLangURL
.lastIndexOf( '/' );
466 if( nLastSlash
!= -1 )
467 aLang
= aLangURL
.copy( nLastSlash
+ 1 );
471 HelpIndexer
aIndexer(aLang
, "help", langFolderDestExpanded
, langFolderDestExpanded
);
472 aIndexer
.indexDocuments();
477 const char* pErrStrId
= nullptr;
478 switch( aErrorInfo
.m_eErrorClass
)
480 case HelpProcessingErrorClass::General
: pErrStrId
= RID_STR_HELPPROCESSING_GENERAL_ERROR
; break;
481 case HelpProcessingErrorClass::XmlParsing
: pErrStrId
= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
; break;
488 aErrStr
= DpResId(pErrStrId
);
491 OUString
aErrMsg( aErrorInfo
.m_aErrorMsg
);
492 sal_Unicode
const nCR
= 13, nLF
= 10;
493 sal_Int32 nSearchCR
= aErrMsg
.indexOf( nCR
);
494 sal_Int32 nSearchLF
= aErrMsg
.indexOf( nLF
);
496 if( nSearchCR
!= -1 || nSearchLF
!= -1 )
498 if( nSearchCR
== -1 )
500 else if( nSearchLF
== -1 )
503 nCopy
= ( nSearchCR
< nSearchLF
) ? nSearchCR
: nSearchLF
;
505 aErrMsg
= aErrMsg
.copy( 0, nCopy
);
508 if (!strcmp(pErrStrId
, RID_STR_HELPPROCESSING_XMLPARSING_ERROR
) && !aErrorInfo
.m_aXMLParsingFile
.isEmpty() )
512 OUString aDecodedFile
= rtl::Uri::decode( aErrorInfo
.m_aXMLParsingFile
,
513 rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
514 aErrStr
+= aDecodedFile
;
515 if( aErrorInfo
.m_nXMLParsingLine
!= -1 )
517 aErrStr
+= ", line " +
518 OUString::number( aErrorInfo
.m_nXMLParsingLine
);
523 OWeakObject
* oWeakThis
= static_cast<OWeakObject
*>(this);
524 throw deployment::DeploymentException( OUString(), oWeakThis
,
525 makeAny( uno::Exception( aErrStr
, oWeakThis
) ) );
533 // Writing the data entry replaces writing the flag file. If we got to this
534 // point the registration was successful.
536 m_backendDb
->addEntry(xPackage
->getURL(), data
);
538 } //if (doRegisterPackage)
542 m_backendDb
->revokeEntry(xPackage
->getURL());
546 void BackendImpl::implCollectXhpFiles( const OUString
& aDir
,
547 std::vector
< OUString
>& o_rXhpFileVector
)
549 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
551 // Scan xhp files recursively
552 Sequence
< OUString
> aSeq
= xSFA
->getFolderContents( aDir
, true );
553 sal_Int32 nCount
= aSeq
.getLength();
554 const OUString
* pSeq
= aSeq
.getConstArray();
555 for( sal_Int32 i
= 0 ; i
< nCount
; ++i
)
557 OUString aURL
= pSeq
[i
];
558 if( xSFA
->isFolder( aURL
) )
560 implCollectXhpFiles( aURL
, o_rXhpFileVector
);
564 sal_Int32 nLastDot
= aURL
.lastIndexOf( '.' );
567 OUString aExt
= aURL
.copy( nLastDot
+ 1 );
568 if( aExt
.equalsIgnoreAsciiCase( "xhp" ) )
569 o_rXhpFileVector
.push_back( aURL
);
575 Reference
< ucb::XSimpleFileAccess3
> const & BackendImpl::getFileAccess()
579 Reference
<XComponentContext
> const & xContext
= getComponentContext();
582 m_xSFA
= ucb::SimpleFileAccess::create(xContext
);
586 throw RuntimeException(
587 "dp_registry::backend::help::BackendImpl::getFileAccess(), "
588 "could not instantiate SimpleFileAccess." );
596 namespace sdecl
= comphelper::service_decl
;
597 sdecl::class_
<BackendImpl
, sdecl::with_args
<true> > serviceBI
;
598 sdecl::ServiceDecl
const serviceDecl(
600 "com.sun.star.comp.deployment.help.PackageRegistryBackend",
601 BACKEND_SERVICE_NAME
);
604 } // namespace backend
605 } // namespace dp_registry
607 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */