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 "workspace.h"
24 
25 #include "favoriteprojectsmodel.h"
26 #include "library/workspacelibrarydb.h"
27 #include "projecttreemodel.h"
28 #include "recentprojectsmodel.h"
29 #include "settings/workspacesettings.h"
30 
31 #include <librepcb/common/application.h>
32 #include <librepcb/common/exceptions.h>
33 #include <librepcb/common/fileio/filepath.h>
34 #include <librepcb/common/fileio/fileutils.h>
35 #include <librepcb/common/fileio/versionfile.h>
36 #include <librepcb/library/library.h>
37 #include <librepcb/libraryeditor/libraryeditor.h>
38 #include <librepcb/project/project.h>
39 
40 #include <QtCore>
41 
42 #include <algorithm>
43 
44 /*******************************************************************************
45  *  Namespace
46  ******************************************************************************/
47 namespace librepcb {
48 
49 using namespace library;
50 using namespace project;
51 
52 namespace workspace {
53 
54 /*******************************************************************************
55  *  Constructors / Destructor
56  ******************************************************************************/
57 
Workspace(const FilePath & wsPath,DirectoryLock::LockHandlerCallback lockCallback)58 Workspace::Workspace(const FilePath& wsPath,
59                      DirectoryLock::LockHandlerCallback lockCallback)
60   : QObject(nullptr),
61     mPath(wsPath),
62     mProjectsPath(mPath.getPathTo("projects")),
63     mMetadataPath(mPath.getPathTo("v" % qApp->getFileFormatVersion().toStr())),
64     mLibrariesPath(mMetadataPath.getPathTo("libraries")),
65     mLock(mMetadataPath) {
66   // check if the workspace is valid
67   if (!isValidWorkspacePath(mPath)) {
68     throw RuntimeError(
69         __FILE__, __LINE__,
70         tr("Invalid workspace path: \"%1\"").arg(mPath.toNative()));
71   }
72   FilePath versionFp = mPath.getPathTo(".librepcb-workspace");
73   QByteArray versionRaw(FileUtils::readFile(versionFp));  // can throw
74   VersionFile wsVersionFile =
75       VersionFile::fromByteArray(versionRaw);  // can throw
76   if (wsVersionFile.getVersion() != FILE_FORMAT_VERSION()) {
77     throw RuntimeError(__FILE__, __LINE__,
78                        tr("The workspace version %1 is not compatible "
79                           "with this application version.")
80                            .arg(wsVersionFile.getVersion().toStr()));
81   }
82 
83   // create directories which do not exist already
84   FileUtils::makePath(mProjectsPath);  // can throw
85   FileUtils::makePath(mMetadataPath);  // can throw
86   FileUtils::makePath(mLibrariesPath);  // can throw
87 
88   // the workspace can be opened by this application, so we will lock it
89   mLock.tryLock(lockCallback);  // can throw
90 
91   // all OK, let's load the workspace stuff!
92 
93   // load workspace settings
94   mWorkspaceSettings.reset(
95       new WorkspaceSettings(mMetadataPath.getPathTo("settings.lp"),
96                             qApp->getFileFormatVersion(), this));
97 
98   // load library database
99   mLibraryDb.reset(new WorkspaceLibraryDb(*this));  // can throw
100 
101   // load project models
102   mRecentProjectsModel.reset(new RecentProjectsModel(*this));
103   mFavoriteProjectsModel.reset(new FavoriteProjectsModel(*this));
104   mProjectTreeModel.reset(new ProjectTreeModel(*this));
105 }
106 
~Workspace()107 Workspace::~Workspace() noexcept {
108 }
109 
110 /*******************************************************************************
111  *  Getters
112  ******************************************************************************/
113 
getProjectTreeModel() const114 ProjectTreeModel& Workspace::getProjectTreeModel() const noexcept {
115   return *mProjectTreeModel;
116 }
117 
getRecentProjectsModel() const118 RecentProjectsModel& Workspace::getRecentProjectsModel() const noexcept {
119   return *mRecentProjectsModel;
120 }
121 
getFavoriteProjectsModel() const122 FavoriteProjectsModel& Workspace::getFavoriteProjectsModel() const noexcept {
123   return *mFavoriteProjectsModel;
124 }
125 
126 /*******************************************************************************
127  *  Project Management
128  ******************************************************************************/
129 
setLastRecentlyUsedProject(const FilePath & filepath)130 void Workspace::setLastRecentlyUsedProject(const FilePath& filepath) noexcept {
131   mRecentProjectsModel->setLastRecentProject(filepath);
132 }
133 
isFavoriteProject(const FilePath & filepath) const134 bool Workspace::isFavoriteProject(const FilePath& filepath) const noexcept {
135   return mFavoriteProjectsModel->isFavoriteProject(filepath);
136 }
137 
addFavoriteProject(const FilePath & filepath)138 void Workspace::addFavoriteProject(const FilePath& filepath) noexcept {
139   mFavoriteProjectsModel->addFavoriteProject(filepath);
140 }
141 
removeFavoriteProject(const FilePath & filepath)142 void Workspace::removeFavoriteProject(const FilePath& filepath) noexcept {
143   mFavoriteProjectsModel->removeFavoriteProject(filepath);
144 }
145 
146 /*******************************************************************************
147  *  Static Methods
148  ******************************************************************************/
149 
isValidWorkspacePath(const FilePath & path)150 bool Workspace::isValidWorkspacePath(const FilePath& path) noexcept {
151   return path.getPathTo(".librepcb-workspace").isExistingFile();
152 }
153 
getFileFormatVersionsOfWorkspace(const FilePath & path)154 QList<Version> Workspace::getFileFormatVersionsOfWorkspace(
155     const FilePath& path) noexcept {
156   QList<Version> list;
157   if (isValidWorkspacePath(path)) {
158     QDir dir(path.toStr());
159     QStringList subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
160     foreach (const QString& subdir, subdirs) {
161       if (subdir.startsWith('v')) {
162         tl::optional<Version> version =
163             Version::tryFromString(subdir.mid(1, -1));
164         if (version) {
165           list.append(*version);
166         }
167       }
168     }
169     std::sort(list.begin(), list.end());
170   }
171   return list;
172 }
173 
getHighestFileFormatVersionOfWorkspace(const FilePath & path)174 tl::optional<Version> Workspace::getHighestFileFormatVersionOfWorkspace(
175     const FilePath& path) noexcept {
176   QList<Version> versions = getFileFormatVersionsOfWorkspace(path);
177   if (versions.count() > 0) {
178     return versions.last();
179   } else {
180     return tl::nullopt;
181   }
182 }
183 
createNewWorkspace(const FilePath & path)184 void Workspace::createNewWorkspace(const FilePath& path) {
185   FileUtils::writeFile(
186       path.getPathTo(".librepcb-workspace"),
187       VersionFile(FILE_FORMAT_VERSION()).toByteArray());  // can throw
188 }
189 
getMostRecentlyUsedWorkspacePath()190 FilePath Workspace::getMostRecentlyUsedWorkspacePath() noexcept {
191   QSettings clientSettings;
192   return FilePath(
193       clientSettings.value("workspaces/most_recently_used").toString());
194 }
195 
setMostRecentlyUsedWorkspacePath(const FilePath & path)196 void Workspace::setMostRecentlyUsedWorkspacePath(
197     const FilePath& path) noexcept {
198   QSettings clientSettings;
199   clientSettings.setValue("workspaces/most_recently_used", path.toNative());
200 }
201 
202 /*******************************************************************************
203  *  End of File
204  ******************************************************************************/
205 
206 }  // namespace workspace
207 }  // namespace librepcb
208