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