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 .
20 #include <config_features.h>
22 #include "dp_help.hrc"
23 #include "dp_backend.h"
24 #include "dp_helpbackenddb.hxx"
26 #include "rtl/uri.hxx"
27 #include "osl/file.hxx"
28 #include "rtl/bootstrap.hxx"
29 #include "ucbhelper/content.hxx"
30 #include "comphelper/servicedecl.hxx"
31 #include "svl/inettype.hxx"
32 #include "uno/current_context.hxx"
33 #include "unotools/pathoptions.hxx"
35 #if HAVE_FEATURE_DESKTOP
36 #include <helpcompiler/compilehelp.hxx>
37 #include <helpcompiler/HelpIndexer.hxx>
39 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
40 #include <com/sun/star/util/XMacroExpander.hpp>
41 #include <com/sun/star/uri/XUriReferenceFactory.hpp>
42 #include <com/sun/star/uri/XVndSunStarExpandUrl.hpp>
43 #include <com/sun/star/script/XInvocation.hpp>
44 #include "boost/optional.hpp"
46 using namespace ::dp_misc
;
47 using namespace ::com::sun::star
;
48 using namespace ::com::sun::star::uno
;
49 using namespace ::com::sun::star::ucb
;
51 namespace dp_registry
{
56 //==============================================================================
57 class BackendImpl
: public ::dp_registry::backend::PackageRegistryBackend
59 class PackageImpl
: public ::dp_registry::backend::Package
61 BackendImpl
* getMyBackend() const;
64 virtual beans::Optional
< beans::Ambiguous
<sal_Bool
> > isRegistered_(
65 ::osl::ResettableMutexGuard
& guard
,
66 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
67 Reference
<XCommandEnvironment
> const & xCmdEnv
);
68 virtual void processPackage_(
69 ::osl::ResettableMutexGuard
& guard
,
72 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
73 Reference
<XCommandEnvironment
> const & xCmdEnv
);
78 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
79 OUString
const & url
, OUString
const & name
,
80 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
81 bool bRemoved
, OUString
const & identifier
);
83 bool extensionContainsCompiledHelp();
86 virtual css::beans::Optional
< OUString
> SAL_CALL
getRegistrationDataURL()
87 throw (deployment::ExtensionRemovedException
, css::uno::RuntimeException
);
89 friend class PackageImpl
;
91 // PackageRegistryBackend
92 virtual Reference
<deployment::XPackage
> bindPackage_(
93 OUString
const & url
, OUString
const & mediaType
,
94 sal_Bool bRemoved
, OUString
const & identifier
,
95 Reference
<XCommandEnvironment
> const & xCmdEnv
);
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 void addDataToDb(OUString
const & url
, HelpBackendDb::Data
const & data
);
103 ::boost::optional
<HelpBackendDb::Data
> readDataFromDb(OUString
const & url
);
104 bool hasActiveEntry(OUString
const & url
);
105 void revokeEntryFromDb(OUString
const & url
);
106 bool activateEntry(OUString
const & url
);
108 Reference
< ucb::XSimpleFileAccess3
> getFileAccess( void );
109 Reference
< ucb::XSimpleFileAccess3
> m_xSFA
;
111 const Reference
<deployment::XPackageTypeInfo
> m_xHelpTypeInfo
;
112 Sequence
< Reference
<deployment::XPackageTypeInfo
> > m_typeInfos
;
113 std::auto_ptr
<HelpBackendDb
> m_backendDb
;
116 BackendImpl( Sequence
<Any
> const & args
,
117 Reference
<XComponentContext
> const & xComponentContext
);
120 virtual Sequence
< Reference
<deployment::XPackageTypeInfo
> > SAL_CALL
121 getSupportedPackageTypes() throw (RuntimeException
);
122 virtual void SAL_CALL
packageRemoved(OUString
const & url
, OUString
const & mediaType
)
123 throw (deployment::DeploymentException
,
124 uno::RuntimeException
);
128 //______________________________________________________________________________
129 BackendImpl::BackendImpl(
130 Sequence
<Any
> const & args
,
131 Reference
<XComponentContext
> const & xComponentContext
)
132 : PackageRegistryBackend( args
, xComponentContext
),
133 m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help",
135 getResourceString(RID_STR_HELP
),
139 m_typeInfos
[ 0 ] = m_xHelpTypeInfo
;
140 if (!transientMode())
142 OUString dbFile
= makeURL(getCachePath(), "backenddb.xml");
144 new HelpBackendDb(getComponentContext(), dbFile
));
146 //clean up data folders which are no longer used.
147 //This must not be done in the same process where the help files
148 //are still registers. Only after revoking and restarting OOo the folders
149 //can be removed. This works now, because the extension manager is a singleton
150 //and the backends are only create once per process.
151 ::std::list
<OUString
> folders
= m_backendDb
->getAllDataUrls();
152 deleteUnusedFolders(OUString(), folders
);
157 //______________________________________________________________________________
158 Sequence
< Reference
<deployment::XPackageTypeInfo
> >
159 BackendImpl::getSupportedPackageTypes() throw (RuntimeException
)
164 void BackendImpl::packageRemoved(OUString
const & url
, OUString
const & /*mediaType*/)
165 throw (deployment::DeploymentException
,
166 uno::RuntimeException
)
168 if (m_backendDb
.get())
169 m_backendDb
->removeEntry(url
);
172 // PackageRegistryBackend
173 //______________________________________________________________________________
174 Reference
<deployment::XPackage
> BackendImpl::bindPackage_(
175 OUString
const & url
, OUString
const & mediaType_
,
176 sal_Bool bRemoved
, OUString
const & identifier
,
177 Reference
<XCommandEnvironment
> const & xCmdEnv
)
179 // we don't support auto detection:
180 if (mediaType_
.isEmpty())
181 throw lang::IllegalArgumentException(
182 StrCannotDetectMediaType::get() + url
,
183 static_cast<OWeakObject
*>(this), static_cast<sal_Int16
>(-1) );
185 OUString type
, subType
;
186 INetContentTypeParameterList params
;
187 if (INetContentTypes::parse( mediaType_
, type
, subType
, ¶ms
))
189 if (type
.equalsIgnoreAsciiCase("application"))
194 ::ucbhelper::Content
ucbContent(
195 url
, xCmdEnv
, getComponentContext() );
196 name
= StrTitle::getTitle( ucbContent
);
199 if (subType
.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
201 return new PackageImpl(
202 this, url
, name
, m_xHelpTypeInfo
, bRemoved
,
207 throw lang::IllegalArgumentException(
208 StrUnsupportedMediaType::get() + mediaType_
,
209 static_cast<OWeakObject
*>(this),
210 static_cast<sal_Int16
>(-1) );
213 void BackendImpl::addDataToDb(
214 OUString
const & url
, HelpBackendDb::Data
const & data
)
216 if (m_backendDb
.get())
217 m_backendDb
->addEntry(url
, data
);
220 ::boost::optional
<HelpBackendDb::Data
> BackendImpl::readDataFromDb(
221 OUString
const & url
)
223 ::boost::optional
<HelpBackendDb::Data
> data
;
224 if (m_backendDb
.get())
225 data
= m_backendDb
->getEntry(url
);
229 bool BackendImpl::hasActiveEntry(OUString
const & url
)
231 if (m_backendDb
.get())
232 return m_backendDb
->hasActiveEntry(url
);
236 void BackendImpl::revokeEntryFromDb(OUString
const & url
)
238 if (m_backendDb
.get())
239 m_backendDb
->revokeEntry(url
);
242 bool BackendImpl::activateEntry(OUString
const & url
)
244 if (m_backendDb
.get())
245 return m_backendDb
->activateEntry(url
);
250 BackendImpl::PackageImpl::PackageImpl(
251 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
252 OUString
const & url
, OUString
const & name
,
253 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
254 bool bRemoved
, OUString
const & identifier
)
255 : Package( myBackend
, url
, name
, name
, xPackageType
, bRemoved
,
261 BackendImpl
* BackendImpl::PackageImpl::getMyBackend() const
263 BackendImpl
* pBackend
= static_cast<BackendImpl
*>(m_myBackend
.get());
264 if (NULL
== pBackend
)
266 //May throw a DisposedException
268 //We should never get here...
269 throw RuntimeException("Failed to get the BackendImpl",
270 static_cast<OWeakObject
*>(const_cast<PackageImpl
*>(this)));
275 bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
277 bool bCompiled
= true;
278 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl(getURL());
280 ::osl::Directory
helpFolder(aExpandedHelpURL
);
281 if ( helpFolder
.open() == ::osl::File::E_None
)
283 //iterate over the contents of the help folder
284 //We assume that all folders withing the help folder contain language specific
285 //help files. If just one of them does not contain compiled help then this
286 //function returns false.
287 ::osl::DirectoryItem item
;
288 ::osl::File::RC errorNext
= ::osl::File::E_None
;
289 while ((errorNext
= helpFolder
.getNextItem(item
)) == ::osl::File::E_None
)
291 //No find the language folders
292 ::osl::FileStatus
stat(osl_FileStatus_Mask_Type
| osl_FileStatus_Mask_FileName
|osl_FileStatus_Mask_FileURL
);
293 if (item
.getFileStatus(stat
) == ::osl::File::E_None
)
295 if (stat
.getFileType() != ::osl::FileStatus::Directory
)
298 //look if there is the folder help.idxl in the language folder
299 OUString
compUrl(stat
.getFileURL() + "/help.idxl");
300 ::osl::Directory
compiledFolder(compUrl
);
301 if (compiledFolder
.open() != ::osl::File::E_None
)
315 if (errorNext
!= ::osl::File::E_NOENT
316 && errorNext
!= ::osl::File::E_None
)
326 //______________________________________________________________________________
327 beans::Optional
< beans::Ambiguous
<sal_Bool
> >
328 BackendImpl::PackageImpl::isRegistered_(
329 ::osl::ResettableMutexGuard
&,
330 ::rtl::Reference
<AbortChannel
> const &,
331 Reference
<XCommandEnvironment
> const & )
333 BackendImpl
* that
= getMyBackend();
336 if (that
->hasActiveEntry(getURL()))
339 return beans::Optional
< beans::Ambiguous
<sal_Bool
> >( true, beans::Ambiguous
<sal_Bool
>( bReg
, false ) );
342 //______________________________________________________________________________
343 void BackendImpl::PackageImpl::processPackage_(
344 ::osl::ResettableMutexGuard
&,
345 bool doRegisterPackage
,
347 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
348 Reference
<XCommandEnvironment
> const & xCmdEnv
)
350 (void)doRegisterPackage
;
354 BackendImpl
* that
= getMyBackend();
355 that
->implProcessHelp( this, doRegisterPackage
, xCmdEnv
);
358 beans::Optional
< OUString
> BackendImpl::PackageImpl::getRegistrationDataURL()
359 throw (deployment::ExtensionRemovedException
,
360 css::uno::RuntimeException
)
363 throw deployment::ExtensionRemovedException();
365 ::boost::optional
<HelpBackendDb::Data
> data
=
366 getMyBackend()->readDataFromDb(getURL());
368 if (data
&& getMyBackend()->hasActiveEntry(getURL()))
369 return beans::Optional
<OUString
>(true, data
->dataUrl
);
371 return beans::Optional
<OUString
>(true, OUString());
374 void BackendImpl::implProcessHelp(
375 PackageImpl
* package
, bool doRegisterPackage
,
376 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
)
378 Reference
< deployment::XPackage
> xPackage(package
);
379 OSL_ASSERT(xPackage
.is());
380 if (doRegisterPackage
)
382 //revive already processed help if possible
383 if ( !activateEntry(xPackage
->getURL()))
385 HelpBackendDb::Data data
;
386 data
.dataUrl
= xPackage
->getURL();
387 if (!package
->extensionContainsCompiledHelp())
389 #if HAVE_FEATURE_DESKTOP
390 const OUString sHelpFolder
= createFolder(OUString(), xCmdEnv
);
391 data
.dataUrl
= sHelpFolder
;
393 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
394 OUString aHelpURL
= xPackage
->getURL();
395 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl( aHelpURL
);
396 if( !xSFA
->isFolder( aExpandedHelpURL
) )
398 OUString aErrStr
= getResourceString( RID_STR_HELPPROCESSING_GENERAL_ERROR
);
399 aErrStr
+= OUString("No help folder" );
400 OWeakObject
* oWeakThis
= static_cast<OWeakObject
*>(this);
401 throw deployment::DeploymentException( OUString(), oWeakThis
,
402 makeAny( uno::Exception( aErrStr
, oWeakThis
) ) );
406 Sequence
< OUString
> aLanguageFolderSeq
= xSFA
->getFolderContents( aExpandedHelpURL
, true );
407 sal_Int32 nLangCount
= aLanguageFolderSeq
.getLength();
408 const OUString
* pSeq
= aLanguageFolderSeq
.getConstArray();
409 for( sal_Int32 iLang
= 0 ; iLang
< nLangCount
; ++iLang
)
411 OUString aLangURL
= pSeq
[iLang
];
412 if( xSFA
->isFolder( aLangURL
) )
414 std::vector
< OUString
> aXhpFileVector
;
416 // calculate jar file URL
417 sal_Int32 indexStartSegment
= aLangURL
.lastIndexOf('/');
419 OUString
langFolderURLSegment(
421 indexStartSegment
+ 1, aLangURL
.getLength() - indexStartSegment
- 1));
423 //create the folder in the "temporary folder"
424 ::ucbhelper::Content langFolderContent
;
425 const OUString langFolderDest
= makeURL(sHelpFolder
, langFolderURLSegment
);
426 const OUString langFolderDestExpanded
= ::dp_misc::expandUnoRcUrl(langFolderDest
);
427 ::dp_misc::create_folder(
429 langFolderDest
, xCmdEnv
);
431 const OUString
aHelpStr("help");
432 const OUString
aSlash("/");
435 makeURL(sHelpFolder
, langFolderURLSegment
+ aSlash
+ aHelpStr
+ ".jar"));
436 aJarFile
= ::dp_misc::expandUnoRcUrl(aJarFile
);
438 OUString aEncodedJarFilePath
= rtl::Uri::encode(
439 aJarFile
, rtl_UriCharClassPchar
,
440 rtl_UriEncodeIgnoreEscapes
,
441 RTL_TEXTENCODING_UTF8
);
442 OUString aDestBasePath
= OUString("vnd.sun.star.zip://" );
443 aDestBasePath
+= aEncodedJarFilePath
;
444 aDestBasePath
+= OUString("/" );
446 sal_Int32 nLenLangFolderURL
= aLangURL
.getLength() + 1;
448 Sequence
< OUString
> aSubLangSeq
= xSFA
->getFolderContents( aLangURL
, true );
449 sal_Int32 nSubLangCount
= aSubLangSeq
.getLength();
450 const OUString
* pSubLangSeq
= aSubLangSeq
.getConstArray();
451 for( sal_Int32 iSubLang
= 0 ; iSubLang
< nSubLangCount
; ++iSubLang
)
453 OUString aSubFolderURL
= pSubLangSeq
[iSubLang
];
454 if( !xSFA
->isFolder( aSubFolderURL
) )
457 implCollectXhpFiles( aSubFolderURL
, aXhpFileVector
);
459 // Copy to package (later: move?)
460 OUString aDestPath
= aDestBasePath
;
461 OUString aPureFolderName
= aSubFolderURL
.copy( nLenLangFolderURL
);
462 aDestPath
+= aPureFolderName
;
463 xSFA
->copy( aSubFolderURL
, aDestPath
);
467 sal_Int32 nXhpFileCount
= aXhpFileVector
.size();
468 OUString
* pXhpFiles
= new OUString
[nXhpFileCount
];
469 for( sal_Int32 iXhp
= 0 ; iXhp
< nXhpFileCount
; ++iXhp
)
471 OUString aXhpFile
= aXhpFileVector
[iXhp
];
472 OUString aXhpRelFile
= aXhpFile
.copy( nLenLangFolderURL
);
473 pXhpFiles
[iXhp
] = aXhpRelFile
;
476 OUString
aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
477 OUString aOfficeHelpPathFileURL
;
478 ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath
, aOfficeHelpPathFileURL
);
480 HelpProcessingErrorInfo aErrorInfo
;
481 bool bSuccess
= compileExtensionHelp(
482 aOfficeHelpPathFileURL
, aHelpStr
, aLangURL
,
483 nXhpFileCount
, pXhpFiles
,
484 langFolderDestExpanded
, aErrorInfo
);
489 sal_Int32 nLastSlash
= aLangURL
.lastIndexOf( '/' );
490 if( nLastSlash
!= -1 )
491 aLang
= aLangURL
.copy( nLastSlash
+ 1 );
493 aLang
= OUString("en" );
495 OUString
aMod("help");
497 HelpIndexer
aIndexer(aLang
, aMod
, langFolderDestExpanded
, langFolderDestExpanded
);
498 aIndexer
.indexDocuments();
503 sal_uInt16 nErrStrId
= 0;
504 switch( aErrorInfo
.m_eErrorClass
)
506 case HELPPROCESSING_GENERAL_ERROR
:
507 case HELPPROCESSING_INTERNAL_ERROR
: nErrStrId
= RID_STR_HELPPROCESSING_GENERAL_ERROR
; break;
508 case HELPPROCESSING_XMLPARSING_ERROR
: nErrStrId
= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
; break;
515 aErrStr
= getResourceString( nErrStrId
);
518 OUString
aErrMsg( aErrorInfo
.m_aErrorMsg
);
519 sal_Unicode nCR
= 13, nLF
= 10;
520 sal_Int32 nSearchCR
= aErrMsg
.indexOf( nCR
);
521 sal_Int32 nSearchLF
= aErrMsg
.indexOf( nLF
);
523 if( nSearchCR
!= -1 || nSearchLF
!= -1 )
525 if( nSearchCR
== -1 )
527 else if( nSearchLF
== -1 )
530 nCopy
= ( nSearchCR
< nSearchLF
) ? nSearchCR
: nSearchLF
;
532 aErrMsg
= aErrMsg
.copy( 0, nCopy
);
535 if( nErrStrId
== RID_STR_HELPPROCESSING_XMLPARSING_ERROR
&& !aErrorInfo
.m_aXMLParsingFile
.isEmpty() )
537 aErrStr
+= OUString(" in " );
539 OUString aDecodedFile
= rtl::Uri::decode( aErrorInfo
.m_aXMLParsingFile
,
540 rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
541 aErrStr
+= aDecodedFile
;
542 if( aErrorInfo
.m_nXMLParsingLine
!= -1 )
544 aErrStr
+= OUString(", line " );
545 aErrStr
+= OUString::valueOf( aErrorInfo
.m_nXMLParsingLine
);
550 OWeakObject
* oWeakThis
= static_cast<OWeakObject
*>(this);
551 throw deployment::DeploymentException( OUString(), oWeakThis
,
552 makeAny( uno::Exception( aErrStr
, oWeakThis
) ) );
560 //Writing the data entry replaces writing the flag file. If we got to this
561 //point the registration was successful.
562 addDataToDb(xPackage
->getURL(), data
);
564 } //if (doRegisterPackage)
567 revokeEntryFromDb(xPackage
->getURL());
571 void BackendImpl::implCollectXhpFiles( const OUString
& aDir
,
572 std::vector
< OUString
>& o_rXhpFileVector
)
574 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
576 // Scan xhp files recursively
577 Sequence
< OUString
> aSeq
= xSFA
->getFolderContents( aDir
, true );
578 sal_Int32 nCount
= aSeq
.getLength();
579 const OUString
* pSeq
= aSeq
.getConstArray();
580 for( sal_Int32 i
= 0 ; i
< nCount
; ++i
)
582 OUString aURL
= pSeq
[i
];
583 if( xSFA
->isFolder( aURL
) )
585 implCollectXhpFiles( aURL
, o_rXhpFileVector
);
589 sal_Int32 nLastDot
= aURL
.lastIndexOf( '.' );
592 OUString aExt
= aURL
.copy( nLastDot
+ 1 );
593 if( aExt
.equalsIgnoreAsciiCase( OUString("xhp" ) ) )
594 o_rXhpFileVector
.push_back( aURL
);
600 Reference
< ucb::XSimpleFileAccess3
> BackendImpl::getFileAccess( void )
604 Reference
<XComponentContext
> const & xContext
= getComponentContext();
607 m_xSFA
= ucb::SimpleFileAccess::create(xContext
);
611 throw RuntimeException(
613 "dp_registry::backend::help::BackendImpl::getFileAccess(), "
614 "could not instatiate SimpleFileAccess." ),
615 Reference
< XInterface
>() );
623 namespace sdecl
= comphelper::service_decl
;
624 sdecl::class_
<BackendImpl
, sdecl::with_args
<true> > serviceBI
;
625 extern sdecl::ServiceDecl
const serviceDecl(
627 "com.sun.star.comp.deployment.help.PackageRegistryBackend",
628 BACKEND_SERVICE_NAME
);
631 } // namespace backend
632 } // namespace dp_registry
634 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */