QBBSystemLocale: Do not set fixed buffer size when reading pps objects
[qt:qt.git] / src / corelib / tools / qlocale_blackberry.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qlocale_blackberry.h"
43 #include "qlocale_p.h"
44
45 #include "qdatetime.h"
46
47 #include "qcoreapplication.h"
48 #include "private/qcore_unix_p.h"
49
50 #include <errno.h>
51 #include <sys/pps.h>
52 #include <unistd.h>
53
54 QT_BEGIN_NAMESPACE
55
56 #ifndef QT_NO_SYSTEMLOCALE
57
58 static const char ppsUomPath[] = "/pps/services/locale/uom";
59 static const char ppsRegionLocalePath[] = "/pps/services/locale/settings";
60 static const char ppsLanguageLocalePath[] = "/pps/services/confstr/_CS_LOCALE";
61 static const char ppsHourFormatPath[] = "/pps/system/settings";
62
63 static const int MAX_PPS_SIZE = 16000;
64
65 QBBSystemLocaleData::QBBSystemLocaleData()
66     : languageNotifier(0)
67     , regionNotifier(0)
68     , measurementNotifier(0)
69     , hourNotifier(0)
70 {
71     // Do not use qWarning to log warnings if qt_safe_open fails to open the pps file
72     // since the user code may install a message handler that invokes QLocale API again
73     // (i.e QDate, QDateTime, ...) which will cause an infinite loop.
74     if ((measurementFd = qt_safe_open(ppsUomPath, O_RDONLY)) == -1)
75         fprintf(stderr, "Failed to open uom pps, errno=%d\n", errno);
76
77     if ((regionFd = qt_safe_open(ppsRegionLocalePath, O_RDONLY)) == -1)
78         fprintf(stderr, "Failed to open region pps, errno=%d\n", errno);
79
80     if ((languageFd = qt_safe_open(ppsLanguageLocalePath, O_RDONLY)) == -1)
81         fprintf(stderr, "Failed to open language pps, errno=%d\n", errno);
82
83     if ((hourFd = qt_safe_open(ppsHourFormatPath, O_RDONLY)) == -1)
84        fprintf(stderr, "Failed to open hour format pps, errno=%d\n", errno);
85
86     // we cannot call this directly, because by the time this constructor is
87     // called, the event dispatcher has not yet been created, causing the
88     // subsequent call to QSocketNotifier constructor to fail.
89     QMetaObject::invokeMethod(this, "installSocketNotifiers", Qt::QueuedConnection);
90
91     readLangageLocale();
92     readRegionLocale();
93     readMeasurementSystem();
94     readHourFormat();
95 }
96
97 QBBSystemLocaleData::~QBBSystemLocaleData()
98 {
99     if (measurementFd != -1)
100         qt_safe_close(measurementFd);
101
102     if (languageFd != -1)
103         qt_safe_close(languageFd);
104
105     if (regionFd != -1)
106         qt_safe_close(regionFd);
107
108     if (hourFd != -1)
109         qt_safe_close(hourFd);
110 }
111
112 uint QBBSystemLocaleData::measurementSystem()
113 {
114     return m_measurementSystem;
115 }
116
117 QVariant QBBSystemLocaleData::timeFormat(QLocale::FormatType formatType)
118 {
119     return getCorrectFormat(regionLocale().timeFormat(formatType), formatType);
120 }
121
122 QVariant QBBSystemLocaleData::dateTimeFormat(QLocale::FormatType formatType)
123 {
124     return getCorrectFormat(regionLocale().dateTimeFormat(formatType), formatType);
125 }
126
127 QLocale QBBSystemLocaleData::languageLocale()
128 {
129     if (!lc_langage.isEmpty())
130         return QLocale(QLatin1String(lc_langage));
131
132     return QLocale::c();
133 }
134
135 QLocale QBBSystemLocaleData::regionLocale()
136 {
137     if (!lc_region.isEmpty())
138         return QLocale(QLatin1String(lc_region));
139
140     return QLocale::c();
141 }
142
143 void QBBSystemLocaleData::installSocketNotifiers()
144 {
145     Q_ASSERT(!languageNotifier || !regionNotifier || !measurementNotifier || !hourNotifier);
146     Q_ASSERT(QCoreApplication::instance());
147
148     languageNotifier = new QSocketNotifier(languageFd, QSocketNotifier::Read, this);
149     QObject::connect(languageNotifier, SIGNAL(activated(int)), this, SLOT(readLangageLocale()));
150
151     regionNotifier = new QSocketNotifier(regionFd, QSocketNotifier::Read, this);
152     QObject::connect(regionNotifier, SIGNAL(activated(int)), this, SLOT(readRegionLocale()));
153
154     measurementNotifier = new QSocketNotifier(measurementFd, QSocketNotifier::Read, this);
155     QObject::connect(measurementNotifier, SIGNAL(activated(int)), this, SLOT(readMeasurementSystem()));
156
157     hourNotifier = new QSocketNotifier(hourFd, QSocketNotifier::Read, this);
158     QObject::connect(hourNotifier, SIGNAL(activated(int)), this, SLOT(readHourFormat()));
159 }
160
161 void QBBSystemLocaleData::readLangageLocale()
162 {
163     lc_langage = readPpsValue("_CS_LOCALE", languageFd);
164 }
165
166 void QBBSystemLocaleData::readRegionLocale()
167 {
168     lc_region = readPpsValue("region", regionFd);
169 }
170
171 void QBBSystemLocaleData::readMeasurementSystem()
172 {
173     QByteArray measurement = readPpsValue("uom", measurementFd);
174     m_measurementSystem = (qstrcmp(measurement, "imperial") == 0) ? QLocale::ImperialSystem : QLocale::MetricSystem;
175 }
176
177 void QBBSystemLocaleData::readHourFormat()
178 {
179     QByteArray hourFormat = readPpsValue("hourFormat", hourFd);
180     is24HourFormat = (qstrcmp(hourFormat, "24") == 0);
181 }
182
183 QByteArray QBBSystemLocaleData::readPpsValue(const char *ppsObject, int ppsFd)
184 {
185     QByteArray result;
186     if (!ppsObject || ppsFd == -1)
187         return result;
188
189
190     // PPS objects are of unknown size, but must be read all at once.
191     // Relying on the file size may not be a good idea since the size may change before reading.
192     // Let's try with an initial size (512), and if the buffer is too small try with bigger one,
193     // until we succeed or until other non buffer-size-related error occurs.
194     // Using QVarLengthArray means the first try (of size == 512) uses a buffer on the stack - no allocation necessary.
195      // Hopefully that covers most use cases.
196     int bytes;
197     QVarLengthArray<char, 512> buffer;
198     for (;;) {
199         errno = 0;
200         bytes = qt_safe_read(ppsFd, buffer.data(), buffer.capacity() - 1);
201         const bool bufferIsTooSmall = (bytes == -1 && errno == EMSGSIZE && buffer.capacity() < MAX_PPS_SIZE);
202         if (!bufferIsTooSmall)
203             break;
204
205         buffer.resize(qMin(buffer.capacity()*2, MAX_PPS_SIZE));
206     }
207
208     // This method is called in the ctor(), so do not use qWarning to log warnings
209     // if qt_safe_read fails to read the pps file
210     // since the user code may install a message handler that invokes QLocale API again
211     // (i.e QDate, QDateTime, ...) which will cause a infinite loop.
212     if (bytes == -1) {
213         fprintf(stderr, "Failed to read pps object:%s, errno=%d\n", ppsObject, errno);
214         return result;
215     }
216     // ensure data is null terminated
217     buffer[bytes] = '\0';
218
219     pps_decoder_t ppsDecoder;
220     pps_decoder_initialize(&ppsDecoder, 0);
221     if (pps_decoder_parse_pps_str(&ppsDecoder, buffer.data()) == PPS_DECODER_OK) {
222         pps_decoder_push(&ppsDecoder, 0);
223         const char *ppsBuff;
224         if (pps_decoder_get_string(&ppsDecoder, ppsObject, &ppsBuff) == PPS_DECODER_OK) {
225             result = ppsBuff;
226         } else {
227             int val;
228             if (pps_decoder_get_int(&ppsDecoder, ppsObject, &val) == PPS_DECODER_OK)
229                 result = QByteArray::number(val);
230         }
231     }
232
233     pps_decoder_cleanup(&ppsDecoder);
234
235     return result;
236 }
237
238 QString QBBSystemLocaleData::getCorrectFormat(const QString &baseFormat, QLocale::FormatType formatType)
239 {
240     QString format = baseFormat;
241     if (is24HourFormat) {
242         if (format.contains(QLatin1String("AP"), Qt::CaseInsensitive)) {
243             format.replace(QLatin1String("AP"), QLatin1String(""), Qt::CaseInsensitive);
244             format.replace(QLatin1String("h"), QLatin1String("H"), Qt::CaseSensitive);
245         }
246
247     } else {
248
249         if (!format.contains(QLatin1String("AP"), Qt::CaseInsensitive)) {
250             format.contains(QLatin1String("HH"), Qt::CaseSensitive) ?
251                 format.replace(QLatin1String("HH"), QLatin1String("hh"), Qt::CaseSensitive) :
252                 format.replace(QLatin1String("H"), QLatin1String("h"), Qt::CaseSensitive);
253
254             formatType == QLocale::LongFormat ? format.append(QLatin1String(" AP t")) : format.append(QLatin1String(" AP"));
255         }
256     }
257
258     return format;
259 }
260
261 Q_GLOBAL_STATIC(QBBSystemLocaleData, bbSysLocaleData)
262
263 QLocale QSystemLocale::fallbackLocale() const
264 {
265     return bbSysLocaleData()->languageLocale();
266 }
267
268 QVariant QSystemLocale::query(QueryType type, QVariant in) const
269 {
270     QBBSystemLocaleData *d = bbSysLocaleData();
271
272     QReadLocker locker(&d->lock);
273
274     const QLocale &lc_language = d->languageLocale();
275     const QLocale &lc_region = d->regionLocale();
276
277     switch (type) {
278     case DecimalPoint:
279         return lc_region.decimalPoint();
280     case GroupSeparator:
281         return lc_region.groupSeparator();
282     case NegativeSign:
283         return lc_region.negativeSign();
284     case PositiveSign:
285         return lc_region.positiveSign();
286     case DateFormatLong:
287         return lc_region.dateFormat(QLocale::LongFormat);
288     case DateFormatShort:
289         return lc_region.dateFormat(QLocale::ShortFormat);
290     case TimeFormatLong:
291         return d->timeFormat(QLocale::LongFormat);
292     case TimeFormatShort:
293         return d->timeFormat(QLocale::ShortFormat);
294     case DateTimeFormatLong:
295         return d->dateTimeFormat(QLocale::LongFormat);
296     case DateTimeFormatShort:
297         return d->dateTimeFormat(QLocale::ShortFormat);
298     case DayNameLong:
299         return lc_language.dayName(in.toInt(), QLocale::LongFormat);
300     case DayNameShort:
301         return lc_language.dayName(in.toInt(), QLocale::ShortFormat);
302     case MonthNameLong:
303         return lc_language.monthName(in.toInt(), QLocale::LongFormat);
304     case MonthNameShort:
305         return lc_language.monthName(in.toInt(), QLocale::ShortFormat);
306     case StandaloneMonthNameLong:
307          return lc_language.standaloneMonthName(in.toInt(), QLocale::LongFormat);
308     case StandaloneMonthNameShort:
309          return lc_language.standaloneMonthName(in.toInt(), QLocale::ShortFormat);
310     case DateToStringLong:
311         return lc_region.toString(in.toDate(), QLocale::LongFormat);
312     case DateToStringShort:
313         return lc_region.toString(in.toDate(), QLocale::ShortFormat);
314     case TimeToStringLong:
315         return lc_region.toString(in.toTime(), d->timeFormat(QLocale::LongFormat).toString());
316     case TimeToStringShort:
317         return lc_region.toString(in.toTime(), d->timeFormat(QLocale::ShortFormat).toString());
318     case DateTimeToStringShort:
319         return lc_region.toString(in.toDateTime(), d->dateTimeFormat(QLocale::ShortFormat).toString());
320     case DateTimeToStringLong:
321         return lc_region.toString(in.toDateTime(), d->dateTimeFormat(QLocale::LongFormat).toString());
322     case MeasurementSystem:
323         return d->measurementSystem();
324     case ZeroDigit:
325         return lc_region.zeroDigit();
326     case CountryId:
327         return lc_region.country();
328     case LanguageId:
329         return lc_language.language();
330     case AMText:
331         return lc_language.amText();
332     case PMText:
333         return lc_language.pmText();
334     default:
335         break;
336     }
337     return QVariant();
338 }
339
340 #endif
341
342 QT_END_NAMESPACE