1 /* LibraryImporter.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 "LibraryImporter.h"
22 #include "ImportCache.h"
23 #include "CachingThread.h"
24 #include "CopyThread.h"
25 #include "Components/Library/LocalLibrary.h"
26 
27 #include "Components/Tagging/ChangeNotifier.h"
28 
29 #include "Utils/FileUtils.h"
30 #include "Utils/Library/LibraryInfo.h"
31 #include "Utils/MetaData/MetaDataList.h"
32 #include "Utils/Message/Message.h"
33 #include "Utils/Logger/Logger.h"
34 
35 #include "Database/Connector.h"
36 #include "Database/LibraryDatabase.h"
37 
38 #include <QMap>
39 #include <QDir>
40 
41 using Library::Importer;
42 using Library::CachingThread;
43 using Library::CopyThread;
44 using Library::ImportCachePtr;
45 
46 struct Importer::Private
47 {
48 	QString sourceDirectory;
49 	QStringList temporaryFiles;
50 
51 	LocalLibrary* library = nullptr;
52 	CachingThread* cachingThread = nullptr;
53 	CopyThread* copyThread = nullptr;
54 	ImportCachePtr importCache = nullptr;
55 
56 	Importer::ImportStatus status;
57 
PrivateImporter::Private58 	Private(LocalLibrary* library) :
59 		library(library),
60 		status(Importer::ImportStatus::NoTracks) {}
61 
deleteTemporaryFilesImporter::Private62 	void deleteTemporaryFiles()
63 	{
64 		Util::File::deleteFiles(temporaryFiles);
65 	}
66 };
67 
Importer(LocalLibrary * library)68 Importer::Importer(LocalLibrary* library) :
69 	QObject(library)
70 {
71 	m = Pimpl::make<Private>(library);
72 
73 	auto* cn = Tagging::ChangeNotifier::instance();
74 	connect(cn, &Tagging::ChangeNotifier::sigMetadataChanged, this, &Importer::metadataChanged);
75 }
76 
77 Importer::~Importer() = default;
78 
isRunning() const79 bool Importer::isRunning() const
80 {
81 	return (m->copyThread && m->copyThread->isRunning()) ||
82 	       (m->cachingThread && m->cachingThread->isRunning());
83 }
84 
importFiles(const QStringList & files,const QString & targetDir)85 void Importer::importFiles(const QStringList& files, const QString& targetDir)
86 {
87 	QStringList filesToBeImported;
88 
89 	for(const auto& file : files)
90 	{
91 		const auto info = m->library->info();
92 
93 		if(Util::File::isSubdir(file, info.path()) ||
94 		   Util::File::isSamePath(file, info.path()))
95 		{
96 			continue;
97 		}
98 
99 		filesToBeImported << files;
100 	}
101 
102 	if(filesToBeImported.isEmpty())
103 	{
104 		emitStatus(ImportStatus::NoValidTracks);
105 		return;
106 	}
107 
108 	emitStatus(ImportStatus::Caching);
109 
110 	if(!targetDir.isEmpty())
111 	{
112 		emit sigTargetDirectoryChanged(targetDir);
113 	}
114 
115 	auto* thread = new CachingThread(filesToBeImported, m->library->info().path());
116 	connect(thread, &CachingThread::finished, this, &Importer::cachingThreadFinished);
117 	connect(thread, &CachingThread::sigCachedFilesChanged, this, &Importer::sigCachedFilesChanged);
118 	connect(thread, &CachingThread::destroyed, this, [=]() {
119 		m->cachingThread = nullptr;
120 	});
121 
122 	m->cachingThread = thread;
123 	thread->start();
124 }
125 
126 // preload thread has cached everything, but ok button has not been clicked yet
cachingThreadFinished()127 void Importer::cachingThreadFinished()
128 {
129 	MetaDataList tracks;
130 	auto* thread = static_cast<CachingThread*>(sender());
131 
132 	m->temporaryFiles << thread->temporaryFiles();
133 	m->importCache = thread->cache();
134 
135 	if(!m->importCache)
136 	{
137 		emitStatus(ImportStatus::NoTracks);
138 	}
139 
140 	else
141 	{
142 		tracks = m->importCache->soundfiles();
143 	}
144 
145 	if(tracks.isEmpty() || thread->isCancelled())
146 	{
147 		emitStatus(ImportStatus::NoTracks);
148 	}
149 
150 	else
151 	{
152 		emitStatus(ImportStatus::CachingFinished);
153 	}
154 
155 	emit sigMetadataCached(tracks);
156 
157 	thread->deleteLater();
158 }
159 
cachedFileCount() const160 int Importer::cachedFileCount() const
161 {
162 	if(!m->importCache)
163 	{
164 		return 0;
165 	}
166 
167 	return m->importCache->soundfiles().count();
168 }
169 
170 // fired if ok was clicked in dialog
acceptImport(const QString & targetDir)171 void Importer::acceptImport(const QString& targetDir)
172 {
173 	emitStatus(ImportStatus::Importing);
174 
175 	auto* copy_thread = new CopyThread(targetDir, m->importCache, this);
176 	connect(copy_thread, &CopyThread::sigProgress, this, &Importer::sigProgress);
177 	connect(copy_thread, &CopyThread::finished, this, &Importer::copyThreadFinished);
178 	connect(copy_thread, &CachingThread::destroyed, this, [=]() {
179 		m->copyThread = nullptr;
180 	});
181 
182 	m->copyThread = copy_thread;
183 	copy_thread->start();
184 }
185 
copyThreadFinished()186 void Importer::copyThreadFinished()
187 {
188 	auto* copyThread = static_cast<CopyThread*>(sender());
189 
190 	reset();
191 
192 	auto* db = DB::Connector::instance();
193 	DB::LibraryDatabase* libraryDatabase = db->libraryDatabase(m->library->info().id(), db->databaseId());
194 
195 	MetaDataList tracks = copyThread->copiedMetadata();
196 	{ // no tracks were copied or rollback was finished
197 		if(tracks.isEmpty())
198 		{
199 			emitStatus(ImportStatus::NoTracks);
200 			copyThread->deleteLater();
201 
202 			return;
203 		}
204 	}
205 
206 	{ // copy was cancelled
207 		spLog(Log::Debug, this) << "Copy folder thread finished " << m->copyThread->wasCancelled();
208 		if(copyThread->wasCancelled())
209 		{
210 			emitStatus(ImportStatus::Rollback);
211 
212 			copyThread->setMode(CopyThread::Mode::Rollback);
213 			copyThread->start();
214 
215 			return;
216 		}
217 	}
218 
219 	copyThread->deleteLater();
220 
221 	bool success = libraryDatabase->storeMetadata(tracks);
222 	// error and success messages
223 	if(!success)
224 	{
225 		emitStatus(ImportStatus::Cancelled);
226 
227 		QString warning = tr("Cannot import tracks");
228 		Message::warning(warning);
229 		return;
230 	}
231 
232 	int copiedFilecount = copyThread->copiedFileCount();
233 	int cacheFilecount = m->importCache->count();
234 
235 	QString message;
236 	if(cacheFilecount == copiedFilecount)
237 	{
238 		message = tr("All files could be imported");
239 	}
240 
241 	else
242 	{
243 		message = tr("%1 of %2 files could be imported")
244 			.arg(copiedFilecount)
245 			.arg(cacheFilecount);
246 	}
247 
248 	emitStatus(ImportStatus::Imported);
249 	Message::info(message);
250 
251 	Tagging::ChangeNotifier::instance()->clearChangedMetadata();
252 }
253 
metadataChanged()254 void Importer::metadataChanged()
255 {
256 	auto* cn = Tagging::ChangeNotifier::instance();
257 	if(m->importCache)
258 	{
259 		m->importCache->changeMetadata(cn->changedMetadata());
260 	}
261 }
262 
263 // fired if cancel button was clicked in dialog
cancelImport()264 bool Importer::cancelImport()
265 {
266 	if(m->cachingThread && m->cachingThread->isRunning())
267 	{
268 		m->cachingThread->cancel();
269 		emitStatus(ImportStatus::Rollback);
270 	}
271 
272 	else if(m->copyThread && m->copyThread->isRunning())
273 	{
274 		m->copyThread->cancel();
275 		emitStatus(ImportStatus::Rollback);
276 	}
277 
278 	return true;
279 }
280 
reset()281 void Importer::reset()
282 {
283 	cancelImport();
284 	m->deleteTemporaryFiles();
285 }
286 
emitStatus(Importer::ImportStatus status)287 void Importer::emitStatus(Importer::ImportStatus status)
288 {
289 	m->status = status;
290 	emit sigStatusChanged(m->status);
291 }
292 
status() const293 Importer::ImportStatus Importer::status() const
294 {
295 	return m->status;
296 }
297