1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "ksycoca.h"
9#include "ksycocadict_p.h"
10#include "ksycocaentry.h"
11#include "ksycocaentry_p.h"
12#include "ksycocafactory_p.h"
13#include "ksycocatype.h"
14#include "sycocadebug.h"
15
16#include <QDebug>
17#include <QHash>
18#include <QIODevice>
19#include <QThread>
20
21class KSycocaFactoryPrivate
22{
23public:
24 KSycocaFactoryPrivate()
25 {
26 }
27 ~KSycocaFactoryPrivate()
28 {
29 delete m_sycocaDict;
30 }
31
32 int mOffset = 0;
33 int m_sycocaDictOffset = 0;
34 int m_beginEntryOffset = 0;
35 int m_endEntryOffset = 0;
36 KSycocaDict *m_sycocaDict = nullptr;
37};
38
39KSycocaFactory::KSycocaFactory(KSycocaFactoryId factory_id, KSycoca *sycoca)
40 : m_sycoca(sycoca)
41 , d(new KSycocaFactoryPrivate)
42{
43 if (!m_sycoca->isBuilding() && (m_str = m_sycoca->findFactory(id: factory_id))) {
44 // Read position of index tables....
45 qint32 i;
46 (*m_str) >> i;
47 d->m_sycocaDictOffset = i;
48 (*m_str) >> i;
49 d->m_beginEntryOffset = i;
50 (*m_str) >> i;
51 d->m_endEntryOffset = i;
52
53 QDataStream *str = stream();
54 qint64 saveOffset = str->device()->pos();
55 // Init index tables
56 d->m_sycocaDict = new KSycocaDict(str, d->m_sycocaDictOffset);
57 saveOffset = str->device()->seek(pos: saveOffset);
58 } else {
59 // We are in kbuildsycoca -- build new database!
60 m_entryDict = new KSycocaEntryDict;
61 d->m_sycocaDict = new KSycocaDict;
62 d->m_beginEntryOffset = 0;
63 d->m_endEntryOffset = 0;
64
65 // m_resourceList will be filled in by inherited constructors
66 }
67 m_sycoca->addFactory(this);
68}
69
70KSycocaFactory::~KSycocaFactory()
71{
72 delete m_entryDict;
73}
74
75void KSycocaFactory::saveHeader(QDataStream &str)
76{
77 // Write header
78 str.device()->seek(pos: d->mOffset);
79 str << qint32(d->m_sycocaDictOffset);
80 str << qint32(d->m_beginEntryOffset);
81 str << qint32(d->m_endEntryOffset);
82}
83
84void KSycocaFactory::save(QDataStream &str)
85{
86 if (!m_entryDict) {
87 return; // Error! Function should only be called when building database
88 }
89 if (!d->m_sycocaDict) {
90 return; // Error!
91 }
92
93 d->mOffset = str.device()->pos(); // store position in member variable
94 d->m_sycocaDictOffset = 0;
95
96 // Write header (pass #1)
97 saveHeader(str);
98
99 d->m_beginEntryOffset = str.device()->pos();
100
101 // Write all entries.
102 int entryCount = 0;
103 for (KSycocaEntry::Ptr entry : std::as_const(t&: *m_entryDict)) {
104 entry->d_ptr->save(s&: str);
105 entryCount++;
106 }
107
108 d->m_endEntryOffset = str.device()->pos();
109
110 // Write indices...
111 // Linear index
112 str << qint32(entryCount);
113 for (const KSycocaEntry::Ptr &entry : std::as_const(t&: *m_entryDict)) {
114 str << qint32(entry.data()->offset());
115 }
116
117 // Dictionary index
118 d->m_sycocaDictOffset = str.device()->pos();
119 d->m_sycocaDict->save(str);
120
121 qint64 endOfFactoryData = str.device()->pos();
122
123 // Update header (pass #2)
124 saveHeader(str);
125
126 // Seek to end.
127 str.device()->seek(pos: endOfFactoryData);
128}
129
130void KSycocaFactory::addEntry(const KSycocaEntry::Ptr &newEntry)
131{
132 if (!m_entryDict) {
133 return; // Error! Function should only be called when
134 }
135 // building database
136
137 if (!d->m_sycocaDict) {
138 return; // Error!
139 }
140
141 KSycocaEntry::Ptr oldEntry = m_entryDict->value(key: newEntry->storageId());
142 if (oldEntry) {
143 // Already exists -> replace
144 // We found a more-local override, e.g. ~/.local/share/applications/kde5/foo.desktop
145 // So forget about the more global file.
146 //
147 // This can also happen with two .protocol files using the same protocol= entry.
148 // If we didn't remove one here, we would end up asserting because save()
149 // wasn't called on one of the entries.
150 // qDebug() << "removing" << oldEntry.data() << oldEntry->entryPath() << "because of" << newEntry->entryPath() << "they have the same storageId" <<
151 // newEntry->storageId();
152 removeEntry(entryName: newEntry->storageId());
153 }
154
155 const QString name = newEntry->storageId();
156 m_entryDict->insert(key: name, value: newEntry);
157 d->m_sycocaDict->add(key: name, payload: newEntry);
158}
159
160void KSycocaFactory::removeEntry(const QString &entryName)
161{
162 if (!m_entryDict) {
163 return; // Error! Function should only be called when
164 }
165 // building database
166
167 if (!d->m_sycocaDict) {
168 return; // Error!
169 }
170
171 m_entryDict->remove(key: entryName);
172 d->m_sycocaDict->remove(key: entryName); // O(N)
173}
174
175KSycocaEntry::List KSycocaFactory::allEntries() const
176{
177 KSycocaEntry::List list;
178
179 // Assume we're NOT building a database
180
181 QDataStream *str = stream();
182 if (!str) {
183 return list;
184 }
185 str->device()->seek(pos: d->m_endEntryOffset);
186 qint32 entryCount;
187 (*str) >> entryCount;
188
189 if (entryCount > 8192) {
190 qCWarning(SYCOCA) << QThread::currentThread() << "error detected in factory" << this;
191 KSycoca::flagError();
192 return list;
193 }
194
195 // offsetList is needed because createEntry() modifies the stream position
196 qint32 *offsetList = new qint32[entryCount];
197 for (int i = 0; i < entryCount; i++) {
198 (*str) >> offsetList[i];
199 }
200
201 for (int i = 0; i < entryCount; i++) {
202 KSycocaEntry *newEntry = createEntry(offset: offsetList[i]);
203 if (newEntry) {
204 list.append(t: KSycocaEntry::Ptr(newEntry));
205 }
206 }
207 delete[] offsetList;
208 return list;
209}
210
211int KSycocaFactory::offset() const
212{
213 return d->mOffset;
214}
215
216const KSycocaResourceList &KSycocaFactory::resourceList() const
217{
218 return m_resourceList;
219}
220
221const KSycocaDict *KSycocaFactory::sycocaDict() const
222{
223 return d->m_sycocaDict;
224}
225
226bool KSycocaFactory::isEmpty() const
227{
228 return d->m_beginEntryOffset == d->m_endEntryOffset;
229}
230
231QDataStream *KSycocaFactory::stream() const
232{
233 return m_str;
234}
235
236QStringList KSycocaFactory::allDirectories(const QString &subdir)
237{
238 // We don't use QStandardPaths::locateAll() because we want all paths, even those that don't exist yet
239 QStringList topDirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
240 for (auto &dir : topDirs) {
241 dir += QLatin1Char('/') + subdir;
242 }
243 return topDirs;
244}
245
246void KSycocaFactory::virtual_hook(int /*id*/, void * /*data*/)
247{
248 /*BASE::virtual_hook( id, data );*/
249}
250

source code of kservice/src/sycoca/ksycocafactory.cpp