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 "librarybaseelement.h"
24 
25 #include "librarybaseelementcheck.h"
26 
27 #include <librepcb/common/application.h>
28 #include <librepcb/common/fileio/sexpression.h>
29 #include <librepcb/common/fileio/versionfile.h>
30 
31 #include <QtCore>
32 
33 /*******************************************************************************
34  *  Namespace
35  ******************************************************************************/
36 namespace librepcb {
37 namespace library {
38 
39 /*******************************************************************************
40  *  Constructors / Destructor
41  ******************************************************************************/
42 
LibraryBaseElement(bool dirnameMustBeUuid,const QString & shortElementName,const QString & longElementName,const Uuid & uuid,const Version & version,const QString & author,const ElementName & name_en_US,const QString & description_en_US,const QString & keywords_en_US)43 LibraryBaseElement::LibraryBaseElement(
44     bool dirnameMustBeUuid, const QString& shortElementName,
45     const QString& longElementName, const Uuid& uuid, const Version& version,
46     const QString& author, const ElementName& name_en_US,
47     const QString& description_en_US, const QString& keywords_en_US)
48   : QObject(nullptr),
49     mDirectory(new TransactionalDirectory()),
50     mDirectoryNameMustBeUuid(dirnameMustBeUuid),
51     mShortElementName(shortElementName),
52     mLongElementName(longElementName),
53     mLoadingFileDocument(),
54     mLoadingFileFormat(qApp->getFileFormatVersion()),
55     mUuid(uuid),
56     mVersion(version),
57     mAuthor(author),
58     mCreated(QDateTime::currentDateTime()),
59     mIsDeprecated(false),
60     mNames(name_en_US),
61     mDescriptions(description_en_US),
62     mKeywords(keywords_en_US) {
63 }
64 
LibraryBaseElement(std::unique_ptr<TransactionalDirectory> directory,bool dirnameMustBeUuid,const QString & shortElementName,const QString & longElementName)65 LibraryBaseElement::LibraryBaseElement(
66     std::unique_ptr<TransactionalDirectory> directory, bool dirnameMustBeUuid,
67     const QString& shortElementName, const QString& longElementName)
68   : QObject(nullptr),
69     mDirectory(std::move(directory)),
70     mDirectoryNameMustBeUuid(dirnameMustBeUuid),
71     mShortElementName(shortElementName),
72     mLongElementName(longElementName),
73     mLoadingFileDocument(),
74     mLoadingFileFormat(qApp->getFileFormatVersion()),
75     mUuid(Uuid::createRandom()),  // just for initialization, will be
76                                   // overwritten
77     mVersion(Version::fromString(
78         "0.1")),  // just for initialization, will be overwritten
79     mNames(ElementName(
80         "unknown")),  // just for initialization, will be overwritten
81     mDescriptions(""),
82     mKeywords("") {
83   // determine the filename of the version file
84   QString versionFileName = ".librepcb-" % mShortElementName;
85 
86   // check if the directory is a library element
87   if (!mDirectory->fileExists(versionFileName)) {
88     throw RuntimeError(
89         __FILE__, __LINE__,
90         tr("Directory is not a library element of type %1: \"%2\"")
91             .arg(mLongElementName, mDirectory->getAbsPath().toNative()));
92   }
93 
94   // check directory name
95   QString dirUuidStr = mDirectory->getAbsPath().getFilename();
96   if (mDirectoryNameMustBeUuid && (!Uuid::isValid(dirUuidStr))) {
97     throw RuntimeError(__FILE__, __LINE__,
98                        tr("Directory name is not a valid UUID: \"%1\"")
99                            .arg(mDirectory->getAbsPath().toNative()));
100   }
101 
102   // read version number from version file
103   VersionFile versionFile =
104       VersionFile::fromByteArray(mDirectory->read(versionFileName));
105   mLoadingFileFormat = versionFile.getVersion();
106   if (mLoadingFileFormat > qApp->getAppVersion()) {
107     throw RuntimeError(
108         __FILE__, __LINE__,
109         QString(
110             tr("The library element %1 was created with a newer application "
111                "version. You need at least LibrePCB version %2 to open it."))
112             .arg(mDirectory->getAbsPath().toNative())
113             .arg(mLoadingFileFormat.toPrettyStr(3)));
114   }
115 
116   // open main file
117   QString sexprFileName = mLongElementName % ".lp";
118   FilePath sexprFilePath = mDirectory->getAbsPath(sexprFileName);
119   mLoadingFileDocument =
120       SExpression::parse(mDirectory->read(sexprFileName), sexprFilePath);
121 
122   // read attributes
123   mUuid = deserialize<Uuid>(mLoadingFileDocument.getChild("@0"),
124                             mLoadingFileFormat);
125   mVersion = deserialize<Version>(mLoadingFileDocument.getChild("version/@0"),
126                                   mLoadingFileFormat);
127   mAuthor = mLoadingFileDocument.getChild("author/@0").getValue();
128   mCreated = deserialize<QDateTime>(mLoadingFileDocument.getChild("created/@0"),
129                                     mLoadingFileFormat);
130   mIsDeprecated = deserialize<bool>(
131       mLoadingFileDocument.getChild("deprecated/@0"), mLoadingFileFormat);
132 
133   // read names, descriptions and keywords in all available languages
134   mNames = LocalizedNameMap(mLoadingFileDocument, mLoadingFileFormat);
135   mDescriptions =
136       LocalizedDescriptionMap(mLoadingFileDocument, mLoadingFileFormat);
137   mKeywords = LocalizedKeywordsMap(mLoadingFileDocument, mLoadingFileFormat);
138 
139   // check if the UUID equals to the directory basename
140   if (mDirectoryNameMustBeUuid && (mUuid.toStr() != dirUuidStr)) {
141     qDebug() << mUuid.toStr() << "!=" << dirUuidStr;
142     throw RuntimeError(
143         __FILE__, __LINE__,
144         QString(
145             tr("UUID mismatch between element directory and main file: \"%1\""))
146             .arg(sexprFilePath.toNative()));
147   }
148 }
149 
~LibraryBaseElement()150 LibraryBaseElement::~LibraryBaseElement() noexcept {
151 }
152 
153 /*******************************************************************************
154  *  Getters
155  ******************************************************************************/
156 
getAllAvailableLocales() const157 QStringList LibraryBaseElement::getAllAvailableLocales() const noexcept {
158   QStringList list;
159   list.append(mNames.keys());
160   list.append(mDescriptions.keys());
161   list.append(mKeywords.keys());
162   list.removeDuplicates();
163   list.sort(Qt::CaseSensitive);
164   return list;
165 }
166 
167 /*******************************************************************************
168  *  General Methods
169  ******************************************************************************/
170 
runChecks() const171 LibraryElementCheckMessageList LibraryBaseElement::runChecks() const {
172   LibraryBaseElementCheck check(*this);
173   return check.runChecks();  // can throw
174 }
175 
save()176 void LibraryBaseElement::save() {
177   // save S-Expressions file
178   mDirectory->write(
179       mLongElementName % ".lp",
180       serializeToDomElement("librepcb_" % mLongElementName).toByteArray());
181 
182   // save version number file
183   mDirectory->write(".librepcb-" % mShortElementName,
184                     VersionFile(qApp->getFileFormatVersion()).toByteArray());
185 }
186 
saveTo(TransactionalDirectory & dest)187 void LibraryBaseElement::saveTo(TransactionalDirectory& dest) {
188   mDirectory->saveTo(dest);  // can throw
189   save();  // can throw
190 }
191 
moveTo(TransactionalDirectory & dest)192 void LibraryBaseElement::moveTo(TransactionalDirectory& dest) {
193   mDirectory->moveTo(dest);  // can throw
194   save();  // can throw
195 }
196 
saveIntoParentDirectory(TransactionalDirectory & dest)197 void LibraryBaseElement::saveIntoParentDirectory(TransactionalDirectory& dest) {
198   TransactionalDirectory dir(dest, mUuid.toStr());
199   saveTo(dir);  // can throw
200 }
201 
moveIntoParentDirectory(TransactionalDirectory & dest)202 void LibraryBaseElement::moveIntoParentDirectory(TransactionalDirectory& dest) {
203   TransactionalDirectory dir(dest, mUuid.toStr());
204   moveTo(dir);  // can throw
205 }
206 
207 /*******************************************************************************
208  *  Protected Methods
209  ******************************************************************************/
210 
cleanupAfterLoadingElementFromFile()211 void LibraryBaseElement::cleanupAfterLoadingElementFromFile() noexcept {
212   mLoadingFileDocument = SExpression();  // destroy the whole DOM tree
213 }
214 
serialize(SExpression & root) const215 void LibraryBaseElement::serialize(SExpression& root) const {
216   root.appendChild(mUuid);
217   mNames.serialize(root);
218   mDescriptions.serialize(root);
219   mKeywords.serialize(root);
220   root.appendChild("author", mAuthor, true);
221   root.appendChild("version", mVersion, true);
222   root.appendChild("created", mCreated, true);
223   root.appendChild("deprecated", mIsDeprecated, true);
224 }
225 
226 /*******************************************************************************
227  *  End of File
228  ******************************************************************************/
229 
230 }  // namespace library
231 }  // namespace librepcb
232