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