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 <sal/config.h>
22 #include <osl/file.hxx>
23 #include <svtools/templatefoldercache.hxx>
24 #include <unotools/ucbstreamhelper.hxx>
25 #include <com/sun/star/sdbc/XResultSet.hpp>
26 #include <com/sun/star/ucb/XDynamicResultSet.hpp>
27 #include <com/sun/star/sdbc/XRow.hpp>
28 #include <com/sun/star/ucb/CommandAbortedException.hpp>
29 #include <com/sun/star/ucb/XContentAccess.hpp>
30 #include <com/sun/star/uno/XComponentContext.hpp>
31 #include <com/sun/star/util/theOfficeInstallationDirectories.hpp>
32 #include <ucbhelper/content.hxx>
33 #include <osl/diagnose.h>
34 #include <rtl/ref.hxx>
35 #include <salhelper/simplereferenceobject.hxx>
36 #include <tools/time.hxx>
37 #include <tools/urlobj.hxx>
38 #include <tools/debug.hxx>
39 #include <comphelper/diagnose_ex.hxx>
40 #include <unotools/pathoptions.hxx>
42 #include <comphelper/processfactory.hxx>
54 using namespace ::utl
;
55 using namespace ::com::sun::star
;
56 using namespace ::com::sun::star::sdbc
;
57 using namespace ::com::sun::star::ucb
;
58 using namespace ::com::sun::star::uno
;
64 static SvStream
& WriteDateTime( SvStream
& _rStorage
, const util::DateTime
& _rDate
)
66 sal_uInt16 hundredthSeconds
= static_cast< sal_uInt16
>( _rDate
.NanoSeconds
/ tools::Time::nanoPerCenti
);
67 _rStorage
.WriteUInt16( hundredthSeconds
);
69 _rStorage
.WriteUInt16( _rDate
.Seconds
);
70 _rStorage
.WriteUInt16( _rDate
.Minutes
);
71 _rStorage
.WriteUInt16( _rDate
.Hours
);
72 _rStorage
.WriteUInt16( _rDate
.Day
);
73 _rStorage
.WriteUInt16( _rDate
.Month
);
74 _rStorage
.WriteInt16( _rDate
.Year
);
80 static SvStream
& operator >> ( SvStream
& _rStorage
, util::DateTime
& _rDate
)
82 sal_uInt16 hundredthSeconds
;
83 _rStorage
.ReadUInt16( hundredthSeconds
);
84 _rDate
.NanoSeconds
= static_cast< sal_uInt32
>( hundredthSeconds
) * tools::Time::nanoPerCenti
;
86 _rStorage
.ReadUInt16( _rDate
.Seconds
);
87 _rStorage
.ReadUInt16( _rDate
.Minutes
);
88 _rStorage
.ReadUInt16( _rDate
.Hours
);
89 _rStorage
.ReadUInt16( _rDate
.Day
);
90 _rStorage
.ReadUInt16( _rDate
.Month
);
91 _rStorage
.ReadInt16( _rDate
.Year
);
100 struct TemplateContent
;
104 typedef ::std::vector
< ::rtl::Reference
< TemplateContent
> > TemplateFolderContent
;
105 typedef TemplateFolderContent::const_iterator ConstFolderIterator
;
106 typedef TemplateFolderContent::iterator FolderIterator
;
110 /** a struct describing one content in one of the template dirs (or at least it's relevant aspects)
112 struct TemplateContent
: public ::salhelper::SimpleReferenceObject
117 INetURLObject m_aURL
;
118 util::DateTime m_aLastModified
; // date of last modification as reported by UCP
119 TemplateFolderContent m_aSubContents
; // sorted (by name) list of the children
122 void implResetDate( )
124 m_aLastModified
.NanoSeconds
= m_aLastModified
.Seconds
= m_aLastModified
.Minutes
= m_aLastModified
.Hours
= 0;
125 m_aLastModified
.Day
= m_aLastModified
.Month
= m_aLastModified
.Year
= 0;
129 virtual ~TemplateContent() override
;
132 explicit TemplateContent( INetURLObject _aURL
);
135 OUString
getURL( ) const { return m_aURL
.GetMainURL( INetURLObject::DecodeMechanism::ToIUri
); }
136 void setModDate( const util::DateTime
& _rDate
) { m_aLastModified
= _rDate
; }
137 const util::DateTime
& getModDate( ) const { return m_aLastModified
; }
139 TemplateFolderContent
& getSubContents() { return m_aSubContents
; }
140 const TemplateFolderContent
& getSubContents() const { return m_aSubContents
; }
142 ConstFolderIterator
end() const { return m_aSubContents
.end(); }
143 TemplateFolderContent::size_type
144 size() const { return m_aSubContents
.size(); }
146 void push_back( const ::rtl::Reference
< TemplateContent
>& _rxNewElement
)
147 { m_aSubContents
.push_back( _rxNewElement
); }
152 TemplateContent::TemplateContent( INetURLObject _aURL
)
153 :m_aURL(std::move( _aURL
))
155 DBG_ASSERT( INetProtocol::NotValid
!= m_aURL
.GetProtocol(), "TemplateContent::TemplateContent: invalid URL!" );
160 TemplateContent::~TemplateContent()
169 /// compares two TemplateContent by URL
170 struct TemplateContentURLLess
172 bool operator() ( const ::rtl::Reference
< TemplateContent
>& _rxLHS
, const ::rtl::Reference
< TemplateContent
>& _rxRHS
) const
174 return _rxLHS
->getURL() < _rxRHS
->getURL();
179 /// sorts the sib contents of a TemplateFolderContent
180 struct SubContentSort
182 void operator() ( TemplateFolderContent
& _rFolder
) const
184 // sort the directory by name
188 TemplateContentURLLess()
191 // sort the sub directories by name
199 void operator() ( const ::rtl::Reference
< TemplateContent
>& _rxContent
) const
201 if ( _rxContent
.is() && _rxContent
->size() )
203 operator()( _rxContent
->getSubContents() );
208 /** does a deep compare of two template contents
210 struct TemplateContentEqual
213 bool operator() (const ::rtl::Reference
< TemplateContent
>& _rLHS
, const ::rtl::Reference
< TemplateContent
>& _rRHS
)
215 if ( !_rLHS
.is() || !_rRHS
.is() )
217 OSL_FAIL( "TemplateContentEqual::operator(): invalid contents!" );
219 // this is not strictly true, in case only one is invalid - but this is a heavy error anyway
222 if ( _rLHS
->getURL() != _rRHS
->getURL() )
225 if ( _rLHS
->getModDate() != _rRHS
->getModDate() )
228 if ( _rLHS
->getSubContents().size() != _rRHS
->getSubContents().size() )
231 if ( !_rLHS
->getSubContents().empty() )
232 { // there are children
234 ::std::pair
< FolderIterator
, FolderIterator
> aFirstDifferent
= ::std::mismatch(
235 _rLHS
->getSubContents().begin(),
236 _rLHS
->getSubContents().end(),
237 _rRHS
->getSubContents().begin(),
240 if ( aFirstDifferent
.first
!= _rLHS
->getSubContents().end() )
241 return false;// the sub contents differ
249 /// base class for functors which act on a SvStream
253 SvStream
& m_rStorage
;
254 explicit StorageHelper( SvStream
& _rStorage
) : m_rStorage( _rStorage
) { }
258 struct StoreContentURL
: public StorageHelper
260 uno::Reference
< util::XOfficeInstallationDirectories
> m_xOfficeInstDirs
;
262 StoreContentURL( SvStream
& _rStorage
,
264 util::XOfficeInstallationDirectories
> xOfficeInstDirs
)
265 : StorageHelper( _rStorage
), m_xOfficeInstDirs(std::move( xOfficeInstDirs
)) { }
267 void operator() ( const ::rtl::Reference
< TemplateContent
>& _rxContent
) const
269 // use the base class operator with the local name of the content
270 OUString sURL
= _rxContent
->getURL();
271 // #116281# Keep office installation relocatable. Never store
272 // any direct references to office installation directory.
273 sURL
= m_xOfficeInstDirs
->makeRelocatableURL( sURL
);
274 m_rStorage
.WriteUniOrByteString( sURL
, m_rStorage
.GetStreamCharSet() );
279 /// functor which stores the complete content of a TemplateContent
280 struct StoreFolderContent
: public StorageHelper
282 uno::Reference
< util::XOfficeInstallationDirectories
> m_xOfficeInstDirs
;
285 StoreFolderContent( SvStream
& _rStorage
,
287 util::XOfficeInstallationDirectories
> xOfficeInstDirs
)
288 : StorageHelper( _rStorage
), m_xOfficeInstDirs(std::move( xOfficeInstDirs
)) { }
291 void operator() ( const TemplateContent
& _rContent
) const
293 // store the info about this content
294 WriteDateTime( m_rStorage
, _rContent
.getModDate() );
296 // store the info about the children
298 m_rStorage
.WriteInt32( _rContent
.size() );
299 // their URLs ( the local name is not enough, since URL might be not a hierarchical one, "expand:" for example )
301 _rContent
.getSubContents().begin(),
302 _rContent
.getSubContents().end(),
303 StoreContentURL( m_rStorage
, m_xOfficeInstDirs
)
307 _rContent
.getSubContents().begin(),
308 _rContent
.getSubContents().end(),
314 void operator() ( const ::rtl::Reference
< TemplateContent
>& _rxContent
) const
316 if ( _rxContent
.is() )
318 operator()( *_rxContent
);
324 /// functor which reads a complete TemplateContent instance
325 struct ReadFolderContent
: public StorageHelper
327 uno::Reference
< util::XOfficeInstallationDirectories
> m_xOfficeInstDirs
;
329 ReadFolderContent( SvStream
& _rStorage
,
331 util::XOfficeInstallationDirectories
> xOfficeInstDirs
)
332 : StorageHelper( _rStorage
), m_xOfficeInstDirs(std::move( xOfficeInstDirs
)) { }
335 void operator() ( TemplateContent
& _rContent
) const
337 // store the info about this content
338 util::DateTime aModDate
;
339 m_rStorage
>> aModDate
;
340 _rContent
.setModDate( aModDate
);
342 // store the info about the children
344 sal_Int32 nChildren
= 0;
345 m_rStorage
.ReadInt32( nChildren
);
346 TemplateFolderContent
& rChildren
= _rContent
.getSubContents();
347 rChildren
.resize( 0 );
348 rChildren
.reserve( nChildren
);
349 // initialize them with their (local) names
350 while ( nChildren
-- )
352 OUString sURL
= m_rStorage
.ReadUniOrByteString(m_rStorage
.GetStreamCharSet());
353 sURL
= m_xOfficeInstDirs
->makeAbsoluteURL( sURL
);
354 rChildren
.push_back( new TemplateContent( INetURLObject( sURL
) ) );
359 _rContent
.getSubContents().begin(),
360 _rContent
.getSubContents().end(),
366 void operator() ( const ::rtl::Reference
< TemplateContent
>& _rxContent
) const
368 if ( _rxContent
.is() )
370 operator()( *_rxContent
);
377 //= TemplateFolderCacheImpl
379 class TemplateFolderCacheImpl
382 TemplateFolderContent m_aPreviousState
; // the current state of the template dirs (as found on the HD)
383 TemplateFolderContent m_aCurrentState
; // the previous state of the template dirs (as found in the cache file)
386 // will be lazy inited; never access directly; use getOfficeInstDirs().
387 uno::Reference
< util::XOfficeInstallationDirectories
> m_xOfficeInstDirs
;
389 std::unique_ptr
<SvStream
> m_pCacheStream
;
390 bool m_bNeedsUpdate
: 1;
391 bool m_bKnowState
: 1;
392 bool m_bValidCurrentState
: 1;
393 bool m_bAutoStoreState
: 1;
396 explicit TemplateFolderCacheImpl( bool _bAutoStoreState
);
397 ~TemplateFolderCacheImpl( );
403 bool openCacheStream( bool _bForRead
);
404 void closeCacheStream( );
406 /// read the state of the dirs from the cache file
407 bool readPreviousState();
408 /// read the current state of the dirs
409 bool readCurrentState();
411 static OUString
implParseSmart( const OUString
& _rPath
);
413 bool implReadFolder( const ::rtl::Reference
< TemplateContent
>& _rxRoot
);
415 static sal_Int32
getMagicNumber();
416 static void normalize( TemplateFolderContent
& _rState
);
418 // @return <TRUE/> if the states equal
419 static bool equalStates( const TemplateFolderContent
& _rLHS
, const TemplateFolderContent
& _rRHS
);
421 // late initialize m_xOfficeInstDirs
422 const uno::Reference
< util::XOfficeInstallationDirectories
>& getOfficeInstDirs();
426 TemplateFolderCacheImpl::TemplateFolderCacheImpl( bool _bAutoStoreState
)
427 :m_bNeedsUpdate ( true )
428 ,m_bKnowState ( false )
429 ,m_bValidCurrentState ( false )
430 ,m_bAutoStoreState ( _bAutoStoreState
)
435 TemplateFolderCacheImpl::~TemplateFolderCacheImpl( )
437 // store the current state if possible and required
438 if ( m_bValidCurrentState
&& m_bAutoStoreState
)
445 sal_Int32
TemplateFolderCacheImpl::getMagicNumber()
447 return (sal_Int8('T') << 12)
448 | (sal_Int8('D') << 8)
449 | (sal_Int8('S') << 4)
454 void TemplateFolderCacheImpl::normalize( TemplateFolderContent
& _rState
)
456 SubContentSort()( _rState
);
460 bool TemplateFolderCacheImpl::equalStates( const TemplateFolderContent
& _rLHS
, const TemplateFolderContent
& _rRHS
)
462 if ( _rLHS
.size() != _rRHS
.size() )
465 // as both arrays are sorted (by definition - this is a precondition of this method)
466 // we can simply go from the front to the back and compare the single elements
468 ::std::pair
< ConstFolderIterator
, ConstFolderIterator
> aFirstDifferent
= ::std::mismatch(
472 TemplateContentEqual()
475 return aFirstDifferent
.first
== _rLHS
.end();
479 void TemplateFolderCacheImpl::storeState()
481 if ( !m_bValidCurrentState
)
484 if ( !(m_bValidCurrentState
&& openCacheStream( false )) )
487 m_pCacheStream
->WriteInt32( getMagicNumber() );
489 // store the template root folders
491 m_pCacheStream
->WriteInt32( m_aCurrentState
.size() );
494 m_aCurrentState
.begin(),
495 m_aCurrentState
.end(),
496 StoreContentURL( *m_pCacheStream
, getOfficeInstDirs() )
501 m_aCurrentState
.begin(),
502 m_aCurrentState
.end(),
503 StoreFolderContent( *m_pCacheStream
, getOfficeInstDirs() )
508 OUString
TemplateFolderCacheImpl::implParseSmart( const OUString
& _rPath
)
510 INetURLObject aParser
;
511 aParser
.SetSmartProtocol( INetProtocol::File
);
512 aParser
.SetURL( _rPath
);
513 if ( INetProtocol::NotValid
== aParser
.GetProtocol() )
516 osl::FileBase::getFileURLFromSystemPath( _rPath
, sURL
);
517 aParser
.SetURL( sURL
);
519 return aParser
.GetMainURL( INetURLObject::DecodeMechanism::ToIUri
);
523 void TemplateFolderCacheImpl::closeCacheStream( )
525 m_pCacheStream
.reset();
529 bool TemplateFolderCacheImpl::implReadFolder( const ::rtl::Reference
< TemplateContent
>& _rxRoot
)
533 // create a content for the current folder root
534 Reference
< XResultSet
> xResultSet
;
535 Sequence
< OUString
> aContentProperties
{ u
"Title"_ustr
, u
"DateModified"_ustr
, u
"DateCreated"_ustr
,
538 // get the set of sub contents in the folder
541 Reference
< XDynamicResultSet
> xDynResultSet
;
543 ::ucbhelper::Content
aTemplateRoot( _rxRoot
->getURL(), Reference
< XCommandEnvironment
>(), comphelper::getProcessComponentContext() );
544 xDynResultSet
= aTemplateRoot
.createDynamicCursor( aContentProperties
);
545 if ( xDynResultSet
.is() )
546 xResultSet
= xDynResultSet
->getStaticResultSet();
548 catch( CommandAbortedException
& )
550 TOOLS_WARN_EXCEPTION( "svtools.misc", "" );
553 catch( css::uno::Exception
& )
557 // collect the infos about the sub contents
558 if ( xResultSet
.is() )
560 Reference
< XRow
> xRow( xResultSet
, UNO_QUERY_THROW
);
561 Reference
< XContentAccess
> xContentAccess( xResultSet
, UNO_QUERY_THROW
);
563 while ( xResultSet
->next() )
565 INetURLObject
aSubContentURL( xContentAccess
->queryContentIdentifierString() );
567 // a new content instance
568 ::rtl::Reference
< TemplateContent
> xChild
= new TemplateContent( std::move(aSubContentURL
) );
571 xChild
->setModDate( xRow
->getTimestamp( 2 ) ); // date modified
572 if ( xRow
->wasNull() )
573 xChild
->setModDate( xRow
->getTimestamp( 3 ) ); // fallback: date created
575 // push back this content
576 _rxRoot
->push_back( xChild
);
579 if ( xRow
->getBoolean( 4 ) && !xRow
->wasNull() )
580 { // yes -> step down
581 ConstFolderIterator aNextLevelRoot
= _rxRoot
->end();
583 implReadFolder( *aNextLevelRoot
);
588 catch( const Exception
& )
590 TOOLS_WARN_EXCEPTION( "svtools", "TemplateFolderCacheImpl::implReadFolder" );
597 bool TemplateFolderCacheImpl::readCurrentState()
600 m_bValidCurrentState
= false;
601 TemplateFolderContent aTemplateFolderContent
;
602 m_aCurrentState
.swap( aTemplateFolderContent
);
604 // the template directories from the config
605 const SvtPathOptions aPathOptions
;
606 const OUString
& aDirs
= aPathOptions
.GetTemplatePath();
608 // loop through all the root-level template folders
609 sal_Int32 nIndex
= 0;
612 OUString
sTemplatePath( aDirs
.getToken(0, ';', nIndex
) );
613 sTemplatePath
= aPathOptions
.ExpandMacros( sTemplatePath
);
615 // Make sure excess ".." path segments (from expanding bootstrap
616 // variables in paths) are normalized in the same way they are
617 // normalized for paths read from the .templdir.cache file (where
618 // paths have gone through makeRelocatable URL on writing out and
619 // then through makeAbsoluteURL when reading back in), as otherwise
620 // equalStates() in needsUpdate() could erroneously consider
621 // m_aCurrentState and m_aPreviousState as different:
622 sTemplatePath
= getOfficeInstDirs()->makeAbsoluteURL(
623 getOfficeInstDirs()->makeRelocatableURL(sTemplatePath
));
625 // create a new entry
626 m_aCurrentState
.push_back( new TemplateContent( INetURLObject( sTemplatePath
) ) );
627 TemplateFolderContent::iterator aCurrentRoot
= m_aCurrentState
.end();
630 if ( !implReadFolder( *aCurrentRoot
) )
633 while ( nIndex
>= 0 );
635 // normalize the array (which basically means "sort it")
636 normalize( m_aCurrentState
);
638 m_bValidCurrentState
= true;
639 return m_bValidCurrentState
;
643 bool TemplateFolderCacheImpl::readPreviousState()
645 DBG_ASSERT( m_pCacheStream
, "TemplateFolderCacheImpl::readPreviousState: not to be called without stream!" );
648 TemplateFolderContent aTemplateFolderContent
;
649 m_aPreviousState
.swap( aTemplateFolderContent
);
651 // check the magic number
652 sal_Int32 nMagic
= 0;
653 m_pCacheStream
->ReadInt32( nMagic
);
654 DBG_ASSERT( getMagicNumber() == nMagic
, "TemplateFolderCacheImpl::readPreviousState: invalid cache file!" );
655 if ( getMagicNumber() != nMagic
)
658 // the root directories
660 sal_Int32 nRootDirectories
= 0;
661 m_pCacheStream
->ReadInt32( nRootDirectories
);
662 // init empty TemplateContents with the URLs
663 m_aPreviousState
.reserve( nRootDirectories
);
664 while ( nRootDirectories
-- )
666 OUString sURL
= m_pCacheStream
->ReadUniOrByteString(m_pCacheStream
->GetStreamCharSet());
667 // #116281# Keep office installation relocatable. Never store
668 // any direct references to office installation directory.
669 sURL
= getOfficeInstDirs()->makeAbsoluteURL( sURL
);
670 m_aPreviousState
.push_back(
671 new TemplateContent( INetURLObject(sURL
) ) );
674 // read the contents of the root folders
676 m_aPreviousState
.begin(),
677 m_aPreviousState
.end(),
678 ReadFolderContent( *m_pCacheStream
, getOfficeInstDirs() )
681 DBG_ASSERT( !m_pCacheStream
->GetErrorCode(), "TemplateFolderCacheImpl::readPreviousState: unknown error during reading the state cache!" );
683 // normalize the array (which basically means "sort it")
684 normalize( m_aPreviousState
);
690 bool TemplateFolderCacheImpl::openCacheStream( bool _bForRead
)
692 // close any old stream instance
695 // get the storage directory
696 OUString sStorageURL
= implParseSmart( SvtPathOptions().GetStoragePath() );
697 INetURLObject
aStorageURL( sStorageURL
);
698 if ( INetProtocol::NotValid
== aStorageURL
.GetProtocol() )
700 OSL_FAIL( "TemplateFolderCacheImpl::openCacheStream: invalid storage path!" );
705 aStorageURL
.Append( u
".templdir.cache" );
708 m_pCacheStream
= UcbStreamHelper::CreateStream( aStorageURL
.GetMainURL( INetURLObject::DecodeMechanism::ToIUri
),
709 _bForRead
? StreamMode::READ
| StreamMode::NOCREATE
: StreamMode::WRITE
| StreamMode::TRUNC
);
710 DBG_ASSERT( m_pCacheStream
, "TemplateFolderCacheImpl::openCacheStream: could not open/create the cache stream!" );
711 if ( m_pCacheStream
&& m_pCacheStream
->GetErrorCode() )
713 m_pCacheStream
.reset();
716 if ( m_pCacheStream
)
717 m_pCacheStream
->SetStreamCharSet( RTL_TEXTENCODING_UTF8
);
719 return nullptr != m_pCacheStream
;
723 bool TemplateFolderCacheImpl::needsUpdate()
726 return m_bNeedsUpdate
;
728 m_bNeedsUpdate
= true;
731 if ( readCurrentState() )
733 // open the stream which contains the cached state of the directories
734 if ( openCacheStream( true ) )
735 { // opening the stream succeeded
736 if ( readPreviousState() )
738 m_bNeedsUpdate
= !equalStates( m_aPreviousState
, m_aCurrentState
);
746 return m_bNeedsUpdate
;
750 const uno::Reference
< util::XOfficeInstallationDirectories
>&
751 TemplateFolderCacheImpl::getOfficeInstDirs()
753 if ( !m_xOfficeInstDirs
.is() )
755 std::lock_guard
aGuard( m_aMutex
);
756 if ( !m_xOfficeInstDirs
.is() )
758 const uno::Reference
< uno::XComponentContext
>& xCtx(
759 comphelper::getProcessComponentContext() );
760 m_xOfficeInstDirs
= util::theOfficeInstallationDirectories::get(xCtx
);
763 return m_xOfficeInstDirs
;
767 //= TemplateFolderCache
770 TemplateFolderCache::TemplateFolderCache( bool _bAutoStoreState
)
771 :m_pImpl( new TemplateFolderCacheImpl( _bAutoStoreState
) )
776 TemplateFolderCache::~TemplateFolderCache( )
781 bool TemplateFolderCache::needsUpdate()
783 return m_pImpl
->needsUpdate();
787 void TemplateFolderCache::storeState()
789 m_pImpl
->storeState();
796 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */