1 /* This file is part of Clementine.
2 Copyright 2010, David Sansome <me@davidsansome.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "devicemanager.h"
19 #include "mtpconnection.h"
20 #include "mtpdevice.h"
21 #include "mtploader.h"
22 #include "core/application.h"
23 #include "core/logging.h"
24 #include "library/librarybackend.h"
25 #include "library/librarymodel.h"
26
27 #include <libmtp.h>
28
29 #include <QFile>
30 #include <QThread>
31
32 bool MtpDevice::sInitialisedLibMTP = false;
33
MtpDevice(const QUrl & url,DeviceLister * lister,const QString & unique_id,DeviceManager * manager,Application * app,int database_id,bool first_time)34 MtpDevice::MtpDevice(const QUrl& url, DeviceLister* lister,
35 const QString& unique_id, DeviceManager* manager,
36 Application* app, int database_id, bool first_time)
37 : ConnectedDevice(url, lister, unique_id, manager, app, database_id,
38 first_time),
39 loader_thread_(new QThread(this)),
40 loader_(nullptr) {
41 if (!sInitialisedLibMTP) {
42 LIBMTP_Init();
43 sInitialisedLibMTP = true;
44 }
45 }
46
~MtpDevice()47 MtpDevice::~MtpDevice() {}
48
Init()49 void MtpDevice::Init() {
50 InitBackendDirectory("/", first_time_, false);
51 model_->Init();
52
53 loader_ =
54 new MtpLoader(url_, app_->task_manager(), backend_, shared_from_this());
55 loader_->moveToThread(loader_thread_);
56
57 connect(loader_, SIGNAL(Error(QString)), SLOT(LoaderError(QString)));
58 connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int)));
59 connect(loader_, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool)));
60 connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase()));
61 }
62
ConnectAsync()63 void MtpDevice::ConnectAsync() {
64 db_busy_.lock();
65 loader_thread_->start();
66 }
67
LoadFinished(bool success)68 void MtpDevice::LoadFinished(bool success) {
69 loader_->deleteLater();
70 loader_ = nullptr;
71 db_busy_.unlock();
72 emit ConnectFinished(unique_id_, success);
73 }
74
LoaderError(const QString & message)75 void MtpDevice::LoaderError(const QString& message) { app_->AddError(message); }
76
StartCopy(QList<Song::FileType> * supported_types)77 bool MtpDevice::StartCopy(QList<Song::FileType>* supported_types) {
78 // Ensure only one "organise files" can be active at any one time
79 db_busy_.lock();
80
81 // Connect to the device
82 connection_.reset(new MtpConnection(url_));
83
84 // Did the caller want a list of supported types?
85 if (supported_types) {
86 if (!GetSupportedFiletypes(supported_types, connection_->device())) {
87 FinishCopy(false);
88 return false;
89 }
90 }
91
92 return true;
93 }
94
ProgressCallback(uint64_t const sent,uint64_t const total,void const * const data)95 static int ProgressCallback(uint64_t const sent, uint64_t const total,
96 void const* const data) {
97 const MusicStorage::CopyJob* job =
98 reinterpret_cast<const MusicStorage::CopyJob*>(data);
99 job->progress_(float(sent) / total);
100
101 return 0;
102 }
103
CopyToStorage(const CopyJob & job)104 bool MtpDevice::CopyToStorage(const CopyJob& job) {
105 if (!connection_->is_valid()) return false;
106
107 // Convert metadata
108 LIBMTP_track_t track;
109 job.metadata_.ToMTP(&track);
110
111 // Send the file
112 int ret = LIBMTP_Send_Track_From_File(connection_->device(),
113 job.source_.toUtf8().constData(),
114 &track, ProgressCallback, &job);
115 if (ret != 0) return false;
116
117 // Add it to our LibraryModel
118 Song metadata_on_device;
119 metadata_on_device.InitFromMTP(&track, url_.host());
120 metadata_on_device.set_directory_id(1);
121 songs_to_add_ << metadata_on_device;
122
123 // Remove the original if requested
124 if (job.remove_original_) {
125 if (!QFile::remove(job.source_)) return false;
126 }
127
128 return true;
129 }
130
FinishCopy(bool success)131 void MtpDevice::FinishCopy(bool success) {
132 if (success) {
133 if (!songs_to_add_.isEmpty()) backend_->AddOrUpdateSongs(songs_to_add_);
134 if (!songs_to_remove_.isEmpty()) backend_->DeleteSongs(songs_to_remove_);
135 }
136
137 songs_to_add_.clear();
138 songs_to_remove_.clear();
139
140 connection_.reset();
141
142 db_busy_.unlock();
143
144 ConnectedDevice::FinishCopy(success);
145 }
146
StartDelete()147 void MtpDevice::StartDelete() { StartCopy(nullptr); }
148
DeleteFromStorage(const DeleteJob & job)149 bool MtpDevice::DeleteFromStorage(const DeleteJob& job) {
150 // Extract the ID from the song's URL
151 QString filename = job.metadata_.url().path();
152 filename.remove('/');
153
154 bool ok = false;
155 uint32_t id = filename.toUInt(&ok);
156 if (!ok) return false;
157
158 // Remove the file
159 int ret = LIBMTP_Delete_Object(connection_->device(), id);
160 if (ret != 0) return false;
161
162 // Remove it from our library model
163 songs_to_remove_ << job.metadata_;
164
165 return true;
166 }
167
FinishDelete(bool success)168 void MtpDevice::FinishDelete(bool success) { FinishCopy(success); }
169
GetSupportedFiletypes(QList<Song::FileType> * ret)170 bool MtpDevice::GetSupportedFiletypes(QList<Song::FileType>* ret) {
171 QMutexLocker l(&db_busy_);
172 MtpConnection connection(url_);
173 if (!connection.is_valid()) {
174 qLog(Warning) << "Error connecting to MTP device, couldn't get list of "
175 "supported filetypes";
176 return false;
177 }
178
179 return GetSupportedFiletypes(ret, connection.device());
180 }
181
GetSupportedFiletypes(QList<Song::FileType> * ret,LIBMTP_mtpdevice_t * device)182 bool MtpDevice::GetSupportedFiletypes(QList<Song::FileType>* ret,
183 LIBMTP_mtpdevice_t* device) {
184 uint16_t* list = nullptr;
185 uint16_t length = 0;
186
187 if (LIBMTP_Get_Supported_Filetypes(device, &list, &length) || !list ||
188 !length)
189 return false;
190
191 for (int i = 0; i < length; ++i) {
192 switch (LIBMTP_filetype_t(list[i])) {
193 case LIBMTP_FILETYPE_WAV:
194 *ret << Song::Type_Wav;
195 break;
196 case LIBMTP_FILETYPE_MP2:
197 case LIBMTP_FILETYPE_MP3:
198 *ret << Song::Type_Mpeg;
199 break;
200 case LIBMTP_FILETYPE_WMA:
201 *ret << Song::Type_Asf;
202 break;
203 case LIBMTP_FILETYPE_MP4:
204 case LIBMTP_FILETYPE_M4A:
205 case LIBMTP_FILETYPE_AAC:
206 *ret << Song::Type_Mp4;
207 break;
208 case LIBMTP_FILETYPE_FLAC:
209 *ret << Song::Type_Flac;
210 *ret << Song::Type_OggFlac;
211 break;
212 case LIBMTP_FILETYPE_OGG:
213 *ret << Song::Type_OggVorbis;
214 *ret << Song::Type_OggSpeex;
215 *ret << Song::Type_OggFlac;
216 break;
217 default:
218 qLog(Error) << "Unknown MTP file format"
219 << LIBMTP_Get_Filetype_Description(
220 LIBMTP_filetype_t(list[i]));
221 break;
222 }
223 }
224
225 free(list);
226 return true;
227 }
228