1 /* This file is part of the KDE project
2 Copyright (C) 2007 Matthias Kretz <kretz@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 or version 3 as published by the Free Software
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "hardwaredatabase.h"
22 #include <kcomponentdata.h>
25 #include <kconfiggroup.h>
27 #include <ksavefile.h>
28 #include <kstandarddirs.h>
29 #include <QtCore/QCache>
30 #include <QtCore/QList>
31 #include <QtCore/QVarLengthArray>
32 #include <QtCore/QVector>
33 #include <QtCore/QDataStream>
34 #include <QtCore/QFile>
35 #include <QtCore/QFileInfo>
36 #include <QtCore/QSet>
37 #include <QtCore/QString>
38 #include <QtCore/QTimerEvent>
40 static const char CACHE_MAGIC
[] = "PHwdbC";
41 static const quint32 CACHE_VERSION
= 2;
42 static const quint32 HEADER_LENGTH
= 14;
46 namespace HardwareDatabase
49 class HardwareDatabasePrivate
: public QObject
52 HardwareDatabasePrivate();
53 void createCache(const QString
&dbFileName
, const QString
&cacheFileName
);
54 bool validCacheHeader(QDataStream
&cacheStream
);
55 Entry
*readEntry(const QString
&uniqueId
);
57 QCache
<QString
, Entry
> entryCache
;
60 void timerEvent(QTimerEvent
*);
68 K_GLOBAL_STATIC(HardwareDatabasePrivate
, s_globalDB
)
70 HardwareDatabasePrivate::HardwareDatabasePrivate()
71 : m_timerId(-1), m_cacheFile(0)
73 const QString dbFileName
= KStandardDirs::locate("data", QLatin1String("libphonon/hardwaredatabase"));
74 if (dbFileName
.isEmpty()) {
75 // no database, we're useless
78 const QString cacheFileName
=
79 KGlobal::mainComponent().dirs()->saveLocation("cache", QLatin1String("libphonon/"))
80 + QLatin1String("hardwaredatabase");
81 const QFileInfo
dbFileInfo(dbFileName
);
82 const QFileInfo
cacheFileInfo(cacheFileName
);
83 if (!cacheFileInfo
.exists() || cacheFileInfo
.lastModified() < dbFileInfo
.lastModified()) {
85 createCache(dbFileName
, cacheFileName
);
87 m_cacheFile
= new QFile(cacheFileName
);
88 m_cacheFile
->open(QIODevice::ReadOnly
);
89 m_timerId
= startTimer(0);
90 QDataStream
cacheStream(m_cacheFile
);
91 if (!validCacheHeader(cacheStream
)) {
95 createCache(dbFileName
, cacheFileName
);
98 m_fileName
= cacheFileName
;
104 BucketEntry(const uint
&a
, const quint32
&b
) : hash(a
), offset(b
) {}
109 QDataStream
&operator<<(QDataStream
&s
, const BucketEntry
&e
)
111 return s
<< e
.hash
<< e
.offset
;
114 QDataStream
&operator>>(QDataStream
&s
, BucketEntry
&e
)
116 return s
>> e
.hash
>> e
.offset
;
119 void HardwareDatabasePrivate::createCache(const QString
&dbFileName
, const QString
&cacheFileName
)
121 KSaveFile
cacheFile(cacheFileName
);
127 const KConfig
dbFile(dbFileName
, KConfig::CascadeConfig
);
128 const bool opened
= cacheFile
.open(); // QIODevice::WriteOnly
130 QDataStream
cacheStream(&cacheFile
);
131 cacheStream
.writeRawData(CACHE_MAGIC
, 6);
132 cacheStream
<< CACHE_VERSION
<< cacheStream
.version() << quint32(0) << quint32(0);
133 QStringList allIds
= dbFile
.groupList();
134 QHash
<uint
, quint32
> offsets
;
135 offsets
.reserve(allIds
.count());
136 foreach (const QString
&uniqueId
, allIds
) {
137 offsets
.insertMulti(qHash(uniqueId
), cacheFile
.pos());
138 const KConfigGroup group
= dbFile
.group(uniqueId
);
139 name
= group
.readEntry("name", QString());
140 iconName
= group
.readEntry("icon", QString());
141 pref
= group
.readEntry("initialPreference", 0);
142 if (group
.hasKey("isAdvancedDevice")) {
143 isAdvanced
= group
.readEntry("isAdvancedDevice", false) ? 1 : 0;
147 cacheStream
<< uniqueId
<< name
<< iconName
<< pref
<< isAdvanced
;
150 const quint32 hashTableBuckets
= offsets
.capacity();
151 const quint32 hashTableOffset
= cacheFile
.pos();
152 QVector
<QList
<BucketEntry
> > bucketContents(hashTableBuckets
);
154 QHashIterator
<uint
, quint32
> it(offsets
);
155 while (it
.hasNext()) {
157 const uint
&h
= it
.key();
158 bucketContents
[h
% hashTableBuckets
] << BucketEntry(h
, it
.value());
162 for (quint32 i
= 0; i
< hashTableBuckets
; ++i
) {
163 cacheStream
<< quint32(0);
165 QVarLengthArray
<quint32
, 4099> bucketOffsets(hashTableBuckets
);
166 for (quint32 i
= 0; i
< hashTableBuckets
; ++i
) {
167 if (bucketContents
[i
].isEmpty()) {
168 bucketOffsets
[i
] = 0;
170 bucketOffsets
[i
] = cacheFile
.pos();
171 cacheStream
<< bucketContents
[i
];
174 bucketContents
.clear();
175 cacheFile
.seek(hashTableOffset
);
176 for (quint32 i
= 0; i
< hashTableBuckets
; ++i
) {
177 cacheStream
<< bucketOffsets
[i
];
179 cacheFile
.seek(HEADER_LENGTH
);
180 cacheStream
<< hashTableOffset
<< hashTableBuckets
;
184 bool HardwareDatabasePrivate::validCacheHeader(QDataStream
&cacheStream
)
188 int datastreamVersion
;
189 const int read
= cacheStream
.readRawData(magic
, 6);
190 cacheStream
>> version
>> datastreamVersion
;
191 return (read
== 6 && 0 == strncmp(magic
, CACHE_MAGIC
, 6) && version
== CACHE_VERSION
&& datastreamVersion
== cacheStream
.version());
194 Entry
*HardwareDatabasePrivate::readEntry(const QString
&uniqueId
)
196 QDataStream cacheStream
;
198 if (m_cacheFile
->seek(HEADER_LENGTH
)) {
199 cacheStream
.setDevice(m_cacheFile
);
206 m_cacheFile
= new QFile(m_fileName
);
207 m_cacheFile
->open(QIODevice::ReadOnly
);
208 m_timerId
= startTimer(0);
209 cacheStream
.setDevice(m_cacheFile
);
210 if (!validCacheHeader(cacheStream
)) {
214 quint32 hashTableOffset
;
215 quint32 hashTableBuckets
;
216 cacheStream
>> hashTableOffset
>> hashTableBuckets
;
217 const uint h
= qHash(uniqueId
);
218 m_cacheFile
->seek(hashTableOffset
+ (h
% hashTableBuckets
) * sizeof(quint32
));
220 cacheStream
>> offset
;
221 //kDebug(601) << hashTableOffset << hashTableBuckets << uniqueId << h << offset;
225 m_cacheFile
->seek(offset
);
226 QList
<BucketEntry
> bucket
;
227 cacheStream
>> bucket
;
235 foreach (const BucketEntry
&entry
, bucket
) {
236 if (entry
.hash
== h
) {
237 m_cacheFile
->seek(entry
.offset
);
238 cacheStream
>> readUdi
;
239 if (readUdi
== uniqueId
) {
240 cacheStream
>> name
>> iconName
>> pref
>> isAdvanced
;
241 Entry
*e
= new Entry(name
, iconName
, pref
, isAdvanced
);
242 s_globalDB
->entryCache
.insert(uniqueId
, e
);
251 void HardwareDatabasePrivate::timerEvent(QTimerEvent
*e
)
253 if (e
->timerId() == m_timerId
) {
256 killTimer(m_timerId
);
258 QObject::timerEvent(e
);
262 bool contains(const QString
&uniqueId
)
264 return (s_globalDB
->entryCache
[uniqueId
] || s_globalDB
->readEntry(uniqueId
));
267 Entry
entryFor(const QString
&uniqueId
)
269 Entry
*e
= s_globalDB
->entryCache
[uniqueId
];
273 e
= s_globalDB
->readEntry(uniqueId
);
280 } // namespace HardwareDatabase