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 .
28 #include <osl/file.hxx>
29 #include <tools/config.hxx>
30 #include <sal/log.hxx>
42 ImplGroupData
* mpNext
;
43 ImplKeyData
* mpFirstKey
;
45 sal_uInt16 mnEmptyLines
;
50 ImplGroupData
* mpFirstGroup
;
52 sal_uInt32 mnDataUpdateId
;
53 sal_uInt32 mnTimeStamp
;
59 static OUString
toUncPath( const OUString
& rPath
)
63 // check if rFileName is already a URL; if not make it so
64 if( rPath
.startsWith( "file://"))
68 else if( ::osl::FileBase::getFileURLFromSystemPath( rPath
, aFileURL
) != ::osl::FileBase::E_None
)
75 static sal_uInt32
ImplSysGetConfigTimeStamp( const OUString
& rFileName
)
77 sal_uInt32 nTimeStamp
= 0;
78 ::osl::DirectoryItem aItem
;
79 ::osl::FileStatus
aStatus( osl_FileStatus_Mask_ModifyTime
);
81 if( ::osl::DirectoryItem::get( rFileName
, aItem
) == ::osl::FileBase::E_None
&&
82 aItem
.getFileStatus( aStatus
) == ::osl::FileBase::E_None
)
84 nTimeStamp
= aStatus
.getModifyTime().Seconds
;
90 static std::unique_ptr
<sal_uInt8
[]> ImplSysReadConfig( const OUString
& rFileName
,
91 sal_uInt64
& rRead
, bool& rbRead
, bool& rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
93 std::unique_ptr
<sal_uInt8
[]> pBuf
;
94 ::osl::File
aFile( rFileName
);
96 if( aFile
.open( osl_File_OpenFlag_Read
) == ::osl::FileBase::E_None
)
99 if( aFile
.getSize( nPos
) == ::osl::FileBase::E_None
)
101 if (nPos
> SAL_MAX_SIZE
) {
105 pBuf
.reset(new sal_uInt8
[static_cast< std::size_t >(nPos
)]);
106 sal_uInt64 nRead
= 0;
107 if( aFile
.read( pBuf
.get(), nPos
, nRead
) == ::osl::FileBase::E_None
&& nRead
== nPos
)
109 //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files
110 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
111 if (nRead
> 2 && memcmp(pBuf
.get(), BOM
, 3) == 0)
114 memmove(pBuf
.get(), pBuf
.get() + 3, sal::static_int_cast
<std::size_t>(nRead
* sizeof(sal_uInt8
)) );
118 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
133 static bool ImplSysWriteConfig( const OUString
& rFileName
,
134 const sal_uInt8
* pBuf
, sal_uInt32 nBufLen
, bool rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
136 bool bSuccess
= false;
137 bool bUTF8BOMSuccess
= false;
139 ::osl::File
aFile( rFileName
);
140 ::osl::FileBase::RC eError
= aFile
.open( osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
141 if( eError
!= ::osl::FileBase::E_None
)
142 eError
= aFile
.open( osl_File_OpenFlag_Write
);
143 if( eError
== ::osl::FileBase::E_None
)
149 //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files
152 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
153 sal_uInt64 nUTF8BOMWritten
;
154 if( aFile
.write( BOM
, 3, nUTF8BOMWritten
) == ::osl::FileBase::E_None
&& 3 == nUTF8BOMWritten
)
156 bUTF8BOMSuccess
= true;
160 if( aFile
.write( pBuf
, nBufLen
, nWritten
) == ::osl::FileBase::E_None
&& nWritten
== nBufLen
)
164 if ( rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
)
166 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
170 return rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
;
174 OString
makeOString(const sal_uInt8
* p
, sal_uInt64 n
)
176 if (n
> SAL_MAX_INT32
)
181 ::std::abort(); //TODO: handle this gracefully
185 reinterpret_cast< char const * >(p
),
186 sal::static_int_cast
< sal_Int32
>(n
));
190 static void ImplMakeConfigList( ImplConfigData
* pData
,
191 const sal_uInt8
* pBuf
, sal_uInt64 nLen
)
196 // Parse buffer and build config list
202 const sal_uInt8
* pLine
;
203 ImplKeyData
* pPrevKey
= nullptr;
205 ImplGroupData
* pPrevGroup
= nullptr;
206 ImplGroupData
* pGroup
= nullptr;
211 if ( pBuf
[i
] == 0x1A )
214 // Remove spaces and tabs
215 while ( (pBuf
[i
] == ' ') || (pBuf
[i
] == '\t') )
218 // remember line-starts
222 // search line-endings
223 while ( (i
< nLen
) && pBuf
[i
] && (pBuf
[i
] != '\r') && (pBuf
[i
] != '\n') &&
229 // if Line-ending is found, continue once
231 (pBuf
[i
] != pBuf
[i
+1]) &&
232 ((pBuf
[i
+1] == '\r') || (pBuf
[i
+1] == '\n')) )
239 pGroup
= new ImplGroupData
;
240 pGroup
->mpNext
= nullptr;
241 pGroup
->mpFirstKey
= nullptr;
242 pGroup
->mnEmptyLines
= 0;
244 pPrevGroup
->mpNext
= pGroup
;
246 pData
->mpFirstGroup
= pGroup
;
251 // filter group names
254 // remove spaces and tabs
255 while ( (*pLine
== ' ') || (*pLine
== '\t') )
261 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != ']') )
265 while ( (pLine
[nNameLen
-1] == ' ') || (pLine
[nNameLen
-1] == '\t') )
268 pGroup
->maGroupName
= makeOString(pLine
, nNameLen
);
274 // If no group exists yet, add to default
277 pGroup
= new ImplGroupData
;
278 pGroup
->mpNext
= nullptr;
279 pGroup
->mpFirstKey
= nullptr;
280 pGroup
->mnEmptyLines
= 0;
281 pData
->mpFirstGroup
= pGroup
;
286 // if empty line, append it
289 while ( pGroup
->mnEmptyLines
)
291 pKey
= new ImplKeyData
;
292 pKey
->mbIsComment
= true;
293 pPrevKey
->mpNext
= pKey
;
295 pGroup
->mnEmptyLines
--;
300 pKey
= new ImplKeyData
;
301 pKey
->mpNext
= nullptr;
303 pPrevKey
->mpNext
= pKey
;
305 pGroup
->mpFirstKey
= pKey
;
307 if ( pLine
[0] == ';' )
309 pKey
->maValue
= makeOString(pLine
, nLineLen
);
310 pKey
->mbIsComment
= true;
314 pKey
->mbIsComment
= false;
316 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != '=') )
319 // Remove spaces and tabs
322 while ( (pLine
[nNameLen
-1] == ' ') || (pLine
[nNameLen
-1] == '\t') )
325 pKey
->maKey
= makeOString(pLine
, nNameLen
);
327 if ( nKeyLen
< nLineLen
)
331 // Remove spaces and tabs
332 while ( (*pLine
== ' ') || (*pLine
== '\t') )
339 while ( (pLine
[nLineLen
-1] == ' ') || (pLine
[nLineLen
-1] == '\t') )
341 pKey
->maValue
= makeOString(pLine
, nLineLen
);
348 // Spaces are counted and appended only after key generation,
349 // as we want to store spaces even after adding new keys
351 pGroup
->mnEmptyLines
++;
357 static std::unique_ptr
<sal_uInt8
[]> ImplGetConfigBuffer( const ImplConfigData
* pData
, sal_uInt32
& rLen
)
359 std::unique_ptr
<sal_uInt8
[]> pWriteBuf
;
361 sal_uInt8 aLineEndBuf
[2] = {0, 0};
363 ImplGroupData
* pGroup
;
365 sal_uInt32 nValueLen
;
367 sal_uInt32 nLineEndLen
;
369 aLineEndBuf
[0] = '\r';
370 aLineEndBuf
[1] = '\n';
374 pGroup
= pData
->mpFirstGroup
;
377 // Don't write empty groups
378 if ( pGroup
->mpFirstKey
)
380 nBufLen
+= pGroup
->maGroupName
.getLength() + nLineEndLen
+ 2;
381 pKey
= pGroup
->mpFirstKey
;
384 nValueLen
= pKey
->maValue
.getLength();
385 if ( pKey
->mbIsComment
)
386 nBufLen
+= nValueLen
+ nLineEndLen
;
388 nBufLen
+= pKey
->maKey
.getLength() + nValueLen
+ nLineEndLen
+ 1;
393 // Write empty lines after each group
394 if ( !pGroup
->mnEmptyLines
)
395 pGroup
->mnEmptyLines
= 1;
396 nBufLen
+= nLineEndLen
* pGroup
->mnEmptyLines
;
399 pGroup
= pGroup
->mpNext
;
402 // Output buffer length
406 pWriteBuf
.reset(new sal_uInt8
[nLineEndLen
]);
407 pWriteBuf
[0] = aLineEndBuf
[0];
408 if ( nLineEndLen
== 2 )
409 pWriteBuf
[1] = aLineEndBuf
[1];
413 // Allocate new write buffer (caller frees it)
414 pWriteBuf
.reset(new sal_uInt8
[nBufLen
]);
417 pBuf
= pWriteBuf
.get();
418 pGroup
= pData
->mpFirstGroup
;
421 // Don't write empty groups
422 if ( pGroup
->mpFirstKey
)
425 memcpy( pBuf
, pGroup
->maGroupName
.getStr(), pGroup
->maGroupName
.getLength() );
426 pBuf
+= pGroup
->maGroupName
.getLength();
428 *pBuf
= aLineEndBuf
[0]; pBuf
++;
429 if ( nLineEndLen
== 2 )
431 *pBuf
= aLineEndBuf
[1]; pBuf
++;
433 pKey
= pGroup
->mpFirstKey
;
436 nValueLen
= pKey
->maValue
.getLength();
437 if ( pKey
->mbIsComment
)
441 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
444 *pBuf
= aLineEndBuf
[0]; pBuf
++;
445 if ( nLineEndLen
== 2 )
447 *pBuf
= aLineEndBuf
[1]; pBuf
++;
452 nKeyLen
= pKey
->maKey
.getLength();
453 memcpy( pBuf
, pKey
->maKey
.getStr(), nKeyLen
);
456 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
458 *pBuf
= aLineEndBuf
[0]; pBuf
++;
459 if ( nLineEndLen
== 2 )
461 *pBuf
= aLineEndBuf
[1]; pBuf
++;
468 // Store empty line after each group
469 sal_uInt16 nEmptyLines
= pGroup
->mnEmptyLines
;
470 while ( nEmptyLines
)
472 *pBuf
= aLineEndBuf
[0]; pBuf
++;
473 if ( nLineEndLen
== 2 )
475 *pBuf
= aLineEndBuf
[1]; pBuf
++;
481 pGroup
= pGroup
->mpNext
;
487 static void ImplReadConfig( ImplConfigData
* pData
)
489 sal_uInt32 nTimeStamp
= 0;
490 sal_uInt64 nRead
= 0;
492 bool bIsUTF8BOM
= false;
493 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplSysReadConfig( pData
->maFileName
, nRead
, bRead
, bIsUTF8BOM
, nTimeStamp
);
495 // Read config list from buffer
498 ImplMakeConfigList( pData
, pBuf
.get(), nRead
);
501 pData
->mnTimeStamp
= nTimeStamp
;
502 pData
->mbModified
= false;
504 pData
->mbRead
= true;
506 pData
->mbIsUTF8BOM
= true;
509 static void ImplWriteConfig( ImplConfigData
* pData
)
511 SAL_WARN_IF( pData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( pData
->maFileName
),
512 "tools.generic", "Config overwrites modified configfile: " << pData
->maFileName
);
514 // Read config list from buffer
516 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplGetConfigBuffer( pData
, nBufLen
);
519 if ( ImplSysWriteConfig( pData
->maFileName
, pBuf
.get(), nBufLen
, pData
->mbIsUTF8BOM
, pData
->mnTimeStamp
) )
520 pData
->mbModified
= false;
523 pData
->mbModified
= false;
526 static void ImplDeleteConfigData( ImplConfigData
* pData
)
528 ImplKeyData
* pTempKey
;
530 ImplGroupData
* pTempGroup
;
531 ImplGroupData
* pGroup
= pData
->mpFirstGroup
;
534 pTempGroup
= pGroup
->mpNext
;
537 pKey
= pGroup
->mpFirstKey
;
540 pTempKey
= pKey
->mpNext
;
545 // remove group and continue
550 pData
->mpFirstGroup
= nullptr;
553 static std::unique_ptr
<ImplConfigData
> ImplGetConfigData( const OUString
& rFileName
)
555 std::unique_ptr
<ImplConfigData
> pData(new ImplConfigData
);
556 pData
->maFileName
= rFileName
;
557 pData
->mpFirstGroup
= nullptr;
558 pData
->mnDataUpdateId
= 0;
559 pData
->mbRead
= false;
560 pData
->mbIsUTF8BOM
= false;
561 ImplReadConfig( pData
.get() );
566 bool Config::ImplUpdateConfig() const
568 // Re-read file if timestamp differs
569 if ( mpData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( maFileName
) )
571 ImplDeleteConfigData( mpData
.get() );
572 ImplReadConfig( mpData
.get() );
573 mpData
->mnDataUpdateId
++;
580 ImplGroupData
* Config::ImplGetGroup() const
582 if ( !mpActGroup
|| (mnDataUpdateId
!= mpData
->mnDataUpdateId
) )
584 ImplGroupData
* pPrevGroup
= nullptr;
585 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
588 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(maGroupName
) )
592 pGroup
= pGroup
->mpNext
;
595 // Add group if not exists
598 pGroup
= new ImplGroupData
;
599 pGroup
->mpNext
= nullptr;
600 pGroup
->mpFirstKey
= nullptr;
601 pGroup
->mnEmptyLines
= 1;
603 pPrevGroup
->mpNext
= pGroup
;
605 mpData
->mpFirstGroup
= pGroup
;
608 // Always inherit group names and update cache members
609 pGroup
->maGroupName
= maGroupName
;
610 const_cast<Config
*>(this)->mnDataUpdateId
= mpData
->mnDataUpdateId
;
611 const_cast<Config
*>(this)->mpActGroup
= pGroup
;
617 Config::Config( const OUString
& rFileName
)
619 // Initialize config data
620 maFileName
= toUncPath( rFileName
);
621 mpData
= ImplGetConfigData( maFileName
);
622 mpActGroup
= nullptr;
625 SAL_INFO("tools.generic", "Config::Config( " << maFileName
<< " )");
630 SAL_INFO("tools.generic", "Config::~Config()" );
633 ImplDeleteConfigData( mpData
.get() );
636 void Config::SetGroup(const OString
& rGroup
)
638 // If group is to be reset, it needs to be updated on next call
639 if ( maGroupName
!= rGroup
)
641 maGroupName
= rGroup
;
642 mnDataUpdateId
= mpData
->mnDataUpdateId
-1;
646 void Config::DeleteGroup(const OString
& rGroup
)
648 // Update config data if necessary
649 if ( !mpData
->mbRead
)
652 mpData
->mbRead
= true;
655 ImplGroupData
* pPrevGroup
= nullptr;
656 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
659 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
663 pGroup
= pGroup
->mpNext
;
669 ImplKeyData
* pTempKey
;
670 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
673 pTempKey
= pKey
->mpNext
;
678 // Rewire pointers and remove group
680 pPrevGroup
->mpNext
= pGroup
->mpNext
;
682 mpData
->mpFirstGroup
= pGroup
->mpNext
;
685 // Rewrite config data
686 mpData
->mbModified
= true;
688 mnDataUpdateId
= mpData
->mnDataUpdateId
;
689 mpData
->mnDataUpdateId
++;
693 OString
Config::GetGroupName(sal_uInt16 nGroup
) const
695 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
696 sal_uInt16 nGroupCount
= 0;
700 if ( nGroup
== nGroupCount
)
702 aGroupName
= pGroup
->maGroupName
;
707 pGroup
= pGroup
->mpNext
;
713 sal_uInt16
Config::GetGroupCount() const
715 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
716 sal_uInt16 nGroupCount
= 0;
720 pGroup
= pGroup
->mpNext
;
726 bool Config::HasGroup(const OString
& rGroup
) const
728 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
733 if( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
739 pGroup
= pGroup
->mpNext
;
745 OString
Config::ReadKey(const OString
& rKey
) const
747 return ReadKey(rKey
, OString());
750 OString
Config::ReadKey(const OString
& rKey
, const OString
& rDefault
) const
752 SAL_INFO("tools.generic", "Config::ReadKey( " << rKey
<< " ) from " << GetGroup()
753 << " in " << maFileName
);
755 // Search key, return value if found
756 ImplGroupData
* pGroup
= ImplGetGroup();
759 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
762 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
763 return pKey
->maValue
;
772 void Config::WriteKey(const OString
& rKey
, const OString
& rStr
)
774 SAL_INFO("tools.generic", "Config::WriteKey( " << rKey
<< ", " << rStr
<< " ) to "
775 << GetGroup() << " in " << maFileName
);
777 // Update config data if necessary
778 if ( !mpData
->mbRead
)
781 mpData
->mbRead
= true;
784 // Search key and update value if found
785 ImplGroupData
* pGroup
= ImplGetGroup();
788 ImplKeyData
* pPrevKey
= nullptr;
789 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
792 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
802 pKey
= new ImplKeyData
;
803 pKey
->mpNext
= nullptr;
805 pKey
->mbIsComment
= false;
807 pPrevKey
->mpNext
= pKey
;
809 pGroup
->mpFirstKey
= pKey
;
813 bNewValue
= pKey
->maValue
!= rStr
;
817 pKey
->maValue
= rStr
;
819 mpData
->mbModified
= true;
824 void Config::DeleteKey(const OString
& rKey
)
826 // Update config data if necessary
827 if ( !mpData
->mbRead
)
830 mpData
->mbRead
= true;
833 // Search key and update value
834 ImplGroupData
* pGroup
= ImplGetGroup();
837 ImplKeyData
* pPrevKey
= nullptr;
838 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
841 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
850 // Rewire group pointers and delete
852 pPrevKey
->mpNext
= pKey
->mpNext
;
854 pGroup
->mpFirstKey
= pKey
->mpNext
;
857 mpData
->mbModified
= true;
862 sal_uInt16
Config::GetKeyCount() const
864 SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName
);
866 // Search key and update value
867 sal_uInt16 nCount
= 0;
868 ImplGroupData
* pGroup
= ImplGetGroup();
871 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
874 if ( !pKey
->mbIsComment
)
884 OString
Config::GetKeyName(sal_uInt16 nKey
) const
886 SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32
>(nKey
))
887 << " ) from " << GetGroup() << " in " << maFileName
);
889 // search key and return name if found
890 ImplGroupData
* pGroup
= ImplGetGroup();
893 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
896 if ( !pKey
->mbIsComment
)
910 OString
Config::ReadKey(sal_uInt16 nKey
) const
912 SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32
>(nKey
))
913 << " ) from " << GetGroup() << " in " << maFileName
);
915 // Search key and return value if found
916 ImplGroupData
* pGroup
= ImplGetGroup();
919 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
922 if ( !pKey
->mbIsComment
)
925 return pKey
->maValue
;
938 if ( mpData
->mbModified
)
939 ImplWriteConfig( mpData
.get() );
942 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */