Define QT_OPEN_LARGEFILE on Symbian + WinCE
[qt-netbsd.git] / src / corelib / io / qsettings_mac.cpp
blob8fcacccf8fd54003db32b1d613584ea4ad72f742
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtCore module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
38 ** $QT_END_LICENSE$
40 ****************************************************************************/
42 #include "qsettings.h"
43 #include "qsettings_p.h"
44 #include "qdatetime.h"
45 #include "qdir.h"
46 #include "qvarlengtharray.h"
47 #include "private/qcore_mac_p.h"
49 QT_BEGIN_NAMESPACE
51 static const CFStringRef hostNames[2] = { kCFPreferencesCurrentHost, kCFPreferencesAnyHost };
52 static const int numHostNames = 2;
55 On the Mac, it is more natural to use '.' as the key separator
56 than '/'. Therefore, it makes sense to replace '/' with '.' in
57 keys. Then we replace '.' with middle dots (which we can't show
58 here) and middle dots with '/'. A key like "4.0/BrowserCommand"
59 becomes "4<middot>0.BrowserCommand".
62 enum RotateShift { Macify = 1, Qtify = 2 };
64 static QString rotateSlashesDotsAndMiddots(const QString &key, int shift)
66 static const int NumKnights = 3;
67 static const char knightsOfTheRoundTable[NumKnights] = { '/', '.', '\xb7' };
68 QString result = key;
70 for (int i = 0; i < result.size(); ++i) {
71 for (int j = 0; j < NumKnights; ++j) {
72 if (result.at(i) == QLatin1Char(knightsOfTheRoundTable[j])) {
73 result[i] = QLatin1Char(knightsOfTheRoundTable[(j + shift) % NumKnights]).unicode();
74 break;
78 return result;
81 static QCFType<CFStringRef> macKey(const QString &key)
83 return QCFString::toCFStringRef(rotateSlashesDotsAndMiddots(key, Macify));
86 static QString qtKey(CFStringRef cfkey)
88 return rotateSlashesDotsAndMiddots(QCFString::toQString(cfkey), Qtify);
91 static QCFType<CFPropertyListRef> macValue(const QVariant &value);
93 static CFArrayRef macList(const QList<QVariant> &list)
95 int n = list.size();
96 QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(n);
97 for (int i = 0; i < n; ++i)
98 cfvalues[i] = macValue(list.at(i));
99 return CFArrayCreate(kCFAllocatorDefault, reinterpret_cast<const void **>(cfvalues.data()),
100 CFIndex(n), &kCFTypeArrayCallBacks);
103 static QCFType<CFPropertyListRef> macValue(const QVariant &value)
105 CFPropertyListRef result = 0;
107 switch (value.type()) {
108 case QVariant::ByteArray:
110 QByteArray ba = value.toByteArray();
111 result = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(ba.data()),
112 CFIndex(ba.size()));
114 break;
115 // should be same as below (look for LIST)
116 case QVariant::List:
117 case QVariant::StringList:
118 case QVariant::Polygon:
119 result = macList(value.toList());
120 break;
121 case QVariant::Map:
124 QMap<QString, QVariant> is potentially a multimap,
125 whereas CFDictionary is a single-valued map. To allow
126 for multiple values with the same key, we store
127 multiple values in a CFArray. To avoid ambiguities,
128 we also wrap lists in a CFArray singleton.
130 QMap<QString, QVariant> map = value.toMap();
131 QMap<QString, QVariant>::const_iterator i = map.constBegin();
133 int maxUniqueKeys = map.size();
134 int numUniqueKeys = 0;
135 QVarLengthArray<QCFType<CFPropertyListRef> > cfkeys(maxUniqueKeys);
136 QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(maxUniqueKeys);
138 while (i != map.constEnd()) {
139 const QString &key = i.key();
140 QList<QVariant> values;
142 do {
143 values << i.value();
144 ++i;
145 } while (i != map.constEnd() && i.key() == key);
147 bool singleton = (values.count() == 1);
148 if (singleton) {
149 switch (values.first().type()) {
150 // should be same as above (look for LIST)
151 case QVariant::List:
152 case QVariant::StringList:
153 case QVariant::Polygon:
154 singleton = false;
155 default:
160 cfkeys[numUniqueKeys] = QCFString::toCFStringRef(key);
161 cfvalues[numUniqueKeys] = singleton ? macValue(values.first()) : macList(values);
162 ++numUniqueKeys;
165 result = CFDictionaryCreate(kCFAllocatorDefault,
166 reinterpret_cast<const void **>(cfkeys.data()),
167 reinterpret_cast<const void **>(cfvalues.data()),
168 CFIndex(numUniqueKeys),
169 &kCFTypeDictionaryKeyCallBacks,
170 &kCFTypeDictionaryValueCallBacks);
172 break;
173 case QVariant::DateTime:
176 CFDate, unlike QDateTime, doesn't store timezone information.
178 QDateTime dt = value.toDateTime();
179 if (dt.timeSpec() == Qt::LocalTime) {
180 QDateTime reference;
181 reference.setTime_t((uint)kCFAbsoluteTimeIntervalSince1970);
182 result = CFDateCreate(kCFAllocatorDefault, CFAbsoluteTime(reference.secsTo(dt)));
183 } else {
184 goto string_case;
187 break;
188 case QVariant::Bool:
189 result = value.toBool() ? kCFBooleanTrue : kCFBooleanFalse;
190 break;
191 case QVariant::Int:
192 case QVariant::UInt:
194 int n = value.toInt();
195 result = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &n);
197 break;
198 case QVariant::Double:
200 double n = value.toDouble();
201 result = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &n);
203 break;
204 case QVariant::LongLong:
205 case QVariant::ULongLong:
207 qint64 n = value.toLongLong();
208 result = CFNumberCreate(0, kCFNumberLongLongType, &n);
210 break;
211 case QVariant::String:
212 string_case:
213 default:
214 result = QCFString::toCFStringRef(QSettingsPrivate::variantToString(value));
216 return result;
219 static QVariant qtValue(CFPropertyListRef cfvalue)
221 if (!cfvalue)
222 return QVariant();
224 CFTypeID typeId = CFGetTypeID(cfvalue);
227 Sorted grossly from most to least frequent type.
229 if (typeId == CFStringGetTypeID()) {
230 return QSettingsPrivate::stringToVariant(QCFString::toQString(static_cast<CFStringRef>(cfvalue)));
231 } else if (typeId == CFNumberGetTypeID()) {
232 CFNumberRef cfnumber = static_cast<CFNumberRef>(cfvalue);
233 if (CFNumberIsFloatType(cfnumber)) {
234 double d;
235 CFNumberGetValue(cfnumber, kCFNumberDoubleType, &d);
236 return d;
237 } else {
238 int i;
239 qint64 ll;
241 if (CFNumberGetValue(cfnumber, kCFNumberIntType, &i))
242 return i;
243 CFNumberGetValue(cfnumber, kCFNumberLongLongType, &ll);
244 return ll;
246 } else if (typeId == CFArrayGetTypeID()) {
247 CFArrayRef cfarray = static_cast<CFArrayRef>(cfvalue);
248 QList<QVariant> list;
249 CFIndex size = CFArrayGetCount(cfarray);
250 bool metNonString = false;
251 for (CFIndex i = 0; i < size; ++i) {
252 QVariant value = qtValue(CFArrayGetValueAtIndex(cfarray, i));
253 if (value.type() != QVariant::String)
254 metNonString = true;
255 list << value;
257 if (metNonString)
258 return list;
259 else
260 return QVariant(list).toStringList();
261 } else if (typeId == CFBooleanGetTypeID()) {
262 return (bool)CFBooleanGetValue(static_cast<CFBooleanRef>(cfvalue));
263 } else if (typeId == CFDataGetTypeID()) {
264 CFDataRef cfdata = static_cast<CFDataRef>(cfvalue);
265 return QByteArray(reinterpret_cast<const char *>(CFDataGetBytePtr(cfdata)),
266 CFDataGetLength(cfdata));
267 } else if (typeId == CFDictionaryGetTypeID()) {
268 CFDictionaryRef cfdict = static_cast<CFDictionaryRef>(cfvalue);
269 CFTypeID arrayTypeId = CFArrayGetTypeID();
270 int size = (int)CFDictionaryGetCount(cfdict);
271 QVarLengthArray<CFPropertyListRef> keys(size);
272 QVarLengthArray<CFPropertyListRef> values(size);
273 CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data());
275 QMultiMap<QString, QVariant> map;
276 for (int i = 0; i < size; ++i) {
277 QString key = QCFString::toQString(static_cast<CFStringRef>(keys[i]));
279 if (CFGetTypeID(values[i]) == arrayTypeId) {
280 CFArrayRef cfarray = static_cast<CFArrayRef>(values[i]);
281 CFIndex arraySize = CFArrayGetCount(cfarray);
282 for (CFIndex j = arraySize - 1; j >= 0; --j)
283 map.insert(key, qtValue(CFArrayGetValueAtIndex(cfarray, j)));
284 } else {
285 map.insert(key, qtValue(values[i]));
288 return map;
289 } else if (typeId == CFDateGetTypeID()) {
290 QDateTime dt;
291 dt.setTime_t((uint)kCFAbsoluteTimeIntervalSince1970);
292 return dt.addSecs((int)CFDateGetAbsoluteTime(static_cast<CFDateRef>(cfvalue)));
294 return QVariant();
297 static QString comify(const QString &organization)
299 for (int i = organization.size() - 1; i >= 0; --i) {
300 QChar ch = organization.at(i);
301 if (ch == QLatin1Char('.') || ch == QChar(0x3002) || ch == QChar(0xff0e)
302 || ch == QChar(0xff61)) {
303 QString suffix = organization.mid(i + 1).toLower();
304 if (suffix.size() == 2 || suffix == QLatin1String("com")
305 || suffix == QLatin1String("org") || suffix == QLatin1String("net")
306 || suffix == QLatin1String("edu") || suffix == QLatin1String("gov")
307 || suffix == QLatin1String("mil") || suffix == QLatin1String("biz")
308 || suffix == QLatin1String("info") || suffix == QLatin1String("name")
309 || suffix == QLatin1String("pro") || suffix == QLatin1String("aero")
310 || suffix == QLatin1String("coop") || suffix == QLatin1String("museum")) {
311 QString result = organization;
312 result.replace(QLatin1Char('/'), QLatin1Char(' '));
313 return result;
315 break;
317 int uc = ch.unicode();
318 if ((uc < 'a' || uc > 'z') && (uc < 'A' || uc > 'Z'))
319 break;
322 QString domain;
323 for (int i = 0; i < organization.size(); ++i) {
324 QChar ch = organization.at(i);
325 int uc = ch.unicode();
326 if ((uc >= 'a' && uc <= 'z') || (uc >= '0' && uc <= '9')) {
327 domain += ch;
328 } else if (uc >= 'A' && uc <= 'Z') {
329 domain += ch.toLower();
330 } else {
331 domain += QLatin1Char(' ');
334 domain = domain.simplified();
335 domain.replace(QLatin1Char(' '), QLatin1Char('-'));
336 if (!domain.isEmpty())
337 domain.append(QLatin1String(".com"));
338 return domain;
341 class QMacSettingsPrivate : public QSettingsPrivate
343 public:
344 QMacSettingsPrivate(QSettings::Scope scope, const QString &organization,
345 const QString &application);
346 ~QMacSettingsPrivate();
348 void remove(const QString &key);
349 void set(const QString &key, const QVariant &value);
350 bool get(const QString &key, QVariant *value) const;
351 QStringList children(const QString &prefix, ChildSpec spec) const;
352 void clear();
353 void sync();
354 void flush();
355 bool isWritable() const;
356 QString fileName() const;
358 private:
359 struct SearchDomain
361 CFStringRef userName;
362 CFStringRef applicationOrSuiteId;
365 QCFString applicationId;
366 QCFString suiteId;
367 QCFString hostName;
368 SearchDomain domains[6];
369 int numDomains;
372 QMacSettingsPrivate::QMacSettingsPrivate(QSettings::Scope scope, const QString &organization,
373 const QString &application)
374 : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
376 QString javaPackageName;
377 int curPos = 0;
378 int nextDot;
380 QString domainName = comify(organization);
381 if (domainName.isEmpty()) {
382 setStatus(QSettings::AccessError);
383 domainName = QLatin1String("unknown-organization.trolltech.com");
386 while ((nextDot = domainName.indexOf(QLatin1Char('.'), curPos)) != -1) {
387 javaPackageName.prepend(domainName.mid(curPos, nextDot - curPos));
388 javaPackageName.prepend(QLatin1Char('.'));
389 curPos = nextDot + 1;
391 javaPackageName.prepend(domainName.mid(curPos));
392 javaPackageName = javaPackageName.toLower();
393 if (curPos == 0)
394 javaPackageName.prepend(QLatin1String("com."));
395 suiteId = javaPackageName;
397 if (scope == QSettings::SystemScope)
398 spec |= F_System;
400 if (application.isEmpty()) {
401 spec |= F_Organization;
402 } else {
403 javaPackageName += QLatin1Char('.');
404 javaPackageName += application;
405 applicationId = javaPackageName;
408 numDomains = 0;
409 for (int i = (spec & F_System) ? 1 : 0; i < 2; ++i) {
410 for (int j = (spec & F_Organization) ? 1 : 0; j < 3; ++j) {
411 SearchDomain &domain = domains[numDomains++];
412 domain.userName = (i == 0) ? kCFPreferencesCurrentUser : kCFPreferencesAnyUser;
413 if (j == 0)
414 domain.applicationOrSuiteId = applicationId;
415 else if (j == 1)
416 domain.applicationOrSuiteId = suiteId;
417 else
418 domain.applicationOrSuiteId = kCFPreferencesAnyApplication;
422 hostName = (scope == QSettings::SystemScope) ? kCFPreferencesCurrentHost : kCFPreferencesAnyHost;
423 sync();
426 QMacSettingsPrivate::~QMacSettingsPrivate()
430 void QMacSettingsPrivate::remove(const QString &key)
432 QStringList keys = children(key + QLatin1Char('/'), AllKeys);
434 // If i == -1, then delete "key" itself.
435 for (int i = -1; i < keys.size(); ++i) {
436 QString subKey = key;
437 if (i >= 0) {
438 subKey += QLatin1Char('/');
439 subKey += keys.at(i);
441 CFPreferencesSetValue(macKey(subKey), 0, domains[0].applicationOrSuiteId,
442 domains[0].userName, hostName);
446 void QMacSettingsPrivate::set(const QString &key, const QVariant &value)
448 CFPreferencesSetValue(macKey(key), macValue(value), domains[0].applicationOrSuiteId,
449 domains[0].userName, hostName);
452 bool QMacSettingsPrivate::get(const QString &key, QVariant *value) const
454 QCFString k = macKey(key);
455 for (int i = 0; i < numDomains; ++i) {
456 for (int j = 0; j < numHostNames; ++j) {
457 QCFType<CFPropertyListRef> ret =
458 CFPreferencesCopyValue(k, domains[i].applicationOrSuiteId, domains[i].userName,
459 hostNames[j]);
460 if (ret) {
461 if (value)
462 *value = qtValue(ret);
463 return true;
467 if (!fallbacks)
468 break;
470 return false;
473 QStringList QMacSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
475 QMap<QString, QString> result;
476 int startPos = prefix.size();
478 for (int i = 0; i < numDomains; ++i) {
479 for (int j = 0; j < numHostNames; ++j) {
480 QCFType<CFArrayRef> cfarray = CFPreferencesCopyKeyList(domains[i].applicationOrSuiteId,
481 domains[i].userName,
482 hostNames[j]);
483 if (cfarray) {
484 CFIndex size = CFArrayGetCount(cfarray);
485 for (CFIndex k = 0; k < size; ++k) {
486 QString currentKey =
487 qtKey(static_cast<CFStringRef>(CFArrayGetValueAtIndex(cfarray, k)));
488 if (currentKey.startsWith(prefix))
489 processChild(currentKey.mid(startPos), spec, result);
494 if (!fallbacks)
495 break;
497 return result.keys();
500 void QMacSettingsPrivate::clear()
502 QCFType<CFArrayRef> cfarray = CFPreferencesCopyKeyList(domains[0].applicationOrSuiteId,
503 domains[0].userName, hostName);
504 CFPreferencesSetMultiple(0, cfarray, domains[0].applicationOrSuiteId, domains[0].userName,
505 hostName);
508 void QMacSettingsPrivate::sync()
510 for (int i = 0; i < numDomains; ++i) {
511 for (int j = 0; j < numHostNames; ++j) {
512 Boolean ok = CFPreferencesSynchronize(domains[i].applicationOrSuiteId,
513 domains[i].userName, hostNames[j]);
514 // only report failures for the primary file (the one we write to)
515 if (!ok && i == 0 && hostNames[j] == hostName && status == QSettings::NoError) {
516 #if 1
517 // work around what seems to be a bug in CFPreferences:
518 // don't report an error if there are no preferences for the application
519 QCFType<CFArrayRef> appIds = CFPreferencesCopyApplicationList(domains[i].userName,
520 hostNames[j]);
522 // iterate through all the applications and see if we're there
523 CFIndex size = CFArrayGetCount(appIds);
524 for (CFIndex k = 0; k < size; ++k) {
525 const void *cfvalue = CFArrayGetValueAtIndex(appIds, k);
526 if (CFGetTypeID(cfvalue) == CFStringGetTypeID()) {
527 if (CFStringCompare(static_cast<CFStringRef>(cfvalue),
528 domains[i].applicationOrSuiteId,
529 kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
530 setStatus(QSettings::AccessError);
531 break;
535 #else
536 setStatus(QSettings::AccessError);
537 #endif
543 void QMacSettingsPrivate::flush()
545 sync();
548 bool QMacSettingsPrivate::isWritable() const
550 QMacSettingsPrivate *that = const_cast<QMacSettingsPrivate *>(this);
551 QString impossibleKey(QLatin1String("qt_internal/"));
553 QSettings::Status oldStatus = that->status;
554 that->status = QSettings::NoError;
556 that->set(impossibleKey, QVariant());
557 that->sync();
558 bool writable = (status == QSettings::NoError) && that->get(impossibleKey, 0);
559 that->remove(impossibleKey);
560 that->sync();
562 that->status = oldStatus;
563 return writable;
566 QString QMacSettingsPrivate::fileName() const
568 QString result;
569 if ((spec & F_System) == 0)
570 result = QDir::homePath();
571 result += QLatin1String("/Library/Preferences/");
572 result += QCFString::toQString(domains[0].applicationOrSuiteId);
573 result += QLatin1String(".plist");
574 return result;
577 QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format,
578 QSettings::Scope scope,
579 const QString &organization,
580 const QString &application)
582 if (format == QSettings::NativeFormat) {
583 return new QMacSettingsPrivate(scope, organization, application);
584 } else {
585 return new QConfFileSettingsPrivate(format, scope, organization, application);
589 static QCFType<CFURLRef> urlFromFileName(const QString &fileName)
591 return CFURLCreateWithFileSystemPath(kCFAllocatorDefault, QCFString(fileName),
592 kCFURLPOSIXPathStyle, false);
595 bool QConfFileSettingsPrivate::readPlistFile(const QString &fileName, ParsedSettingsMap *map) const
597 QCFType<CFDataRef> resource;
598 SInt32 code;
599 if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, urlFromFileName(fileName),
600 &resource, 0, 0, &code))
601 return false;
603 QCFString errorStr;
604 QCFType<CFPropertyListRef> propertyList =
605 CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource, kCFPropertyListImmutable,
606 &errorStr);
608 if (!propertyList)
609 return true;
610 if (CFGetTypeID(propertyList) != CFDictionaryGetTypeID())
611 return false;
613 CFDictionaryRef cfdict =
614 static_cast<CFDictionaryRef>(static_cast<CFPropertyListRef>(propertyList));
615 int size = (int)CFDictionaryGetCount(cfdict);
616 QVarLengthArray<CFPropertyListRef> keys(size);
617 QVarLengthArray<CFPropertyListRef> values(size);
618 CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data());
620 for (int i = 0; i < size; ++i) {
621 QString key = qtKey(static_cast<CFStringRef>(keys[i]));
622 map->insert(QSettingsKey(key, Qt::CaseSensitive), qtValue(values[i]));
624 return true;
627 bool QConfFileSettingsPrivate::writePlistFile(const QString &fileName,
628 const ParsedSettingsMap &map) const
630 QVarLengthArray<QCFType<CFStringRef> > cfkeys(map.size());
631 QVarLengthArray<QCFType<CFPropertyListRef> > cfvalues(map.size());
632 int i = 0;
633 ParsedSettingsMap::const_iterator j;
634 for (j = map.constBegin(); j != map.constEnd(); ++j) {
635 cfkeys[i] = macKey(j.key());
636 cfvalues[i] = macValue(j.value());
637 ++i;
640 QCFType<CFDictionaryRef> propertyList =
641 CFDictionaryCreate(kCFAllocatorDefault,
642 reinterpret_cast<const void **>(cfkeys.data()),
643 reinterpret_cast<const void **>(cfvalues.data()),
644 CFIndex(map.size()),
645 &kCFTypeDictionaryKeyCallBacks,
646 &kCFTypeDictionaryValueCallBacks);
648 QCFType<CFDataRef> xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault, propertyList);
650 SInt32 code;
651 return CFURLWriteDataAndPropertiesToResource(urlFromFileName(fileName), xmlData, 0, &code);
654 QT_END_NAMESPACE