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