2 * @file Common/inifile.cpp
3 * @brief Internal implementation of the class for reading and writing parameter values in INI files.
6 * Copyright (c) 2011 Enar Vaikene
8 * This file is part of the eVaf C++ cross-platform application development framework.
10 * This file can be used under the terms of the GNU General Public License
11 * version 3.0 as published by the Free Software Foundation and appearing in
12 * the file LICENSE included in the packaging of this file. Please review the
13 * the following information to ensure the GNU General Public License version
14 * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
16 * Alternatively, this file may be used in accordance with the Commercial License
17 * Agreement provided with the Software.
21 #include "inifile_p.h"
28 // End of line characters for Linux and Windows
35 using namespace eVaf::Common
;
37 //-------------------------------------------------------------------
39 IniFile::IniFile(QString
const & fileName
, QIODevice::OpenMode mode
)
41 d
= new Internal::IniFileImpl(fileName
, mode
);
49 bool IniFile::isValid() const
54 QString
IniFile::errorString() const
56 return d
->errorString();
59 QVariant
IniFile::getValue(QString
const & paramName
, QVariant
const & defaultValue
)
61 return d
->getValue(paramName
, defaultValue
);
64 bool IniFile::setValue(QString
const & paramName
, QVariant
const & value
)
66 return d
->setValue(paramName
, value
);
70 //-------------------------------------------------------------------
72 using namespace eVaf::Common::Internal
;
74 IniFileImpl::IniFileImpl(QString
const & fileName
, QIODevice::OpenMode mode
)
79 // Verify that we can open the file in the specified mode
82 mErrorString
= f
.errorString();
83 EVAF_ERROR("Failed to open INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
86 // Get and store the last modified time
88 mLastModified
= fi
.lastModified();
94 IniFileImpl::~IniFileImpl()
99 void IniFileImpl::updateCache(quint64 pos
, qint64 diff
)
101 // Walk through all the sections in the cache
102 QHash
<QString
, QExplicitlySharedDataPointer
<IniFileSection
> >::const_iterator it
;
103 for (it
= mCache
.constBegin(); it
!= mCache
.constEnd(); ++it
) {
104 QExplicitlySharedDataPointer
<IniFileSection
> sectionObject
= *it
;
106 // Update the section object if it comes after the current file offset
107 if (sectionObject
->filePos
> pos
)
108 sectionObject
->filePos
+= diff
;
110 // Update individual values in the section that come after the current file offset
111 QHash
<QString
, QExplicitlySharedDataPointer
<IniFileValue
> >::const_iterator it1
;
112 for (it1
= sectionObject
->values
.constBegin(); it1
!= sectionObject
->values
.constEnd(); ++it1
) {
113 QExplicitlySharedDataPointer
<IniFileValue
> valueObject
= *it1
;
114 if (valueObject
->filePos
> pos
)
115 valueObject
->filePos
+= diff
;
120 QExplicitlySharedDataPointer
<IniFileSection
> IniFileImpl::getSection(QFile
& file
, QString
const & sectionName
)
122 // Check for external modifications
124 if (fi
.lastModified() != mLastModified
) {
125 // The INI file was modified externally and our internal cache is probably invalid
127 mLastModified
= fi
.lastModified();
130 // Look for the section in the cache first
131 QHash
<QString
, QExplicitlySharedDataPointer
<IniFileSection
> >::const_iterator it
= mCache
.constFind(sectionName
.toLower());
132 if (it
!= mCache
.constEnd()) {
133 // Found in the cache
135 file
.seek((*it
)->filePos
);
139 // Read the INI file and look for the section
140 while (mValid
&& !file
.atEnd()) {
141 QString line
= file
.readLine().trimmed();
143 // Ignore the line if it is empty, a comment or not a section name
144 if (line
.isEmpty() || line
.startsWith(';') || line
.startsWith('#') || !line
.startsWith('['))
147 // Position of the closing ']'
148 int idx
= line
.indexOf(']');
152 // Is this the section that we are looking for?
153 if (line
.mid(1, idx
- 1).compare(sectionName
, Qt::CaseInsensitive
) == 0) {
154 // Create the section object and add to the cache
155 QExplicitlySharedDataPointer
<IniFileSection
> sectionObject(new IniFileSection(file
.pos()));
156 sectionObject
->name
= sectionName
.toLower();
157 mCache
.insert(sectionName
.toLower(), sectionObject
);
159 // Returns the section object
160 return sectionObject
;
165 // No such section found in the INI file -- return an invalid object; the file is already positioned at the end
166 return QExplicitlySharedDataPointer
<IniFileSection
>();
169 QExplicitlySharedDataPointer
<IniFileValue
> IniFileImpl::getParameter(QFile
& file
, IniFileSection
& section
, QString
const & paramName
)
171 // Look for the parameter in the cache first
172 QHash
<QString
, QExplicitlySharedDataPointer
<IniFileValue
> >::const_iterator it
= section
.values
.constFind(paramName
.toLower());
173 if (it
!= section
.values
.constEnd()) {
174 // Found it in the cache
176 file
.seek((*it
)->filePos
);
180 // Read the INI file and look for the parameter name
181 while (mValid
&& !file
.atEnd()) {
183 // Current file position
184 quint64 currentPos
= file
.pos();
186 QString line
= file
.readLine().trimmed();
188 // Ignore the line if it is empty or a comment
189 if (line
.isEmpty() || line
.startsWith(';') || line
.startsWith('#'))
192 // Returns an invalid object if we reach the beginning of another section
193 if (line
.startsWith('[')) {
194 // Rewind to the stored position
195 file
.seek(currentPos
);
197 return QExplicitlySharedDataPointer
<IniFileValue
>();
200 // Locate '=' in the line and get the name/value pair
201 int idx
= line
.indexOf('=');
205 QString name
= line
.mid(0, idx
).trimmed().toLower();
206 QString value
= line
.mid(idx
+ 1).trimmed();
208 // Check for the 'windows:' or 'linux:' prefix in the parameter name
209 bool thisOsOnly
= false;
211 if (name
.startsWith("windows:"))
213 if (name
.startsWith("linux:")) {
219 if (name
.startsWith("linux:"))
221 if (name
.startsWith("windows:")) {
227 // If the parameter value is not in the cache, add it to the cache
228 QExplicitlySharedDataPointer
<IniFileValue
> valueObject
;
229 QHash
<QString
, QExplicitlySharedDataPointer
<IniFileValue
> >::const_iterator it
= section
.values
.constFind(name
);
230 if (it
== section
.values
.constEnd()) {
231 valueObject
= new IniFileValue(currentPos
);
232 valueObject
->name
= name
;
233 valueObject
->paramValue
= value
;
234 section
.values
.insert(name
, valueObject
);
237 // We should not find it in the cache, but if we somehow managed to end up here, use and update the object from the cache
239 valueObject
->name
= name
;
240 valueObject
->paramValue
= value
;
243 // Is this the parameter vwe are looking for?
244 if (name
.compare(paramName
, Qt::CaseInsensitive
) == 0) {
245 // Rewind to the beginning of the line
246 file
.seek(currentPos
);
248 // Return the value object
253 // The parameter with this name was not found in the INI file -- return an invalid object; the file is already positioned at the next section or the end of the file
254 return QExplicitlySharedDataPointer
<IniFileValue
>();
257 QVariant
IniFileImpl::getValue(QString
const & paramName
, QVariant
const & defaultValue
)
259 // Locate the '/' character that separates section names from key names
260 int idx
= paramName
.indexOf('/');
264 // Separate section and key names
265 QString section
= paramName
.left(idx
);
266 QString key
= paramName
.mid(idx
+ 1);
270 if (!f
.open(mMode
)) {
271 mErrorString
= f
.errorString();
273 EVAF_ERROR("Failed to open the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
277 // Locate the section
278 QExplicitlySharedDataPointer
<IniFileSection
> sectionObject
= getSection(f
, section
);
282 // Locate the parameter
283 QExplicitlySharedDataPointer
<IniFileValue
> valueObject
= getParameter(f
, *sectionObject
, key
);
290 return toVariant(valueObject
->paramValue
, defaultValue
);
293 bool IniFileImpl::setValue(QString
const & paramName
, QVariant
const & value
)
295 // Locate the '/' character that separates section names from key names
296 int idx
= paramName
.indexOf('/');
300 // Separate section and key names
301 QString section
= paramName
.left(idx
).toLower();
302 QString key
= paramName
.mid(idx
+ 1).toLower();
304 // Format the value depending on the type
306 switch (value
.type()) {
308 valueString
= "0x" + QString::number(value
.toUInt(), 16);
311 valueString
= QString::number(value
.toInt());
313 case QVariant::Double
:
314 valueString
= QString::number(value
.toDouble(), 'f');
317 valueString
= value
.toBool() ? "true" : "false";
320 valueString
= value
.toChar();
323 valueString
= value
.toString();
328 if (!f
.open(mMode
)) {
329 mErrorString
= f
.errorString();
331 EVAF_ERROR("Failed to open the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
335 mErrorString
.clear();
337 // Get the section object
338 QExplicitlySharedDataPointer
<IniFileSection
> sectionObject
= getSection(f
, section
);
340 // If the section is not found, add a new section to the end of the INI file
341 if (!sectionObject
) {
343 // Write the new section to the INI file (the file is already positioned at the end)
344 if (f
.write(QString("[%1]" EOL
).arg(section
).toLocal8Bit()) == -1) {
345 mErrorString
= f
.errorString();
347 EVAF_ERROR("Failed to write to the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
351 // Current file position
352 quint64 currentPos
= f
.pos();
354 // Add the new section to the internal cache
355 sectionObject
= new IniFileSection(currentPos
);
356 mCache
.insert(section
.toLower(), sectionObject
);
358 // Write the parameter value to the INI file
359 if (f
.write(QString("%1 = %2" EOL
).arg(key
).arg(valueString
).toLocal8Bit()) == -1) {
360 mErrorString
= f
.errorString();
362 EVAF_ERROR("Failed to write to the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
366 // Add the parameter value to the internal cache
367 QExplicitlySharedDataPointer
<IniFileValue
> valueObject(new IniFileValue(currentPos
));
368 valueObject
->name
= key
;
369 valueObject
->paramValue
= valueString
;
370 sectionObject
->values
.insert(key
, valueObject
);
373 // If the section is found, use the existing section object from the cache
376 quint64 oldPos
= f
.pos();
377 QString prefix
; // Platform-specific prefix
379 // Locate the parameter value
380 QExplicitlySharedDataPointer
<IniFileValue
> valueObject
= getParameter(f
, *sectionObject
, key
);
382 // The file is now positioned either at the beginning of the parameter line or at the end of the section/file
383 currentPos
= f
.pos();
386 // Parameter was found; skip the line with the current parameter value
390 // Format the prefix string if necessary
391 if (valueObject
->thisOsOnly
) {
402 // Parameter was not found; create a new value object
403 valueObject
= new IniFileValue(currentPos
);
404 valueObject
->name
= key
;
407 // Store everything from the current position till the end of file in a temporary buffer
409 if (!tmp
.open(QBuffer::ReadWrite
)) {
410 mErrorString
= tmp
.errorString();
412 EVAF_ERROR("Failed to open the temporary buffer : %s", qPrintable(mErrorString
));
415 if (tmp
.write(f
.readAll()) == -1) {
416 mErrorString
= tmp
.errorString();
418 EVAF_ERROR("Failed to write to the temporary buffer : %s", qPrintable(mErrorString
));
422 // Rewind to the original position and write the new parameter value
424 if (f
.write(QString("%1%2 = %3" EOL
).arg(prefix
).arg(key
).arg(valueString
).toLocal8Bit()) == -1) {
425 mErrorString
= f
.errorString();
427 EVAF_ERROR("Failed to write to the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
431 // How much were sections and parameters shifted due to the new parameter value?
432 qint64 diff
= qint64(f
.pos() - oldPos
);
434 // Write everything back from the temporary buffer
436 if (f
.write(tmp
.readAll()) == -1) {
437 mErrorString
= f
.errorString();
439 EVAF_ERROR("Failed to write to the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
444 // If the original file was larger than the new file, then truncate it
445 if (f
.size() > f
.pos()) {
446 if (!f
.resize(f
.pos())) {
447 mErrorString
= f
.errorString();
449 EVAF_ERROR("Failed to resize the INI file %s : %s", qPrintable(mFileName
), qPrintable(mErrorString
));
454 // If the shift was not zero, update the cache
456 updateCache(currentPos
, diff
);
462 // Update the time when the INI file was last modified
463 QFileInfo
fi(mFileName
);
464 mLastModified
= fi
.lastModified();