delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / ktimezoned / ktimezoned.cpp
blob6fd2e819014b62af360fb4a5443757b6028090f6
1 /*
2 This file is part of the KDE libraries
3 Copyright (c) 2005-2007 David Jarvie <djarvie@kde.org>
4 Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include "ktimezoned.moc"
24 #include <climits>
25 #include <cstdlib>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QDir>
30 #include <QRegExp>
31 #include <QStringList>
32 #include <QTextStream>
33 #include <QtDBus/QtDBus>
35 #include <kglobal.h>
36 #include <klocale.h>
37 #include <kcodecs.h>
38 #include <kstandarddirs.h>
39 #include <kstringhandler.h>
40 #include <ktemporaryfile.h>
41 #include <kdebug.h>
42 #include <kconfiggroup.h>
44 #include <kpluginfactory.h>
45 #include <kpluginloader.h>
47 K_PLUGIN_FACTORY(KTimeZonedFactory,
48 registerPlugin<KTimeZoned>();
50 K_EXPORT_PLUGIN(KTimeZonedFactory("ktimezoned"))
53 // Config file entry names
54 const char ZONEINFO_DIR[] = "ZoneinfoDir"; // path to zoneinfo/ directory
55 const char ZONE_TAB[] = "Zonetab"; // path & name of zone.tab
56 const char ZONE_TAB_CACHE[] = "ZonetabCache"; // type of cached simulated zone.tab
57 const char LOCAL_ZONE[] = "LocalZone"; // name of local time zone
60 KTimeZoned::KTimeZoned(QObject* parent, const QList<QVariant>&)
61 : KDEDModule(parent),
62 mSource(0),
63 mZonetabWatch(0),
64 mDirWatch(0)
66 init(false);
69 KTimeZoned::~KTimeZoned()
71 delete mSource;
72 mSource = 0;
73 delete mZonetabWatch;
74 mZonetabWatch = 0;
75 delete mDirWatch;
76 mDirWatch = 0;
79 void KTimeZoned::initialize(bool reinit)
81 // If we reach here, the module has already been constructed and therefore
82 // initialized. So only do anything if reinit is true.
83 if (reinit)
84 init(true);
87 void KTimeZoned::init(bool restart)
89 if (restart)
91 kDebug(1221) << "KTimeZoned::init(restart)";
92 delete mSource;
93 mSource = 0;
94 delete mZonetabWatch;
95 mZonetabWatch = 0;
96 delete mDirWatch;
97 mDirWatch = 0;
100 #ifdef Q_OS_WIN
101 // On Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
102 // holds the time zone database. The TZI binary value is the TIME_ZONE_INFORMATION structure.
103 #else
104 // For Unix, read zone.tab.
105 KConfig config(QLatin1String("ktimezonedrc"));
106 if (restart)
107 config.reparseConfiguration();
108 KConfigGroup group(&config, "TimeZones");
109 mZoneinfoDir = group.readEntry(ZONEINFO_DIR);
110 mZoneTab = group.readEntry(ZONE_TAB);
111 mConfigLocalZone = group.readEntry(LOCAL_ZONE);
112 QString ztc = group.readEntry(ZONE_TAB_CACHE, QString());
113 mZoneTabCache = (ztc == "Solaris") ? Solaris : NoCache;
114 QString oldZoneinfoDir = mZoneinfoDir;
115 QString oldZoneTab = mZoneTab;
116 CacheType oldCacheType = mZoneTabCache;
118 // Open zone.tab if we already know where it is
119 QFile f;
120 if (!mZoneTab.isEmpty() && !mZoneinfoDir.isEmpty())
122 f.setFileName(mZoneTab);
123 if (!f.open(QIODevice::ReadOnly))
124 mZoneTab.clear();
125 else if (mZoneTabCache != NoCache)
127 // Check whether the cached zone.tab is still up to date
128 #ifdef __GNUC__
129 #warning Implement checking whether Solaris cached zone.tab is up to date
130 #endif
134 if (mZoneTab.isEmpty() || mZoneinfoDir.isEmpty())
136 // Search for zone.tab
137 if (!findZoneTab(f))
138 return;
139 mZoneTab = f.fileName();
141 if (mZoneinfoDir != oldZoneinfoDir
142 || mZoneTab != oldZoneTab
143 || mZoneTabCache != oldCacheType)
145 // Update config file and notify interested applications
146 group.writeEntry(ZONEINFO_DIR, mZoneinfoDir);
147 group.writeEntry(ZONE_TAB, mZoneTab);
148 QString ztc;
149 switch (mZoneTabCache)
151 case Solaris: ztc = "Solaris"; break;
152 default: break;
154 group.writeEntry(ZONE_TAB_CACHE, ztc);
155 group.sync();
156 QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "configChanged");
157 QDBusConnection::sessionBus().send(message);
161 // Read zone.tab and create a collection of KTimeZone instances
162 readZoneTab(f);
164 mZonetabWatch = new KDirWatch(this);
165 mZonetabWatch->addFile(mZoneTab);
166 connect(mZonetabWatch, SIGNAL(dirty(const QString&)), SLOT(zonetab_Changed(const QString&)));
167 #endif
169 // Find the local system time zone and set up file monitors to detect changes
170 findLocalZone();
173 // Check if the local zone has been updated, and if so, write the new
174 // zone to the config file and notify interested parties.
175 void KTimeZoned::updateLocalZone()
177 if (mConfigLocalZone != mLocalZone)
179 KConfig config(QLatin1String("ktimezonedrc"));
180 KConfigGroup group(&config, "TimeZones");
181 mConfigLocalZone = mLocalZone;
182 group.writeEntry(LOCAL_ZONE, mConfigLocalZone);
183 group.sync();
185 QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "configChanged");
186 QDBusConnection::sessionBus().send(message);
191 * Find the location of the zoneinfo files and store in mZoneinfoDir.
192 * Open or if necessary create zone.tab.
194 bool KTimeZoned::findZoneTab(QFile& f)
196 #if defined(SOLARIS) || defined(USE_SOLARIS)
197 const QString ZONE_TAB_FILE = QLatin1String("/tab/zone_sun.tab");
198 const QString ZONE_INFO_DIR = QLatin1String("/usr/share/lib/zoneinfo");
199 #else
200 const QString ZONE_TAB_FILE = QLatin1String("/zone.tab");
201 const QString ZONE_INFO_DIR = QLatin1String("/usr/share/zoneinfo");
202 #endif
204 mZoneTabCache = NoCache;
206 // Find and open zone.tab - it's all easy except knowing where to look.
207 // Try the LSB location first.
208 QDir dir;
209 QString zoneinfoDir = ZONE_INFO_DIR;
210 // make a note if the dir exists; whether it contains zone.tab or not
211 if (dir.exists(zoneinfoDir))
213 mZoneinfoDir = zoneinfoDir;
214 f.setFileName(zoneinfoDir + ZONE_TAB_FILE);
215 if (f.open(QIODevice::ReadOnly))
216 return true;
217 kDebug(1221) << "Can't open " << f.fileName();
220 zoneinfoDir = QLatin1String("/usr/lib/zoneinfo");
221 if (dir.exists(zoneinfoDir))
223 mZoneinfoDir = zoneinfoDir;
224 f.setFileName(zoneinfoDir + ZONE_TAB_FILE);
225 if (f.open(QIODevice::ReadOnly))
226 return true;
227 kDebug(1221) << "Can't open " << f.fileName();
230 zoneinfoDir = ::getenv("TZDIR");
231 if (!zoneinfoDir.isEmpty() && dir.exists(zoneinfoDir))
233 mZoneinfoDir = zoneinfoDir;
234 f.setFileName(zoneinfoDir + ZONE_TAB_FILE);
235 if (f.open(QIODevice::ReadOnly))
236 return true;
237 kDebug(1221) << "Can't open " << f.fileName();
240 zoneinfoDir = QLatin1String("/usr/share/lib/zoneinfo");
241 if (dir.exists(zoneinfoDir + QLatin1String("/src")))
243 mZoneinfoDir = zoneinfoDir;
244 // Solaris support. Synthesise something that looks like a zone.tab,
245 // and cache it between sessions.
247 // grep -h ^Zone /usr/share/lib/zoneinfo/src/* | awk '{print "??\t+9999+99999\t" $2}'
249 // where the country code is set to "??" and the latitude/longitude
250 // values are dummies.
252 QDir d(mZoneinfoDir + QLatin1String("/src"));
253 d.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
254 QStringList fileList = d.entryList();
256 mZoneTab = KStandardDirs::locateLocal("cache", QLatin1String("zone.tab"));
257 f.setFileName(mZoneTab);
258 if (!f.open(QIODevice::WriteOnly))
260 kError(1221) << "Could not create zone.tab cache" << endl;
261 return false;
264 QFile zoneFile;
265 QList<QByteArray> tokens;
266 QByteArray line;
267 line.reserve(1024);
268 QTextStream tmpStream(&f);
269 qint64 r;
270 for (int i = 0, end = fileList.count(); i < end; ++i)
272 zoneFile.setFileName(d.filePath(fileList[i].toLatin1()));
273 if (!zoneFile.open(QIODevice::ReadOnly))
275 kDebug(1221) << "Could not open file '" << zoneFile.fileName().toLatin1() \
276 << "' for reading." << endl;
277 continue;
279 while (!zoneFile.atEnd())
281 if ((r = zoneFile.readLine(line.data(), 1023)) > 0
282 && line.startsWith("Zone"))
284 line.replace('\t', ' '); // change tabs to spaces
285 tokens = line.split(' ');
286 for (int j = 0, jend = tokens.count(); j < jend; ++j)
287 if (tokens[j].endsWith(' '))
288 tokens[j].chop(1);
289 tmpStream << "??\t+9999+99999\t" << tokens[1] << "\n";
292 zoneFile.close();
294 f.close();
295 if (!f.open(QIODevice::ReadOnly))
297 kError(1221) << "Could not reopen zone.tab cache file for reading." << endl;
298 return false;
300 mZoneTabCache = Solaris;
301 return true;
303 return false;
306 // Parse zone.tab and for each time zone, create a KSystemTimeZone instance.
307 // Note that only data needed by this module is specified to KSystemTimeZone.
308 void KTimeZoned::readZoneTab(QFile &f)
310 // Parse the already open real or fake zone.tab.
311 QRegExp lineSeparator("[ \t]");
312 if (!mSource)
313 mSource = new KSystemTimeZoneSource;
314 mZones.clear();
315 QTextStream str(&f);
316 while (!str.atEnd())
318 QString line = str.readLine();
319 if (line.isEmpty() || line[0] == '#')
320 continue;
321 QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
322 int n = tokens.count();
323 if (n < 3)
325 kError(1221) << "readZoneTab(): invalid record: " << line << endl;
326 continue;
329 // Add entry to list.
330 if (tokens[0] == "??")
331 tokens[0] = "";
332 else if (!tokens[0].isEmpty())
333 mHaveCountryCodes = true;
334 mZones.add(KSystemTimeZone(mSource, tokens[2], tokens[0]));
336 f.close();
339 // Find the local time zone, starting from scratch.
340 void KTimeZoned::findLocalZone()
342 delete mDirWatch;
343 mDirWatch = 0;
344 mLocalZone.clear();
345 mLocalIdFile.clear();
346 mLocalZoneDataFile.clear();
348 // SOLUTION 1: DEFINITIVE.
349 // First try the simplest solution of checking for well-formed TZ setting.
350 const char *envtz = ::getenv("TZ");
351 if (checkTZ(envtz))
353 mSavedTZ = envtz;
354 if (!mLocalZone.isEmpty()) kDebug(1221)<<"TZ: "<<mLocalZone;
357 if (mLocalZone.isEmpty())
359 // SOLUTION 2: DEFINITIVE.
360 // BSD & Linux support: local time zone id in /etc/timezone.
361 checkTimezone();
362 if (!mLocalZone.isEmpty()) kDebug(1221)<<"/etc/timezone: "<<mLocalZone;
364 if (mLocalZone.isEmpty() && !mZoneinfoDir.isEmpty())
366 // SOLUTION 3: DEFINITIVE.
367 // Try to follow any /etc/localtime symlink to a zoneinfo file.
368 // SOLUTION 4: DEFINITIVE.
369 // Try to match /etc/localtime against the list of zoneinfo files.
370 matchZoneFile(QLatin1String("/etc/localtime"));
371 if (!mLocalZone.isEmpty()) kDebug(1221)<<"/etc/localtime: "<<mLocalZone;
373 if (mLocalZone.isEmpty())
375 // SOLUTION 5: DEFINITIVE.
376 // Solaris support using /etc/default/init.
377 checkDefaultInit();
378 if (!mLocalZone.isEmpty()) kDebug(1221)<<"/etc/default/init: "<<mLocalZone;
381 if (!mLocalZone.isEmpty())
383 // The local time zone is defined by a file.
384 // Watch for changes in the file so as to be notified of any change
385 // in local time zone.
386 mDirWatch = new KDirWatch(this);
387 mDirWatch->addFile(mLocalIdFile);
388 if (!mLocalZoneDataFile.isEmpty())
389 mDirWatch->addFile(mLocalZoneDataFile);
390 connect(mDirWatch, SIGNAL(dirty(const QString&)), SLOT(localChanged(const QString&)));
392 else if (!mZoneinfoDir.isEmpty())
394 // SOLUTION 6: HEURISTIC.
395 // None of the deterministic stuff above has worked: try a heuristic. We
396 // try to find a pair of matching time zone abbreviations...that way, we'll
397 // likely return a value in the user's own country.
398 tzset();
399 QByteArray tzname0(tzname[0]); // store copies, because zone.parse() will change them
400 QByteArray tzname1(tzname[1]);
401 int bestOffset = INT_MAX;
402 KSystemTimeZoneSource::startParseBlock();
403 const KTimeZones::ZoneMap zmap = mZones.zones();
404 for (KTimeZones::ZoneMap::ConstIterator it = zmap.constBegin(), end = zmap.constEnd(); it != end; ++it)
406 KTimeZone zone = it.value();
407 int candidateOffset = qAbs(zone.currentOffset(Qt::LocalTime));
408 if (candidateOffset < bestOffset
409 && zone.parse())
411 QList<QByteArray> abbrs = zone.abbreviations();
412 if (abbrs.contains(tzname0) && abbrs.contains(tzname1))
414 // kDebug(1221) << "local=" << zone.name();
415 mLocalZone = zone.name();
416 bestOffset = candidateOffset;
417 if (!bestOffset)
418 break;
422 KSystemTimeZoneSource::endParseBlock();
423 if (!mLocalZone.isEmpty())
424 mLocalMethod = TzName;
425 if (!mLocalZone.isEmpty()) kDebug(1221)<<"tzname: "<<mLocalZone;
427 if (mLocalZone.isEmpty())
429 // SOLUTION 7: FAILSAFE.
430 mLocalZone = KTimeZone::utc().name();
431 mLocalMethod = Utc;
432 if (!mLocalZone.isEmpty()) kDebug(1221)<<"Failsafe: "<<mLocalZone;
435 // Finally, if the local zone identity has changed, store
436 // the new one in the config file.
437 updateLocalZone();
440 // Called when KDirWatch detects a change in zone.tab
441 void KTimeZoned::zonetab_Changed(const QString& path)
443 kDebug(1221) << "zone.tab changed";
444 if (path != mZoneTab)
446 kError(1221) << "Wrong path (" << path << ") for zone.tab";
447 return;
449 QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "zonetabChanged");
450 QList<QVariant> args;
451 args += mZoneTab;
452 message.setArguments(args);
453 QDBusConnection::sessionBus().send(message);
455 // Reread zone.tab and recreate the collection of KTimeZone instances,
456 // in case any zones have been created or deleted and one of them
457 // subsequently becomes the local zone.
458 QFile f;
459 f.setFileName(mZoneTab);
460 if (!f.open(QIODevice::ReadOnly))
461 kError(1221) << "Could not open zone.tab (" << mZoneTab << ") to reread";
462 else
463 readZoneTab(f);
466 // Called when KDirWatch detects a change
467 void KTimeZoned::localChanged(const QString& path)
469 if (path == mLocalZoneDataFile)
471 // Only need to update the definition of the local zone,
472 // not its identity.
473 QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "zoneDefinitionChanged");
474 QList<QVariant> args;
475 args += mLocalZone;
476 message.setArguments(args);
477 QDBusConnection::sessionBus().send(message);
478 return;
480 QString oldDataFile = mLocalZoneDataFile;
481 switch (mLocalMethod)
483 case EnvTzLink:
484 case EnvTzFile:
486 const char *envtz = ::getenv("TZ");
487 if (mSavedTZ != envtz)
489 // TZ has changed - start from scratch again
490 findLocalZone();
491 return;
493 // The contents of the file pointed to by TZ has changed.
495 // Fall through to LocaltimeLink
496 case LocaltimeLink:
497 case LocaltimeCopy:
498 matchZoneFile(mLocalIdFile);
499 break;
500 case Timezone:
501 checkTimezone();
502 break;
503 case DefaultInit:
504 checkDefaultInit();
505 break;
506 default:
507 return;
509 if (oldDataFile != mLocalZoneDataFile)
511 if (!oldDataFile.isEmpty())
512 mDirWatch->removeFile(oldDataFile);
513 if (!mLocalZoneDataFile.isEmpty())
514 mDirWatch->addFile(mLocalZoneDataFile);
516 updateLocalZone();
519 bool KTimeZoned::checkTZ(const char *envZone)
521 // SOLUTION 1: DEFINITIVE.
522 // First try the simplest solution of checking for well-formed TZ setting.
523 if (envZone)
525 if (envZone[0] == '\0')
527 mLocalMethod = EnvTz;
528 mLocalZone = KTimeZone::utc().name();
529 mLocalIdFile.clear();
530 mLocalZoneDataFile.clear();
531 return true;
533 if (envZone[0] == ':')
535 // TZ specifies a file name, either relative to zoneinfo/ or absolute.
536 QString TZfile = QFile::decodeName(envZone + 1);
537 if (TZfile.startsWith(mZoneinfoDir))
539 // It's an absolute file name in the zoneinfo directory.
540 // Convert it to a file name relative to zoneinfo/.
541 TZfile = TZfile.mid(mZoneinfoDir.length());
543 if (TZfile.startsWith(QLatin1Char('/')))
545 // It's an absolute file name.
546 QString symlink;
547 if (matchZoneFile(TZfile))
549 mLocalMethod = static_cast<LocalMethod>(EnvTz | (mLocalMethod & TypeMask));
550 return true;
553 else if (!TZfile.isEmpty())
555 // It's a file name relative to zoneinfo/
556 mLocalZone = TZfile;
557 if (!mLocalZone.isEmpty())
559 mLocalMethod = EnvTz;
560 mLocalZoneDataFile = mZoneinfoDir + '/' + TZfile;
561 mLocalIdFile.clear();
562 return true;
567 return false;
570 bool KTimeZoned::checkTimezone()
572 // SOLUTION 2: DEFINITIVE.
573 // BSD support.
574 kDebug(1221)<<"checkTimezone()";
575 QFile f;
576 f.setFileName(QLatin1String("/etc/timezone"));
577 if (!f.open(QIODevice::ReadOnly))
578 return false;
579 kDebug(1221)<<"checkTimezone(): /etc/timezone opened";
580 // Read the first line of the file.
581 QTextStream ts(&f);
582 ts.setCodec("ISO-8859-1");
583 QString zoneName;
584 if (!ts.atEnd())
585 zoneName = ts.readLine();
586 f.close();
587 if (!zoneName.isEmpty())
589 KTimeZone local = mZones.zone(zoneName);
590 kDebug(1221)<<"checkTimezone(): local="<<local.isValid()<<", name="<<zoneName;
591 if (local.isValid())
593 mLocalZone = zoneName;
594 mLocalMethod = Timezone;
595 mLocalIdFile = f.fileName();
596 mLocalZoneDataFile = mZoneinfoDir.isEmpty() ? QString() : mZoneinfoDir + '/' + zoneName;
597 return true;
600 return false;
603 bool KTimeZoned::matchZoneFile(const QString &path)
605 // SOLUTION 3: DEFINITIVE.
606 // Try to follow any symlink to a zoneinfo file.
607 // Get the path of the file which the symlink points to.
608 QFile f;
609 f.setFileName(path);
610 QFileInfo fi(f);
611 if (fi.isSymLink())
613 // The file is a symlink.
614 QString zoneInfoFileName = fi.canonicalFilePath();
615 QFileInfo fiz(zoneInfoFileName);
616 if (fiz.exists() && fiz.isReadable())
618 if (zoneInfoFileName.startsWith(mZoneinfoDir))
620 // We've got the zoneinfo file path.
621 // The time zone name is the part of the path after the zoneinfo directory.
622 QString name = zoneInfoFileName.mid(mZoneinfoDir.length() + 1);
623 // kDebug(1221) << "local=" << name;
624 KTimeZone local = mZones.zone(name);
625 if (!local.isValid())
626 return false;
627 mLocalZone = name;
629 else
631 // It isn't a zoneinfo file or a copy thereof.
632 // Use the absolute path as the time zone name.
633 mLocalZone = f.fileName();
635 mLocalMethod = LocaltimeLink;
636 mLocalIdFile = f.fileName();
637 mLocalZoneDataFile = zoneInfoFileName;
638 return true;
641 else if (f.open(QIODevice::ReadOnly))
643 // SOLUTION 4: DEFINITIVE.
644 // Try to match the file against the list of zoneinfo files.
646 // Compute the file's MD5 sum.
647 KMD5 context("");
648 context.reset();
649 context.update(f);
650 qlonglong referenceSize = f.size();
651 QString referenceMd5Sum = context.hexDigest();
652 MD5Map::ConstIterator it5, end5;
653 KTimeZone local;
654 QString zoneName;
656 if (!mConfigLocalZone.isEmpty())
658 // We know the local zone from last time.
659 // Check whether the file still matches it.
660 KTimeZone tzone = mZones.zone(mConfigLocalZone);
661 if (tzone.isValid())
663 local = compareChecksum(tzone, referenceMd5Sum, referenceSize);
664 if (local.isValid())
665 zoneName = local.name();
669 if (!local.isValid() && mHaveCountryCodes)
671 /* Look for time zones with the user's country code.
672 * This has two advantages: 1) it shortens the search;
673 * 2) it increases the chance of the correctly titled time zone
674 * being found, since multiple time zones can have identical
675 * definitions. For example, Europe/Guernsey is identical to
676 * Europe/London, but the latter is more likely to be the right
677 * zone name for a user with 'gb' country code.
679 QString country = KGlobal::locale()->country().toUpper();
680 const KTimeZones::ZoneMap zmap = mZones.zones();
681 for (KTimeZones::ZoneMap::ConstIterator zit = zmap.constBegin(), zend = zmap.constEnd(); zit != zend; ++zit)
683 KTimeZone tzone = zit.value();
684 if (tzone.countryCode() == country)
686 local = compareChecksum(tzone, referenceMd5Sum, referenceSize);
687 if (local.isValid())
689 zoneName = local.name();
690 break;
696 if (!local.isValid())
698 // Look for a checksum match with the cached checksum values
699 MD5Map oldChecksums = mMd5Sums; // save a copy of the existing checksums
700 for (it5 = mMd5Sums.constBegin(), end5 = mMd5Sums.constEnd(); it5 != end5; ++it5)
702 if (it5.value() == referenceMd5Sum)
704 // The cached checksum matches. Ensure that the file hasn't changed.
705 if (compareChecksum(it5, referenceMd5Sum, referenceSize))
707 zoneName = it5.key();
708 local = mZones.zone(zoneName);
709 if (local.isValid())
710 break;
712 oldChecksums.clear(); // the cache has been cleared
713 break;
717 if (!local.isValid())
719 // The checksum didn't match any in the cache.
720 // Continue building missing entries in the cache on the assumption that
721 // we haven't previously looked at the zoneinfo file which matches.
722 const KTimeZones::ZoneMap zmap = mZones.zones();
723 for (KTimeZones::ZoneMap::ConstIterator zit = zmap.constBegin(), zend = zmap.constEnd(); zit != zend; ++zit)
725 KTimeZone zone = zit.value();
726 zoneName = zone.name();
727 if (!mMd5Sums.contains(zoneName))
729 QString candidateMd5Sum = calcChecksum(zoneName, referenceSize);
730 if (candidateMd5Sum == referenceMd5Sum)
732 // kDebug(1221) << "local=" << zone.name();
733 local = zone;
734 break;
740 if (!local.isValid())
742 // Didn't find the file, so presumably a previously cached checksum must
743 // have changed. Delete all the old checksums.
744 MD5Map::ConstIterator mit;
745 MD5Map::ConstIterator mend = oldChecksums.constEnd();
746 for (mit = oldChecksums.constBegin(); mit != mend; ++mit)
747 mMd5Sums.remove(mit.key());
749 // And recalculate the old checksums
750 for (mit = oldChecksums.constBegin(); mit != mend; ++mit)
752 zoneName = mit.key();
753 QString candidateMd5Sum = calcChecksum(zoneName, referenceSize);
754 if (candidateMd5Sum == referenceMd5Sum)
756 // kDebug(1221) << "local=" << zoneName;
757 local = mZones.zone(zoneName);
758 break;
763 bool success = false;
764 if (local.isValid())
766 // The file matches a zoneinfo file
767 mLocalZone = zoneName;
768 mLocalZoneDataFile = mZoneinfoDir + '/' + zoneName;
769 success = true;
771 else
773 // The file doesn't match a zoneinfo file. If it's a TZfile, use it directly.
774 // Read the file type identifier.
775 char buff[4];
776 f.reset();
777 QDataStream str(&f);
778 if (str.readRawData(buff, 4) == 4
779 && buff[0] == 'T' && buff[1] == 'Z' && buff[2] == 'i' && buff[3] == 'f')
781 // Use its absolute path as the zone name.
782 mLocalZone = f.fileName();
783 mLocalZoneDataFile.clear();
784 success = true;
787 f.close();
788 if (success)
790 mLocalMethod = LocaltimeCopy;
791 mLocalIdFile = f.fileName();
792 return true;
795 return false;
798 bool KTimeZoned::checkDefaultInit()
800 // SOLUTION 5: DEFINITIVE.
801 // Solaris support using /etc/default/init.
802 QFile f;
803 f.setFileName(QLatin1String("/etc/default/init"));
804 if (!f.open(QIODevice::ReadOnly))
805 return false;
806 // Read the last line starting "TZ=".
807 QString zoneName;
808 QTextStream ts(&f);
809 ts.setCodec("ISO-8859-1");
810 while (!ts.atEnd())
812 zoneName = ts.readLine();
813 if (zoneName.startsWith("TZ="))
815 zoneName = zoneName.mid(3);
816 // kDebug(1221) << "local=" << zoneName;
817 break;
820 f.close();
821 KTimeZone local = mZones.zone(zoneName);
822 if (!local.isValid())
823 return false;
824 mLocalZone = zoneName;
825 mLocalMethod = DefaultInit;
826 mLocalIdFile = f.fileName();
827 mLocalZoneDataFile = mZoneinfoDir.isEmpty() ? QString() : mZoneinfoDir + '/' + zoneName;
828 return true;
831 // Check whether the checksum for a time zone matches a given saved checksum.
832 KTimeZone KTimeZoned::compareChecksum(const KTimeZone &zone, const QString &referenceMd5Sum, qlonglong size)
834 MD5Map::ConstIterator it5 = mMd5Sums.constFind(zone.name());
835 if (it5 == mMd5Sums.constEnd())
837 // No checksum has been computed yet for this zone file.
838 // Compute it now.
839 QString candidateMd5Sum = calcChecksum(zone.name(), size);
840 if (candidateMd5Sum == referenceMd5Sum)
842 // kDebug(1221) << "local=" << zone.name();
843 return zone;
845 return KTimeZone();
847 if (it5.value() == referenceMd5Sum)
849 // The cached checksum matches. Ensure that the file hasn't changed.
850 if (compareChecksum(it5, referenceMd5Sum, size))
851 return mZones.zone(it5.key());
853 return KTimeZone();
856 // Check whether a checksum matches a given saved checksum.
857 // Returns false if the file no longer matches and cache was cleared.
858 bool KTimeZoned::compareChecksum(MD5Map::ConstIterator it5, const QString &referenceMd5Sum, qlonglong size)
860 // The cached checksum matches. Ensure that the file hasn't changed.
861 QString zoneName = it5.key();
862 QString candidateMd5Sum = calcChecksum(zoneName, size);
863 if (candidateMd5Sum.isNull())
864 mMd5Sums.remove(zoneName); // no match - wrong file size
865 else if (candidateMd5Sum == referenceMd5Sum)
866 return true;
868 // File(s) have changed, so clear the cache
869 mMd5Sums.clear();
870 mMd5Sums[zoneName] = candidateMd5Sum; // reinsert the newly calculated checksum
871 return false;
874 // Calculate the MD5 checksum for the given zone file, provided that its size matches.
875 // The calculated checksum is cached.
876 QString KTimeZoned::calcChecksum(const QString &zoneName, qlonglong size)
878 QString path = mZoneinfoDir + '/' + zoneName;
879 QFileInfo fi(path);
880 if (static_cast<qlonglong>(fi.size()) == size)
882 // Only do the heavy lifting for file sizes which match.
883 QFile f;
884 f.setFileName(path);
885 if (f.open(QIODevice::ReadOnly))
887 KMD5 context("");
888 context.reset();
889 context.update(f);
890 QString candidateMd5Sum = context.hexDigest();
891 f.close();
892 mMd5Sums[zoneName] = candidateMd5Sum; // cache the new checksum
893 return candidateMd5Sum;
896 return QString();