1 /*
2 * LibrePCB - Professional EDA for everyone!
3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4 * https://librepcb.org/
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*******************************************************************************
21 * Includes
22 ******************************************************************************/
23 #include "workspacelibrarydb.h"
24
25 #include "../workspace.h"
26 #include "workspacelibraryscanner.h"
27
28 #include <librepcb/common/fileio/filepath.h>
29 #include <librepcb/common/fileio/sexpression.h>
30 #include <librepcb/common/sqlitedatabase.h>
31 #include <librepcb/library/cat/componentcategory.h>
32 #include <librepcb/library/cat/packagecategory.h>
33 #include <librepcb/library/cmp/component.h>
34 #include <librepcb/library/dev/device.h>
35 #include <librepcb/library/library.h>
36 #include <librepcb/library/pkg/package.h>
37 #include <librepcb/library/sym/symbol.h>
38
39 #include <QtCore>
40 #include <QtSql>
41
42 /*******************************************************************************
43 * Namespace
44 ******************************************************************************/
45 namespace librepcb {
46 namespace workspace {
47
48 using namespace library;
49
50 /*******************************************************************************
51 * Constructors / Destructor
52 ******************************************************************************/
53
WorkspaceLibraryDb(Workspace & ws)54 WorkspaceLibraryDb::WorkspaceLibraryDb(Workspace& ws)
55 : QObject(nullptr), mWorkspace(ws) {
56 qDebug("Load workspace library database...");
57
58 // open SQLite database
59 mFilePath = ws.getLibrariesPath().getPathTo(
60 QString("cache_v%1.sqlite").arg(sCurrentDbVersion));
61 mDb.reset(new SQLiteDatabase(mFilePath)); // can throw
62
63 // Check database version - actually it must match the version in the
64 // filename, but if not (e.g. due to a mistake by us) we just remove the whole
65 // database and create a new one.
66 int dbVersion = getDbVersion();
67 if (dbVersion != sCurrentDbVersion) {
68 qCritical() << "Library database version" << dbVersion << "is wrong!";
69 mDb.reset();
70 QFile(mFilePath.toStr()).remove();
71 mDb.reset(new SQLiteDatabase(mFilePath)); // can throw
72 createAllTables(); // can throw
73 setDbVersion(sCurrentDbVersion); // can throw
74 }
75
76 // create library scanner object
77 mLibraryScanner.reset(new WorkspaceLibraryScanner(mWorkspace, mFilePath));
78 connect(mLibraryScanner.data(), &WorkspaceLibraryScanner::scanStarted, this,
79 &WorkspaceLibraryDb::scanStarted, Qt::QueuedConnection);
80 connect(mLibraryScanner.data(),
81 &WorkspaceLibraryScanner::scanLibraryListUpdated, this,
82 &WorkspaceLibraryDb::scanLibraryListUpdated, Qt::QueuedConnection);
83 connect(mLibraryScanner.data(), &WorkspaceLibraryScanner::scanProgressUpdate,
84 this, &WorkspaceLibraryDb::scanProgressUpdate, Qt::QueuedConnection);
85 connect(mLibraryScanner.data(), &WorkspaceLibraryScanner::scanSucceeded, this,
86 &WorkspaceLibraryDb::scanSucceeded, Qt::QueuedConnection);
87 connect(mLibraryScanner.data(), &WorkspaceLibraryScanner::scanFailed, this,
88 &WorkspaceLibraryDb::scanFailed, Qt::QueuedConnection);
89 connect(mLibraryScanner.data(), &WorkspaceLibraryScanner::scanFinished, this,
90 &WorkspaceLibraryDb::scanFinished, Qt::QueuedConnection);
91
92 qDebug("Workspace library database successfully loaded!");
93 }
94
~WorkspaceLibraryDb()95 WorkspaceLibraryDb::~WorkspaceLibraryDb() noexcept {
96 }
97
98 /*******************************************************************************
99 * Getters: Libraries
100 ******************************************************************************/
101
getLibraries() const102 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getLibraries() const {
103 QSqlQuery query =
104 mDb->prepareQuery("SELECT version, filepath FROM libraries");
105 mDb->exec(query);
106
107 QMultiMap<Version, FilePath> libraries;
108 while (query.next()) {
109 Version version =
110 Version::fromString(query.value(0).toString()); // can throw
111 FilePath filepath(FilePath::fromRelative(mWorkspace.getLibrariesPath(),
112 query.value(1).toString()));
113 if (filepath.isValid()) {
114 libraries.insert(version, filepath);
115 } else {
116 throw LogicError(__FILE__, __LINE__);
117 }
118 }
119 return libraries;
120 }
121
122 /*******************************************************************************
123 * Getters: Library Elements by their UUID
124 ******************************************************************************/
125
getLibraries(const Uuid & uuid) const126 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getLibraries(
127 const Uuid& uuid) const {
128 return getElementFilePathsFromDb("libraries", uuid);
129 }
130
getComponentCategories(const Uuid & uuid) const131 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getComponentCategories(
132 const Uuid& uuid) const {
133 return getElementFilePathsFromDb("component_categories", uuid);
134 }
135
getPackageCategories(const Uuid & uuid) const136 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getPackageCategories(
137 const Uuid& uuid) const {
138 return getElementFilePathsFromDb("package_categories", uuid);
139 }
140
getSymbols(const Uuid & uuid) const141 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getSymbols(
142 const Uuid& uuid) const {
143 return getElementFilePathsFromDb("symbols", uuid);
144 }
145
getPackages(const Uuid & uuid) const146 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getPackages(
147 const Uuid& uuid) const {
148 return getElementFilePathsFromDb("packages", uuid);
149 }
150
getComponents(const Uuid & uuid) const151 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getComponents(
152 const Uuid& uuid) const {
153 return getElementFilePathsFromDb("components", uuid);
154 }
155
getDevices(const Uuid & uuid) const156 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getDevices(
157 const Uuid& uuid) const {
158 return getElementFilePathsFromDb("devices", uuid);
159 }
160
161 /*******************************************************************************
162 * Getters: Best Match Library Elements by their UUID
163 ******************************************************************************/
164
getLatestLibrary(const Uuid & uuid) const165 FilePath WorkspaceLibraryDb::getLatestLibrary(const Uuid& uuid) const {
166 return getLatestVersionFilePath(getLibraries(uuid));
167 }
168
getLatestComponentCategory(const Uuid & uuid) const169 FilePath WorkspaceLibraryDb::getLatestComponentCategory(
170 const Uuid& uuid) const {
171 return getLatestVersionFilePath(getComponentCategories(uuid));
172 }
173
getLatestPackageCategory(const Uuid & uuid) const174 FilePath WorkspaceLibraryDb::getLatestPackageCategory(const Uuid& uuid) const {
175 return getLatestVersionFilePath(getPackageCategories(uuid));
176 }
177
getLatestSymbol(const Uuid & uuid) const178 FilePath WorkspaceLibraryDb::getLatestSymbol(const Uuid& uuid) const {
179 return getLatestVersionFilePath(getSymbols(uuid));
180 }
181
getLatestPackage(const Uuid & uuid) const182 FilePath WorkspaceLibraryDb::getLatestPackage(const Uuid& uuid) const {
183 return getLatestVersionFilePath(getPackages(uuid));
184 }
185
getLatestComponent(const Uuid & uuid) const186 FilePath WorkspaceLibraryDb::getLatestComponent(const Uuid& uuid) const {
187 return getLatestVersionFilePath(getComponents(uuid));
188 }
189
getLatestDevice(const Uuid & uuid) const190 FilePath WorkspaceLibraryDb::getLatestDevice(const Uuid& uuid) const {
191 return getLatestVersionFilePath(getDevices(uuid));
192 }
193
194 /*******************************************************************************
195 * Getters: Library elements by search keyword
196 ******************************************************************************/
197
198 template <>
getElementsBySearchKeyword(const QString & keyword) const199 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<Library>(
200 const QString& keyword) const {
201 return getElementsBySearchKeyword("libraries", "lib_id", keyword);
202 }
203
204 template <>
getElementsBySearchKeyword(const QString & keyword) const205 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<ComponentCategory>(
206 const QString& keyword) const {
207 return getElementsBySearchKeyword("component_categories", "cat_id", keyword);
208 }
209
210 template <>
getElementsBySearchKeyword(const QString & keyword) const211 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<PackageCategory>(
212 const QString& keyword) const {
213 return getElementsBySearchKeyword("package_categories", "cat_id", keyword);
214 }
215
216 template <>
getElementsBySearchKeyword(const QString & keyword) const217 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<Symbol>(
218 const QString& keyword) const {
219 return getElementsBySearchKeyword("symbols", "symbol_id", keyword);
220 }
221
222 template <>
getElementsBySearchKeyword(const QString & keyword) const223 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<Package>(
224 const QString& keyword) const {
225 return getElementsBySearchKeyword("packages", "package_id", keyword);
226 }
227
228 template <>
getElementsBySearchKeyword(const QString & keyword) const229 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<Component>(
230 const QString& keyword) const {
231 return getElementsBySearchKeyword("components", "component_id", keyword);
232 }
233
234 template <>
getElementsBySearchKeyword(const QString & keyword) const235 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword<Device>(
236 const QString& keyword) const {
237 return getElementsBySearchKeyword("devices", "device_id", keyword);
238 }
239
240 /*******************************************************************************
241 * Getters: Library elements of a specified library
242 ******************************************************************************/
243
244 template <>
getLibraryElements(const FilePath & lib) const245 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<ComponentCategory>(
246 const FilePath& lib) const {
247 return getLibraryElements(lib, "component_categories"); // can throw
248 }
249
250 template <>
getLibraryElements(const FilePath & lib) const251 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<PackageCategory>(
252 const FilePath& lib) const {
253 return getLibraryElements(lib, "package_categories"); // can throw
254 }
255
256 template <>
getLibraryElements(const FilePath & lib) const257 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<Symbol>(
258 const FilePath& lib) const {
259 return getLibraryElements(lib, "symbols"); // can throw
260 }
261
262 template <>
getLibraryElements(const FilePath & lib) const263 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<Package>(
264 const FilePath& lib) const {
265 return getLibraryElements(lib, "packages"); // can throw
266 }
267
268 template <>
getLibraryElements(const FilePath & lib) const269 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<Component>(
270 const FilePath& lib) const {
271 return getLibraryElements(lib, "components"); // can throw
272 }
273
274 template <>
getLibraryElements(const FilePath & lib) const275 QList<FilePath> WorkspaceLibraryDb::getLibraryElements<Device>(
276 const FilePath& lib) const {
277 return getLibraryElements(lib, "devices"); // can throw
278 }
279
280 /*******************************************************************************
281 * Getters: Element Metadata
282 ******************************************************************************/
283
284 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const285 void WorkspaceLibraryDb::getElementTranslations<Library>(
286 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
287 QString* desc, QString* keywords) const {
288 getElementTranslations("libraries", "lib_id", elemDir, localeOrder, name,
289 desc, keywords);
290 }
291
292 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const293 void WorkspaceLibraryDb::getElementTranslations<ComponentCategory>(
294 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
295 QString* desc, QString* keywords) const {
296 getElementTranslations("component_categories", "cat_id", elemDir, localeOrder,
297 name, desc, keywords);
298 }
299
300 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const301 void WorkspaceLibraryDb::getElementTranslations<PackageCategory>(
302 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
303 QString* desc, QString* keywords) const {
304 getElementTranslations("package_categories", "cat_id", elemDir, localeOrder,
305 name, desc, keywords);
306 }
307
308 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const309 void WorkspaceLibraryDb::getElementTranslations<Symbol>(
310 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
311 QString* desc, QString* keywords) const {
312 getElementTranslations("symbols", "symbol_id", elemDir, localeOrder, name,
313 desc, keywords);
314 }
315
316 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const317 void WorkspaceLibraryDb::getElementTranslations<Package>(
318 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
319 QString* desc, QString* keywords) const {
320 getElementTranslations("packages", "package_id", elemDir, localeOrder, name,
321 desc, keywords);
322 }
323
324 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const325 void WorkspaceLibraryDb::getElementTranslations<Component>(
326 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
327 QString* desc, QString* keywords) const {
328 getElementTranslations("components", "component_id", elemDir, localeOrder,
329 name, desc, keywords);
330 }
331
332 template <>
getElementTranslations(const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const333 void WorkspaceLibraryDb::getElementTranslations<Device>(
334 const FilePath& elemDir, const QStringList& localeOrder, QString* name,
335 QString* desc, QString* keywords) const {
336 getElementTranslations("devices", "device_id", elemDir, localeOrder, name,
337 desc, keywords);
338 }
339
340 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const341 void WorkspaceLibraryDb::getElementMetadata<Library>(const FilePath elemDir,
342 Uuid* uuid,
343 Version* version) const {
344 return getElementMetadata("libraries", elemDir, uuid, version);
345 }
346
347 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const348 void WorkspaceLibraryDb::getElementMetadata<ComponentCategory>(
349 const FilePath elemDir, Uuid* uuid, Version* version) const {
350 return getElementMetadata("component_categories", elemDir, uuid, version);
351 }
352
353 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const354 void WorkspaceLibraryDb::getElementMetadata<PackageCategory>(
355 const FilePath elemDir, Uuid* uuid, Version* version) const {
356 return getElementMetadata("package_categories", elemDir, uuid, version);
357 }
358
359 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const360 void WorkspaceLibraryDb::getElementMetadata<Symbol>(const FilePath elemDir,
361 Uuid* uuid,
362 Version* version) const {
363 return getElementMetadata("symbols", elemDir, uuid, version);
364 }
365
366 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const367 void WorkspaceLibraryDb::getElementMetadata<Package>(const FilePath elemDir,
368 Uuid* uuid,
369 Version* version) const {
370 return getElementMetadata("packages", elemDir, uuid, version);
371 }
372
373 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const374 void WorkspaceLibraryDb::getElementMetadata<Component>(const FilePath elemDir,
375 Uuid* uuid,
376 Version* version) const {
377 return getElementMetadata("components", elemDir, uuid, version);
378 }
379
380 template <>
getElementMetadata(const FilePath elemDir,Uuid * uuid,Version * version) const381 void WorkspaceLibraryDb::getElementMetadata<Device>(const FilePath elemDir,
382 Uuid* uuid,
383 Version* version) const {
384 return getElementMetadata("devices", elemDir, uuid, version);
385 }
386
getLibraryMetadata(const FilePath libDir,QPixmap * icon) const387 void WorkspaceLibraryDb::getLibraryMetadata(const FilePath libDir,
388 QPixmap* icon) const {
389 QSqlQuery query = mDb->prepareQuery(
390 "SELECT icon_png FROM libraries WHERE filepath = :filepath");
391 query.bindValue(":filepath",
392 libDir.toRelative(mWorkspace.getLibrariesPath()));
393 mDb->exec(query);
394
395 if (query.first()) {
396 QByteArray blob = query.value(0).toByteArray();
397 if (icon) icon->loadFromData(blob, "png");
398 } else {
399 throw RuntimeError(__FILE__, __LINE__,
400 tr("Library not found in workspace library: \"%1\"")
401 .arg(libDir.toNative()));
402 }
403 }
404
getDeviceMetadata(const FilePath & devDir,Uuid * pkgUuid,Uuid * cmpUuid) const405 void WorkspaceLibraryDb::getDeviceMetadata(const FilePath& devDir,
406 Uuid* pkgUuid, Uuid* cmpUuid) const {
407 QSqlQuery query = mDb->prepareQuery(
408 "SELECT package_uuid, component_uuid "
409 "FROM devices WHERE filepath = :filepath");
410 query.bindValue(":filepath",
411 devDir.toRelative(mWorkspace.getLibrariesPath()));
412 mDb->exec(query);
413
414 if (query.first()) {
415 Uuid uuid = Uuid::fromString(query.value(0).toString()); // can throw
416 if (pkgUuid) *pkgUuid = uuid;
417 uuid = Uuid::fromString(query.value(1).toString()); // can throw
418 if (cmpUuid) *cmpUuid = uuid;
419 } else {
420 throw RuntimeError(__FILE__, __LINE__,
421 tr("Device not found in workspace library: \"%1\"")
422 .arg(devDir.toNative()));
423 }
424 }
425
426 /*******************************************************************************
427 * Getters: Special
428 ******************************************************************************/
429
getComponentCategoryChilds(const tl::optional<Uuid> & parent) const430 QSet<Uuid> WorkspaceLibraryDb::getComponentCategoryChilds(
431 const tl::optional<Uuid>& parent) const {
432 return getCategoryChilds("component_categories", parent);
433 }
434
getPackageCategoryChilds(const tl::optional<Uuid> & parent) const435 QSet<Uuid> WorkspaceLibraryDb::getPackageCategoryChilds(
436 const tl::optional<Uuid>& parent) const {
437 return getCategoryChilds("package_categories", parent);
438 }
439
getComponentCategoryParents(const Uuid & category) const440 QList<Uuid> WorkspaceLibraryDb::getComponentCategoryParents(
441 const Uuid& category) const {
442 return getCategoryParents("component_categories", category);
443 }
444
getPackageCategoryParents(const Uuid & category) const445 QList<Uuid> WorkspaceLibraryDb::getPackageCategoryParents(
446 const Uuid& category) const {
447 return getCategoryParents("package_categories", category);
448 }
449
getComponentCategoryElementCount(const tl::optional<Uuid> & category,int * categories,int * symbols,int * components,int * devices) const450 void WorkspaceLibraryDb::getComponentCategoryElementCount(
451 const tl::optional<Uuid>& category, int* categories, int* symbols,
452 int* components, int* devices) const {
453 if (categories) {
454 *categories = getCategoryChildCount("component_categories", category);
455 }
456 if (symbols) {
457 *symbols = getCategoryElementCount("symbols", "symbol_id", category);
458 }
459 if (components) {
460 *components =
461 getCategoryElementCount("components", "component_id", category);
462 }
463 if (devices) {
464 *devices = getCategoryElementCount("devices", "device_id", category);
465 }
466 }
467
getPackageCategoryElementCount(const tl::optional<Uuid> & category,int * categories,int * packages) const468 void WorkspaceLibraryDb::getPackageCategoryElementCount(
469 const tl::optional<Uuid>& category, int* categories, int* packages) const {
470 if (categories) {
471 *categories = getCategoryChildCount("package_categories", category);
472 }
473 if (packages) {
474 *packages = getCategoryElementCount("packages", "package_id", category);
475 }
476 }
477
getSymbolsByCategory(const tl::optional<Uuid> & category) const478 QSet<Uuid> WorkspaceLibraryDb::getSymbolsByCategory(
479 const tl::optional<Uuid>& category) const {
480 return getElementsByCategory("symbols", "symbol_id", category);
481 }
482
getPackagesByCategory(const tl::optional<Uuid> & category) const483 QSet<Uuid> WorkspaceLibraryDb::getPackagesByCategory(
484 const tl::optional<Uuid>& category) const {
485 return getElementsByCategory("packages", "package_id", category);
486 }
487
getComponentsByCategory(const tl::optional<Uuid> & category) const488 QSet<Uuid> WorkspaceLibraryDb::getComponentsByCategory(
489 const tl::optional<Uuid>& category) const {
490 return getElementsByCategory("components", "component_id", category);
491 }
492
getDevicesByCategory(const tl::optional<Uuid> & category) const493 QSet<Uuid> WorkspaceLibraryDb::getDevicesByCategory(
494 const tl::optional<Uuid>& category) const {
495 return getElementsByCategory("devices", "device_id", category);
496 }
497
getDevicesOfComponent(const Uuid & component) const498 QSet<Uuid> WorkspaceLibraryDb::getDevicesOfComponent(
499 const Uuid& component) const {
500 QSqlQuery query = mDb->prepareQuery(
501 "SELECT uuid FROM devices WHERE component_uuid = :uuid");
502 query.bindValue(":uuid", component.toStr());
503 mDb->exec(query);
504
505 QSet<Uuid> elements;
506 while (query.next()) {
507 elements.insert(Uuid::fromString(query.value(0).toString())); // can throw
508 }
509 return elements;
510 }
511
512 /*******************************************************************************
513 * General Methods
514 ******************************************************************************/
515
startLibraryRescan()516 void WorkspaceLibraryDb::startLibraryRescan() noexcept {
517 mLibraryScanner->startScan();
518 }
519
520 /*******************************************************************************
521 * Private Methods
522 ******************************************************************************/
523
getElementTranslations(const QString & table,const QString & idRow,const FilePath & elemDir,const QStringList & localeOrder,QString * name,QString * desc,QString * keywords) const524 void WorkspaceLibraryDb::getElementTranslations(const QString& table,
525 const QString& idRow,
526 const FilePath& elemDir,
527 const QStringList& localeOrder,
528 QString* name, QString* desc,
529 QString* keywords) const {
530 QSqlQuery query = mDb->prepareQuery(
531 "SELECT locale, name, description, keywords FROM " % table %
532 "_tr "
533 "INNER JOIN " %
534 table % " ON " % table % ".id=" % table % "_tr." % idRow %
535 " "
536 "WHERE " %
537 table % ".filepath = :filepath");
538 query.bindValue(":filepath",
539 elemDir.toRelative(mWorkspace.getLibrariesPath()));
540 mDb->exec(query);
541
542 LocalizedNameMap nameMap(ElementName("unknown"));
543 LocalizedDescriptionMap descriptionMap("unknown");
544 LocalizedKeywordsMap keywordsMap("unknown");
545 while (query.next()) {
546 QString locale = query.value(0).toString();
547 QString name = query.value(1).toString();
548 QString description = query.value(2).toString();
549 QString keywords = query.value(3).toString();
550 if (!name.isNull()) nameMap.insert(locale, ElementName(name)); // can throw
551 if (!description.isNull()) descriptionMap.insert(locale, description);
552 if (!keywords.isNull()) keywordsMap.insert(locale, keywords);
553 }
554
555 if (name) *name = *nameMap.value(localeOrder);
556 if (desc) *desc = descriptionMap.value(localeOrder);
557 if (keywords) *keywords = keywordsMap.value(localeOrder);
558 }
559
getElementMetadata(const QString & table,const FilePath elemDir,Uuid * uuid,Version * version) const560 void WorkspaceLibraryDb::getElementMetadata(const QString& table,
561 const FilePath elemDir, Uuid* uuid,
562 Version* version) const {
563 QSqlQuery query = mDb->prepareQuery("SELECT uuid, version FROM " % table %
564 " WHERE filepath = :filepath");
565 query.bindValue(":filepath",
566 elemDir.toRelative(mWorkspace.getLibrariesPath()));
567 mDb->exec(query);
568
569 while (query.next()) {
570 QString uuidStr = query.value(0).toString();
571 QString versionStr = query.value(1).toString();
572 if (uuid) *uuid = Uuid::fromString(uuidStr); // can throw
573 if (version) *version = Version::fromString(versionStr); // can throw
574 }
575 }
576
getElementFilePathsFromDb(const QString & tablename,const Uuid & uuid) const577 QMultiMap<Version, FilePath> WorkspaceLibraryDb::getElementFilePathsFromDb(
578 const QString& tablename, const Uuid& uuid) const {
579 QSqlQuery query = mDb->prepareQuery("SELECT version, filepath FROM " %
580 tablename % " WHERE uuid = :uuid");
581 query.bindValue(":uuid", uuid.toStr());
582 mDb->exec(query);
583
584 QMultiMap<Version, FilePath> elements;
585 while (query.next()) {
586 Version version =
587 Version::fromString(query.value(0).toString()); // can throw
588 FilePath filepath(FilePath::fromRelative(mWorkspace.getLibrariesPath(),
589 query.value(1).toString()));
590 if (filepath.isValid()) {
591 elements.insert(version, filepath);
592 } else {
593 throw LogicError(__FILE__, __LINE__);
594 }
595 }
596 return elements;
597 }
598
getLatestVersionFilePath(const QMultiMap<Version,FilePath> & list) const599 FilePath WorkspaceLibraryDb::getLatestVersionFilePath(
600 const QMultiMap<Version, FilePath>& list) const noexcept {
601 if (list.isEmpty())
602 return FilePath();
603 else
604 return list.last(); // highest version number
605 }
606
getCategoryChilds(const QString & tablename,const tl::optional<Uuid> & categoryUuid) const607 QSet<Uuid> WorkspaceLibraryDb::getCategoryChilds(
608 const QString& tablename, const tl::optional<Uuid>& categoryUuid) const {
609 QSqlQuery query = mDb->prepareQuery(
610 "SELECT uuid FROM " % tablename % " WHERE parent_uuid " %
611 (categoryUuid ? "= '" % categoryUuid->toStr() % "'"
612 : QString("IS NULL")));
613 mDb->exec(query);
614
615 QSet<Uuid> elements;
616 while (query.next()) {
617 elements.insert(Uuid::fromString(query.value(0).toString())); // can throw
618 }
619 return elements;
620 }
621
getCategoryParents(const QString & tablename,const Uuid & category) const622 QList<Uuid> WorkspaceLibraryDb::getCategoryParents(const QString& tablename,
623 const Uuid& category) const {
624 tl::optional<Uuid> optCategory = category;
625 QList<Uuid> parentUuids;
626 while ((optCategory = getCategoryParent(tablename, *optCategory))) {
627 if (parentUuids.contains(*optCategory)) {
628 throw RuntimeError(__FILE__, __LINE__,
629 tr("Endless loop "
630 "in category parentship detected (%1).")
631 .arg(optCategory->toStr()));
632 } else {
633 parentUuids.append(*optCategory);
634 }
635 }
636 return parentUuids;
637 }
638
getCategoryParent(const QString & tablename,const Uuid & category) const639 tl::optional<Uuid> WorkspaceLibraryDb::getCategoryParent(
640 const QString& tablename, const Uuid& category) const {
641 QSqlQuery query = mDb->prepareQuery(
642 "SELECT parent_uuid FROM " % tablename % " WHERE uuid = '" %
643 category.toStr() % "'" % " ORDER BY version DESC" % " LIMIT 1");
644 mDb->exec(query);
645
646 if (query.next()) {
647 QVariant value = query.value(0);
648 if (!value.isNull()) {
649 return Uuid::fromString(value.toString()); // can throw
650 } else {
651 return tl::nullopt;
652 }
653 } else {
654 throw RuntimeError(__FILE__, __LINE__,
655 tr("The category "
656 "\"%1\" does not exist in the library database.")
657 .arg(category.toStr()));
658 }
659 }
660
getCategoryChildCount(const QString & tablename,const tl::optional<Uuid> & category) const661 int WorkspaceLibraryDb::getCategoryChildCount(
662 const QString& tablename, const tl::optional<Uuid>& category) const {
663 QSqlQuery query = mDb->prepareQuery(
664 "SELECT COUNT(*) FROM " % tablename % " WHERE parent_uuid " %
665 (category ? "= '" % category->toStr() % "'" : QString("IS NULL")));
666 return mDb->count(query);
667 }
668
getCategoryElementCount(const QString & tablename,const QString & idrowname,const tl::optional<Uuid> & category) const669 int WorkspaceLibraryDb::getCategoryElementCount(
670 const QString& tablename, const QString& idrowname,
671 const tl::optional<Uuid>& category) const {
672 QSqlQuery query = mDb->prepareQuery(
673 "SELECT COUNT(*) FROM " % tablename % " LEFT JOIN " % tablename % "_cat" %
674 " ON " % tablename % ".id=" % tablename % "_cat." % idrowname %
675 " WHERE category_uuid " %
676 (category ? "= '" % category->toStr() % "'" : QString("IS NULL")));
677 return mDb->count(query);
678 }
679
getElementsByCategory(const QString & tablename,const QString & idrowname,const tl::optional<Uuid> & categoryUuid) const680 QSet<Uuid> WorkspaceLibraryDb::getElementsByCategory(
681 const QString& tablename, const QString& idrowname,
682 const tl::optional<Uuid>& categoryUuid) const {
683 QSqlQuery query = mDb->prepareQuery(
684 "SELECT uuid FROM " % tablename % " LEFT JOIN " % tablename %
685 "_cat "
686 "ON " %
687 tablename % ".id=" % tablename % "_cat." % idrowname %
688 " "
689 "WHERE category_uuid " %
690 (categoryUuid ? "= '" % categoryUuid->toStr() % "'"
691 : QString("IS NULL")));
692 mDb->exec(query);
693
694 QSet<Uuid> elements;
695 while (query.next()) {
696 elements.insert(Uuid::fromString(query.value(0).toString())); // can throw
697 }
698 return elements;
699 }
700
getElementsBySearchKeyword(const QString & tablename,const QString & idrowname,const QString & keyword) const701 QList<Uuid> WorkspaceLibraryDb::getElementsBySearchKeyword(
702 const QString& tablename, const QString& idrowname,
703 const QString& keyword) const {
704 QSqlQuery query = mDb->prepareQuery(QString("SELECT %1.uuid FROM %1, %1_tr "
705 "ON %1.id=%1_tr.%2 "
706 "WHERE %1_tr.name LIKE :keyword "
707 "OR %1_tr.keywords LIKE :keyword "
708 "ORDER BY %1_tr.name ASC ")
709 .arg(tablename, idrowname));
710 query.bindValue(":keyword", "%" + keyword + "%");
711 mDb->exec(query);
712
713 QList<Uuid> elements;
714 elements.reserve(query.size());
715 while (query.next()) {
716 elements.append(Uuid::fromString(query.value(0).toString())); // can throw
717 }
718 return elements;
719 }
720
getLibraryId(const FilePath & lib) const721 int WorkspaceLibraryDb::getLibraryId(const FilePath& lib) const {
722 QString relativeLibraryPath = lib.toRelative(mWorkspace.getLibrariesPath());
723 QSqlQuery query = mDb->prepareQuery(
724 "SELECT id FROM libraries "
725 "WHERE filepath = '" %
726 relativeLibraryPath %
727 "'"
728 "LIMIT 1");
729 mDb->exec(query);
730
731 if (query.next()) {
732 bool ok = false;
733 int id = query.value(0).toInt(&ok);
734 if (!ok) throw LogicError(__FILE__, __LINE__);
735 return id;
736 } else {
737 throw RuntimeError(__FILE__, __LINE__,
738 tr("The library "
739 "\"%1\" does not exist in the library database.")
740 .arg(relativeLibraryPath));
741 }
742 }
743
getLibraryElements(const FilePath & lib,const QString & tablename) const744 QList<FilePath> WorkspaceLibraryDb::getLibraryElements(
745 const FilePath& lib, const QString& tablename) const {
746 QSqlQuery query = mDb->prepareQuery("SELECT filepath FROM " % tablename %
747 " WHERE lib_id = :lib_id");
748 query.bindValue(":lib_id", getLibraryId(lib));
749 mDb->exec(query);
750
751 QList<FilePath> elements;
752 while (query.next()) {
753 FilePath filepath(FilePath::fromRelative(mWorkspace.getLibrariesPath(),
754 query.value(0).toString()));
755 if (filepath.isValid()) {
756 elements.append(filepath);
757 } else {
758 throw LogicError(__FILE__, __LINE__);
759 }
760 }
761 return elements;
762 }
763
createAllTables()764 void WorkspaceLibraryDb::createAllTables() {
765 QStringList queries;
766
767 // internal
768 queries << QString(
769 "CREATE TABLE IF NOT EXISTS internal ("
770 "`id` INTEGER PRIMARY KEY NOT NULL, "
771 "`key` TEXT UNIQUE NOT NULL, "
772 "`value_text` TEXT, "
773 "`value_int` INTEGER, "
774 "`value_real` REAL, "
775 "`value_blob` BLOB "
776 ")");
777
778 // libraries
779 queries << QString(
780 "CREATE TABLE IF NOT EXISTS libraries ("
781 "`id` INTEGER PRIMARY KEY NOT NULL, "
782 "`filepath` TEXT UNIQUE NOT NULL, "
783 "`uuid` TEXT NOT NULL, "
784 "`version` TEXT NOT NULL, "
785 "`icon_png` BLOB "
786 ")");
787 queries << QString(
788 "CREATE TABLE IF NOT EXISTS libraries_tr ("
789 "`id` INTEGER PRIMARY KEY NOT NULL, "
790 "`lib_id` INTEGER "
791 "REFERENCES libraries(id) ON DELETE CASCADE NOT NULL, "
792 "`locale` TEXT NOT NULL, "
793 "`name` TEXT, "
794 "`description` TEXT, "
795 "`keywords` TEXT, "
796 "UNIQUE(lib_id, locale)"
797 ")");
798
799 // component categories
800 queries << QString(
801 "CREATE TABLE IF NOT EXISTS component_categories ("
802 "`id` INTEGER PRIMARY KEY NOT NULL, "
803 "`lib_id` INTEGER NOT NULL, "
804 "`filepath` TEXT UNIQUE NOT NULL, "
805 "`uuid` TEXT NOT NULL, "
806 "`version` TEXT NOT NULL, "
807 "`parent_uuid` TEXT"
808 ")");
809 queries << QString(
810 "CREATE TABLE IF NOT EXISTS component_categories_tr ("
811 "`id` INTEGER PRIMARY KEY NOT NULL, "
812 "`cat_id` INTEGER "
813 "REFERENCES component_categories(id) ON DELETE CASCADE NOT NULL, "
814 "`locale` TEXT NOT NULL, "
815 "`name` TEXT, "
816 "`description` TEXT, "
817 "`keywords` TEXT, "
818 "UNIQUE(cat_id, locale)"
819 ")");
820
821 // package categories
822 queries << QString(
823 "CREATE TABLE IF NOT EXISTS package_categories ("
824 "`id` INTEGER PRIMARY KEY NOT NULL, "
825 "`lib_id` INTEGER NOT NULL, "
826 "`filepath` TEXT UNIQUE NOT NULL, "
827 "`uuid` TEXT NOT NULL, "
828 "`version` TEXT NOT NULL, "
829 "`parent_uuid` TEXT"
830 ")");
831 queries << QString(
832 "CREATE TABLE IF NOT EXISTS package_categories_tr ("
833 "`id` INTEGER PRIMARY KEY NOT NULL, "
834 "`cat_id` INTEGER "
835 "REFERENCES package_categories(id) ON DELETE CASCADE NOT NULL, "
836 "`locale` TEXT NOT NULL, "
837 "`name` TEXT, "
838 "`description` TEXT, "
839 "`keywords` TEXT, "
840 "UNIQUE(cat_id, locale)"
841 ")");
842
843 // symbols
844 queries << QString(
845 "CREATE TABLE IF NOT EXISTS symbols ("
846 "`id` INTEGER PRIMARY KEY NOT NULL, "
847 "`lib_id` INTEGER NOT NULL, "
848 "`filepath` TEXT UNIQUE NOT NULL, "
849 "`uuid` TEXT NOT NULL, "
850 "`version` TEXT NOT NULL"
851 ")");
852 queries << QString(
853 "CREATE TABLE IF NOT EXISTS symbols_tr ("
854 "`id` INTEGER PRIMARY KEY NOT NULL, "
855 "`symbol_id` INTEGER "
856 "REFERENCES symbols(id) ON DELETE CASCADE NOT NULL, "
857 "`locale` TEXT NOT NULL, "
858 "`name` TEXT, "
859 "`description` TEXT, "
860 "`keywords` TEXT, "
861 "UNIQUE(symbol_id, locale)"
862 ")");
863 queries << QString(
864 "CREATE TABLE IF NOT EXISTS symbols_cat ("
865 "`id` INTEGER PRIMARY KEY NOT NULL, "
866 "`symbol_id` INTEGER "
867 "REFERENCES symbols(id) ON DELETE CASCADE NOT NULL, "
868 "`category_uuid` TEXT NOT NULL, "
869 "UNIQUE(symbol_id, category_uuid)"
870 ")");
871
872 // packages
873 queries << QString(
874 "CREATE TABLE IF NOT EXISTS packages ("
875 "`id` INTEGER PRIMARY KEY NOT NULL, "
876 "`lib_id` INTEGER NOT NULL, "
877 "`filepath` TEXT UNIQUE NOT NULL, "
878 "`uuid` TEXT NOT NULL, "
879 "`version` TEXT NOT NULL "
880 ")");
881 queries << QString(
882 "CREATE TABLE IF NOT EXISTS packages_tr ("
883 "`id` INTEGER PRIMARY KEY NOT NULL, "
884 "`package_id` INTEGER "
885 "REFERENCES packages(id) ON DELETE CASCADE NOT NULL, "
886 "`locale` TEXT NOT NULL, "
887 "`name` TEXT, "
888 "`description` TEXT, "
889 "`keywords` TEXT, "
890 "UNIQUE(package_id, locale)"
891 ")");
892 queries << QString(
893 "CREATE TABLE IF NOT EXISTS packages_cat ("
894 "`id` INTEGER PRIMARY KEY NOT NULL, "
895 "`package_id` INTEGER "
896 "REFERENCES packages(id) ON DELETE CASCADE NOT NULL, "
897 "`category_uuid` TEXT NOT NULL, "
898 "UNIQUE(package_id, category_uuid)"
899 ")");
900
901 // components
902 queries << QString(
903 "CREATE TABLE IF NOT EXISTS components ("
904 "`id` INTEGER PRIMARY KEY NOT NULL, "
905 "`lib_id` INTEGER NOT NULL, "
906 "`filepath` TEXT UNIQUE NOT NULL, "
907 "`uuid` TEXT NOT NULL, "
908 "`version` TEXT NOT NULL"
909 ")");
910 queries << QString(
911 "CREATE TABLE IF NOT EXISTS components_tr ("
912 "`id` INTEGER PRIMARY KEY NOT NULL, "
913 "`component_id` INTEGER "
914 "REFERENCES components(id) ON DELETE CASCADE NOT NULL, "
915 "`locale` TEXT NOT NULL, "
916 "`name` TEXT, "
917 "`description` TEXT, "
918 "`keywords` TEXT, "
919 "UNIQUE(component_id, locale)"
920 ")");
921 queries << QString(
922 "CREATE TABLE IF NOT EXISTS components_cat ("
923 "`id` INTEGER PRIMARY KEY NOT NULL, "
924 "`component_id` INTEGER "
925 "REFERENCES components(id) ON DELETE CASCADE NOT NULL, "
926 "`category_uuid` TEXT NOT NULL, "
927 "UNIQUE(component_id, category_uuid)"
928 ")");
929
930 // devices
931 queries << QString(
932 "CREATE TABLE IF NOT EXISTS devices ("
933 "`id` INTEGER PRIMARY KEY NOT NULL, "
934 "`lib_id` INTEGER NOT NULL, "
935 "`filepath` TEXT UNIQUE NOT NULL, "
936 "`uuid` TEXT NOT NULL, "
937 "`version` TEXT NOT NULL, "
938 "`component_uuid` TEXT NOT NULL, "
939 "`package_uuid` TEXT NOT NULL"
940 ")");
941 queries << QString(
942 "CREATE TABLE IF NOT EXISTS devices_tr ("
943 "`id` INTEGER PRIMARY KEY NOT NULL, "
944 "`device_id` INTEGER "
945 "REFERENCES devices(id) ON DELETE CASCADE NOT NULL, "
946 "`locale` TEXT NOT NULL, "
947 "`name` TEXT, "
948 "`description` TEXT, "
949 "`keywords` TEXT, "
950 "UNIQUE(device_id, locale)"
951 ")");
952 queries << QString(
953 "CREATE TABLE IF NOT EXISTS devices_cat ("
954 "`id` INTEGER PRIMARY KEY NOT NULL, "
955 "`device_id` INTEGER "
956 "REFERENCES devices(id) ON DELETE CASCADE NOT NULL, "
957 "`category_uuid` TEXT NOT NULL, "
958 "UNIQUE(device_id, category_uuid)"
959 ")");
960
961 // execute queries
962 foreach (const QString& string, queries) {
963 QSqlQuery query = mDb->prepareQuery(string); // can throw
964 mDb->exec(query); // can throw
965 }
966 }
967
getDbVersion() const968 int WorkspaceLibraryDb::getDbVersion() const noexcept {
969 try {
970 QSqlQuery query = mDb->prepareQuery(
971 "SELECT value_int FROM internal WHERE key = 'version'");
972 mDb->exec(query);
973 if (query.next()) {
974 bool ok = false;
975 int version = query.value(0).toInt(&ok);
976 if (!ok) throw LogicError(__FILE__, __LINE__);
977 return version;
978 } else {
979 throw LogicError(__FILE__, __LINE__);
980 }
981 } catch (const Exception& e) {
982 return -1;
983 }
984 }
985
setDbVersion(int version)986 void WorkspaceLibraryDb::setDbVersion(int version) {
987 QSqlQuery query = mDb->prepareQuery(
988 "INSERT INTO internal (key, value_int) "
989 "VALUES ('version', :version)");
990 query.bindValue(":version", version);
991 mDb->insert(query); // can throw
992 }
993
994 /*******************************************************************************
995 * End of File
996 ******************************************************************************/
997
998 } // namespace workspace
999 } // namespace librepcb
1000