1 /* LibraryManager.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "LibraryManager.h"
22
23 #include "Components/Library/LocalLibrary.h"
24
25 #include "Database/Connector.h"
26 #include "Database/Library.h"
27 #include "Database/LibraryDatabase.h"
28
29 #include "Interfaces/LibraryPlaylistInteractor.h"
30
31 #include "Utils/Utils.h"
32 #include "Utils/Algorithm.h"
33 #include "Utils/FileUtils.h"
34 #include "Utils/Library/LibraryInfo.h"
35 #include "Utils/Settings/Settings.h"
36 #include "Utils/Logger/Logger.h"
37
38 #include <QDir>
39 #include <QMap>
40 #include <QObject>
41 #include <QString>
42
43 namespace File = ::Util::File;
44 namespace Algorithm = Util::Algorithm;
45
46 using Library::Manager;
47 using Library::Info;
48 using OrderMap = QMap<LibraryId, int>;
49
50 constexpr const auto InvalidLibraryId = -5;
51
52 struct Manager::Private
53 {
54 public:
55 QMap<LibraryId, LocalLibrary*> libraryMap;
56 LibraryPlaylistInteractor* playlistInteractor;
57 QList<Info> libraries;
58 DB::Connector* database;
59 DB::Library* libraryConnector;
60
PrivateManager::Private61 Private(LibraryPlaylistInteractor* playlistInteractor) :
62 playlistInteractor {playlistInteractor},
63 database {DB::Connector::instance()},
64 libraryConnector {database->libraryConnector()} {}
65
checkNewPathManager::Private66 bool checkNewPath(const QString& path, LibraryId libraryId = InvalidLibraryId) const
67 {
68 if(path.isEmpty())
69 {
70 return false;
71 }
72
73 for(const auto& info : libraries)
74 {
75 if(info.id() != libraryId)
76 {
77 if(Util::File::isSamePath(info.path(), path))
78 {
79 return false;
80 }
81
82 else if(Util::File::isSubdir(path, info.path()))
83 {
84 return false;
85 }
86 }
87 }
88
89 return true;
90 }
91
getNextIdManager::Private92 LibraryId getNextId() const
93 {
94 const auto it =
95 std::max_element(libraries.begin(), libraries.end(), [](const auto& info1, const auto& info2) {
96 return (info1.id() < info2.id());
97 });
98
99 return (it != libraries.end()) ? it->id() + 1 : 0;
100 }
101
orderMapManager::Private102 OrderMap orderMap() const
103 {
104 OrderMap orderMap;
105 int i = 0;
106 for(auto it = libraries.begin(); it != libraries.end(); it++, i++)
107 {
108 orderMap[it->id()] = i;
109 }
110
111 return orderMap;
112 }
113
refetchLibrariesManager::Private114 bool refetchLibraries()
115 {
116 const auto orderMap = this->orderMap();
117 const auto success = this->libraryConnector->reorderLibraries(orderMap);
118 this->libraries = this->libraryConnector->getAllLibraries();
119
120 return success;
121 }
122 };
123
Manager(LibraryPlaylistInteractor * playlistInteractor)124 Manager::Manager(LibraryPlaylistInteractor* playlistInteractor) :
125 QObject()
126 {
127 m = Pimpl::make<Private>(playlistInteractor);
128 reset();
129 }
130
131 Manager::~Manager() = default;
132
reset()133 void Manager::reset()
134 {
135 m->libraries = m->libraryConnector->getAllLibraries();
136
137 if(m->libraries.isEmpty())
138 {
139 m->libraries = GetSetting(Set::Lib_AllLibraries);
140
141 int i = 0;
142 for(auto it = m->libraries.begin(); it != m->libraries.end(); it++, i++)
143 {
144 m->libraryConnector->insertLibrary(it->id(), it->name(), it->path(), i);
145 spLog(Log::Info, this) << "All libraries are empty: Insert " << it->toString();
146 }
147
148 SetSetting(Set::Lib_AllLibraries, QList<::Library::Info>());
149 }
150
151 if(m->libraries.isEmpty())
152 {
153 const auto oldPath = GetSetting(Set::Lib_Path);
154 if(!oldPath.isEmpty())
155 {
156 const auto info = Info("Local Library", oldPath, 0);
157 m->libraryConnector->insertLibrary(0, info.name(), info.path(), 0);
158 m->libraries << info;
159 }
160
161 SetSetting(Set::Lib_Path, QString());
162 }
163
164 for(int i = m->libraries.size() - 1; i >= 0; i--)
165 {
166 if(m->libraries[i].valid())
167 {
168 m->database->registerLibraryDatabase(m->libraries[i].id());
169 }
170 else
171 {
172 m->libraries.removeAt(i);
173 }
174 }
175 }
176
addLibrary(const QString & name,const QString & path)177 LibraryId Manager::addLibrary(const QString& name, const QString& path)
178 {
179 if((!m->checkNewPath(path)) || (name.isEmpty()))
180 {
181 return -1;
182 }
183
184 const auto id = m->getNextId();
185 m->libraries << Info(name, path, id);
186
187 auto* libraryDatabase = m->database->registerLibraryDatabase(id);
188 libraryDatabase->deleteAllTracks(false); // maybe some corpses from earlier days
189
190 const auto inserted = m->libraryConnector->insertLibrary(id, name, path, 0);
191 if(inserted)
192 {
193 const auto reordered = m->libraryConnector->reorderLibraries(m->orderMap());
194 if(reordered)
195 {
196 emit sigAdded(id);
197 return id;
198 }
199 }
200
201 return -1;
202 }
203
renameLibrary(LibraryId id,const QString & newName)204 bool Manager::renameLibrary(LibraryId id, const QString& newName)
205 {
206 if(newName.isEmpty())
207 {
208 return false;
209 }
210
211 const auto nameExists = Util::Algorithm::contains(m->libraries, [&](const auto& info) {
212 return (info.name() == newName);
213 });
214
215 if(nameExists)
216 {
217 spLog(Log::Warning, this) << "Cannot rename library: Name already exists";
218 return false;
219 }
220
221 auto it = Util::Algorithm::find(m->libraries, [&](const auto& info) {
222 return (info.id() == id);
223 });
224
225 if(it == m->libraries.end())
226 {
227 spLog(Log::Warning, this) << "Cannot rename library: Cannot find id";
228 return false;
229 }
230
231 *it = Info(newName, it->path(), it->id());
232
233 const auto edited = m->libraryConnector->editLibrary(it->id(), newName, it->path());
234 if(edited)
235 {
236 emit sigRenamed(id);
237 return true;
238 }
239
240 spLog(Log::Warning, this) << "Cannot rename library: Database error";
241 return false;
242 }
243
removeLibrary(LibraryId id)244 bool Manager::removeLibrary(LibraryId id)
245 {
246 const auto exists = (Util::Algorithm::contains(m->libraries, [&](const auto& info) {
247 return (info.id() == id);
248 }));
249
250 if(m->libraryMap[id])
251 {
252 delete m->libraryMap[id];
253 }
254
255 m->libraryMap.remove(id);
256 m->database->deleteLibraryDatabase(id);
257
258 const auto removed = m->libraryConnector->removeLibrary(id);
259 if(removed)
260 {
261 m->refetchLibraries();
262 emit sigRemoved(id);
263
264 return exists;
265 }
266
267 return false;
268 }
269
moveLibrary(int from,int to)270 bool Manager::moveLibrary(int from, int to)
271 {
272 if(from < 0 || from >= count() || to < 0 || to >= count())
273 {
274 return false;
275 }
276
277 const auto id = m->libraries[from].id();
278
279 m->libraries.move(from, to);
280 m->refetchLibraries();
281
282 emit sigMoved(id, from, to);
283
284 return (!m->libraries.isEmpty());
285 }
286
changeLibraryPath(LibraryId id,const QString & newPath)287 bool Manager::changeLibraryPath(LibraryId id, const QString& newPath)
288 {
289 if(!m->checkNewPath(newPath, id))
290 {
291 return false;
292 }
293
294 const auto it = Algorithm::find(m->libraries, [&id](const Info& info) {
295 return (id == info.id());
296 });
297
298 if(it == m->libraries.end())
299 {
300 return false;
301 }
302
303 const auto oldInfo = std::move(*it);
304 *it = Info(oldInfo.name(), newPath, oldInfo.id());
305
306 auto* libraryDatabase = m->database->libraryDatabase(id, m->database->databaseId());
307 if(libraryDatabase->libraryId() >= 0)
308 {
309 libraryDatabase->deleteAllTracks(false);
310 }
311
312 if(m->libraryMap.contains(id))
313 {
314 auto* localLibrary = m->libraryMap[id];
315 if(localLibrary)
316 {
317 localLibrary->refetch();
318 }
319 }
320
321 const auto success = m->libraryConnector->editLibrary(oldInfo.id(), oldInfo.name(), newPath);
322 if(success)
323 {
324 emit sigPathChanged(id);
325 }
326
327 else
328 {
329 spLog(Log::Warning, this) << "Cannot change library path";
330 }
331
332 return success;
333 }
334
requestLibraryName(const QString & path)335 QString Manager::requestLibraryName(const QString& path)
336 {
337 return Util::stringToFirstUpper(QDir(path).dirName());
338 }
339
allLibraries() const340 QList<Info> Manager::allLibraries() const
341 {
342 return m->libraries;
343 }
344
count() const345 int Manager::count() const
346 {
347 return m->libraries.size();
348 }
349
libraryInfo(LibraryId id) const350 Info Manager::libraryInfo(LibraryId id) const
351 {
352 const auto it = Algorithm::find(m->libraries, [&](const auto& info) {
353 return (info.id() == id);
354 });
355
356 return (it != m->libraries.end()) ? *it : Info();
357 }
358
libraryInfoByPath(const QString & path) const359 Library::Info Manager::libraryInfoByPath(const QString& path) const
360 {
361 Info ret;
362
363 for(auto it = m->libraries.begin(); it != m->libraries.end(); it++)
364 {
365 if(path.startsWith(it->path()) && path.length() > ret.path().length())
366 {
367 ret = *it;
368 }
369 }
370
371 return ret;
372 }
373
libraryInstance(LibraryId id)374 LocalLibrary* Manager::libraryInstance(LibraryId id)
375 {
376 LocalLibrary* localLibrary = nullptr;
377 auto it = Algorithm::find(m->libraries, [&id](const Info& info) {
378 return (info.id() == id);
379 });
380
381 if(it != m->libraries.end() && m->libraryMap.contains(id))
382 {
383 localLibrary = m->libraryMap[id];
384 }
385
386 if(localLibrary == nullptr)
387 {
388 localLibrary = new LocalLibrary(this, id, m->playlistInteractor);
389 m->libraryMap[id] = localLibrary;
390 }
391
392 return localLibrary;
393 }
394