Fix mtp device support
This commit is contained in:
@@ -1055,6 +1055,7 @@ void Song::ToItdb(Itdb_Track *track) const {
|
|||||||
void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
||||||
|
|
||||||
d->valid_ = true;
|
d->valid_ = true;
|
||||||
|
d->source_ = Source_Device;
|
||||||
|
|
||||||
set_title(QString::fromUtf8(track->title));
|
set_title(QString::fromUtf8(track->title));
|
||||||
set_artist(QString::fromUtf8(track->artist));
|
set_artist(QString::fromUtf8(track->artist));
|
||||||
@@ -1063,7 +1064,7 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
|||||||
d->composer_ = QString::fromUtf8(track->composer);
|
d->composer_ = QString::fromUtf8(track->composer);
|
||||||
d->track_ = track->tracknumber;
|
d->track_ = track->tracknumber;
|
||||||
|
|
||||||
d->url_ = QUrl(QString("mtp://%1/%2").arg(host, track->item_id));
|
d->url_ = QUrl(QString("mtp://%1/%2").arg(host, QString::number(track->item_id)));
|
||||||
d->basefilename_ = QString::number(track->item_id);
|
d->basefilename_ = QString::number(track->item_id);
|
||||||
d->filesize_ = track->filesize;
|
d->filesize_ = track->filesize;
|
||||||
d->mtime_ = track->modificationdate;
|
d->mtime_ = track->modificationdate;
|
||||||
@@ -1072,7 +1073,7 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
|||||||
set_length_nanosec(track->duration * kNsecPerMsec);
|
set_length_nanosec(track->duration * kNsecPerMsec);
|
||||||
|
|
||||||
d->samplerate_ = track->samplerate;
|
d->samplerate_ = track->samplerate;
|
||||||
d->bitdepth_ = 0; //track->bitdepth;
|
d->bitdepth_ = 0;
|
||||||
d->bitrate_ = track->bitrate;
|
d->bitrate_ = track->bitrate;
|
||||||
|
|
||||||
d->playcount_ = track->usecount;
|
d->playcount_ = track->usecount;
|
||||||
@@ -1087,11 +1088,12 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
|||||||
case LIBMTP_FILETYPE_FLAC: d->filetype_ = FileType_OggFlac; break;
|
case LIBMTP_FILETYPE_FLAC: d->filetype_ = FileType_OggFlac; break;
|
||||||
case LIBMTP_FILETYPE_MP2: d->filetype_ = FileType_MPEG; break;
|
case LIBMTP_FILETYPE_MP2: d->filetype_ = FileType_MPEG; break;
|
||||||
case LIBMTP_FILETYPE_M4A: d->filetype_ = FileType_MP4; break;
|
case LIBMTP_FILETYPE_M4A: d->filetype_ = FileType_MP4; break;
|
||||||
default: d->filetype_ = FileType_Unknown; break;
|
default:
|
||||||
|
d->filetype_ = FileType_Unknown;
|
||||||
|
d->valid_ = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->source_ = Source_Device;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Song::ToMTP(LIBMTP_track_t *track) const {
|
void Song::ToMTP(LIBMTP_track_t *track) const {
|
||||||
@@ -1101,14 +1103,18 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
|
|||||||
track->storage_id = 0;
|
track->storage_id = 0;
|
||||||
|
|
||||||
track->title = strdup(d->title_.toUtf8().constData());
|
track->title = strdup(d->title_.toUtf8().constData());
|
||||||
track->artist = strdup(d->artist_.toUtf8().constData());
|
track->artist = strdup(effective_albumartist().toUtf8().constData());
|
||||||
track->album = strdup(d->album_.toUtf8().constData());
|
track->album = strdup(d->album_.toUtf8().constData());
|
||||||
track->genre = strdup(d->genre_.toUtf8().constData());
|
track->genre = strdup(d->genre_.toUtf8().constData());
|
||||||
track->date = nullptr;
|
track->date = nullptr;
|
||||||
track->tracknumber = d->track_;
|
track->tracknumber = d->track_;
|
||||||
track->composer = strdup(d->composer_.toUtf8().constData());
|
if (d->composer_.isEmpty())
|
||||||
|
track->composer = nullptr;
|
||||||
|
else
|
||||||
|
track->composer = strdup(d->composer_.toUtf8().constData());
|
||||||
|
|
||||||
track->filename = strdup(d->basefilename_.toUtf8().constData());
|
track->filename = strdup(d->basefilename_.toUtf8().constData());
|
||||||
|
|
||||||
track->filesize = d->filesize_;
|
track->filesize = d->filesize_;
|
||||||
track->modificationdate = d->mtime_;
|
track->modificationdate = d->mtime_;
|
||||||
|
|
||||||
@@ -1123,15 +1129,15 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
|
|||||||
track->usecount = d->playcount_;
|
track->usecount = d->playcount_;
|
||||||
|
|
||||||
switch (d->filetype_) {
|
switch (d->filetype_) {
|
||||||
case FileType_ASF: track->filetype = LIBMTP_FILETYPE_ASF; break;
|
case FileType_ASF: track->filetype = LIBMTP_FILETYPE_ASF; break;
|
||||||
case FileType_MP4: track->filetype = LIBMTP_FILETYPE_MP4; break;
|
case FileType_MP4: track->filetype = LIBMTP_FILETYPE_MP4; break;
|
||||||
case FileType_MPEG: track->filetype = LIBMTP_FILETYPE_MP3; break;
|
case FileType_MPEG: track->filetype = LIBMTP_FILETYPE_MP3; break;
|
||||||
case FileType_FLAC:
|
case FileType_FLAC:
|
||||||
case FileType_OggFlac: track->filetype = LIBMTP_FILETYPE_FLAC; break;
|
case FileType_OggFlac: track->filetype = LIBMTP_FILETYPE_FLAC; break;
|
||||||
case FileType_OggSpeex:
|
case FileType_OggSpeex:
|
||||||
case FileType_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG; break;
|
case FileType_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG; break;
|
||||||
case FileType_WAV: track->filetype = LIBMTP_FILETYPE_WAV; break;
|
case FileType_WAV: track->filetype = LIBMTP_FILETYPE_WAV; break;
|
||||||
default: track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break;
|
default: track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ class CollectionModel;
|
|||||||
class DeviceLister;
|
class DeviceLister;
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
|
|
||||||
class ConnectedDevice : public QObject,
|
class ConnectedDevice : public QObject, public virtual MusicStorage, public std::enable_shared_from_this<ConnectedDevice> {
|
||||||
public virtual MusicStorage,
|
|
||||||
public std::enable_shared_from_this<ConnectedDevice> {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -50,6 +48,7 @@ class ConnectedDevice : public QObject,
|
|||||||
~ConnectedDevice();
|
~ConnectedDevice();
|
||||||
|
|
||||||
virtual bool Init() = 0;
|
virtual bool Init() = 0;
|
||||||
|
virtual void NewConnection() {}
|
||||||
virtual void ConnectAsync();
|
virtual void ConnectAsync();
|
||||||
// For some devices (e.g. CD devices) we don't have callbacks to be notified when something change:
|
// For some devices (e.g. CD devices) we don't have callbacks to be notified when something change:
|
||||||
// we can call this method to refresh device's state
|
// we can call this method to refresh device's state
|
||||||
|
|||||||
@@ -47,10 +47,12 @@ using std::placeholders::_3;
|
|||||||
|
|
||||||
QString GioLister::DeviceInfo::unique_id() const {
|
QString GioLister::DeviceInfo::unique_id() const {
|
||||||
|
|
||||||
|
if (!volume_root_uri.isEmpty()) return volume_root_uri;
|
||||||
|
|
||||||
if (mount)
|
if (mount)
|
||||||
return QString("Gio/%1/%2/%3").arg(mount_uuid, filesystem_type).arg(filesystem_size);
|
return QString("Gio/%1/%2/%3").arg(mount_uuid, filesystem_type).arg(filesystem_size);
|
||||||
|
else
|
||||||
return QString("Gio/unmounted/%1").arg((qulonglong)volume.get());
|
return QString("Gio/unmounted/%1").arg((qulonglong)volume.get());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,43 +193,65 @@ QVariantMap GioLister::DeviceHardwareInfo(const QString &id) {
|
|||||||
|
|
||||||
QList<QUrl> GioLister::MakeDeviceUrls(const QString &id) {
|
QList<QUrl> GioLister::MakeDeviceUrls(const QString &id) {
|
||||||
|
|
||||||
|
QString volume_root_uri;
|
||||||
QString mount_point;
|
QString mount_point;
|
||||||
QString uri;
|
QString mount_uri;
|
||||||
QString unix_device;
|
QString unix_device;
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
|
volume_root_uri = devices_[id].volume_root_uri;
|
||||||
mount_point = devices_[id].mount_path;
|
mount_point = devices_[id].mount_path;
|
||||||
uri = devices_[id].mount_uri;
|
mount_uri = devices_[id].mount_uri;
|
||||||
unix_device = devices_[id].volume_unix_device;
|
unix_device = devices_[id].volume_unix_device;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gphoto2 gives invalid hostnames with []:, characters in
|
QStringList uris;
|
||||||
uri.replace(QRegExp("//\\[usb:(\\d+),(\\d+)\\]"), "//usb-\\1-\\2");
|
if (!volume_root_uri.isEmpty())
|
||||||
|
uris << volume_root_uri;
|
||||||
|
|
||||||
QUrl url(uri);
|
if (!mount_uri.isEmpty())
|
||||||
|
uris << mount_uri;
|
||||||
|
|
||||||
QList<QUrl> ret;
|
QList<QUrl> ret;
|
||||||
|
|
||||||
if (url.isValid()) {
|
for (QString uri : uris) {
|
||||||
QRegExp device_re("usb/(\\d+)/(\\d+)");
|
|
||||||
if (device_re.indexIn(unix_device) >= 0) {
|
|
||||||
QUrlQuery url_query(url);
|
|
||||||
url_query.addQueryItem("busnum", device_re.cap(1));
|
|
||||||
url_query.addQueryItem("devnum", device_re.cap(2));
|
|
||||||
url.setQuery(url_query);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for file:// GIO URIs - we have to check whether they point to an ipod.
|
// gphoto2 gives invalid hostnames with []:, characters in
|
||||||
if (url.scheme() == "file") {
|
uri.replace(QRegExp("//\\[usb:(\\d+),(\\d+)\\]"), "//usb-\\1-\\2");
|
||||||
ret << MakeUrlFromLocalPath(url.path());
|
|
||||||
|
QUrl url;
|
||||||
|
|
||||||
|
if (uri.contains(QRegExp("..+:.*"))) {
|
||||||
|
url = QUrl::fromEncoded(uri.toUtf8());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
url = MakeUrlFromLocalPath(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.isValid()) {
|
||||||
|
|
||||||
|
// Special case for file:// GIO URIs - we have to check whether they point to an ipod.
|
||||||
|
if (url.isLocalFile() && IsIpod(url.path())) {
|
||||||
|
url.setScheme("ipod");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp device_re("usb/(\\d+)/(\\d+)");
|
||||||
|
if (device_re.indexIn(unix_device) >= 0) {
|
||||||
|
QUrlQuery url_query(url);
|
||||||
|
url_query.addQueryItem("busnum", device_re.cap(1));
|
||||||
|
url_query.addQueryItem("devnum", device_re.cap(2));
|
||||||
|
url.setQuery(url_query);
|
||||||
|
}
|
||||||
|
|
||||||
ret << url;
|
ret << url;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret << MakeUrlFromLocalPath(mount_point);
|
if (!mount_point.isEmpty()) {
|
||||||
|
ret << MakeUrlFromLocalPath(mount_point);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@@ -473,6 +497,7 @@ void GioLister::DeviceInfo::ReadVolumeInfo(GVolume *volume) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GioLister::DeviceInfo::ReadDriveInfo(GDrive *drive) {
|
void GioLister::DeviceInfo::ReadDriveInfo(GDrive *drive) {
|
||||||
|
|
||||||
this->drive.reset_without_add(drive);
|
this->drive.reset_without_add(drive);
|
||||||
if (!drive) return;
|
if (!drive) return;
|
||||||
|
|
||||||
@@ -485,6 +510,7 @@ QString GioLister::FindUniqueIdByMount(GMount *mount) const {
|
|||||||
if (info.mount == mount) return info.unique_id();
|
if (info.mount == mount) return info.unique_id();
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GioLister::FindUniqueIdByVolume(GVolume *volume) const {
|
QString GioLister::FindUniqueIdByVolume(GVolume *volume) const {
|
||||||
@@ -509,7 +535,7 @@ void GioLister::MountUnmountFinished(GObject *object, GAsyncResult *result, gpoi
|
|||||||
void GioLister::UnmountDevice(const QString &id) {
|
void GioLister::UnmountDevice(const QString &id) {
|
||||||
|
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
if (!devices_.contains(id)) return;
|
if (!devices_.contains(id) || !devices_[id].mount || devices_[id].volume_root_uri.startsWith("mtp://")) return;
|
||||||
|
|
||||||
const DeviceInfo &info = devices_[id];
|
const DeviceInfo &info = devices_[id];
|
||||||
|
|
||||||
@@ -537,7 +563,7 @@ void GioLister::UpdateDeviceFreeSpace(const QString &id) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
if (!devices_.contains(id)) return;
|
if (!devices_.contains(id) || !devices_[id].mount || devices_[id].volume_root_uri.startsWith("mtp://")) return;
|
||||||
|
|
||||||
DeviceInfo &device_info = devices_[id];
|
DeviceInfo &device_info = devices_[id];
|
||||||
|
|
||||||
@@ -563,10 +589,11 @@ void GioLister::UpdateDeviceFreeSpace(const QString &id) {
|
|||||||
|
|
||||||
bool GioLister::DeviceNeedsMount(const QString &id) {
|
bool GioLister::DeviceNeedsMount(const QString &id) {
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
return devices_.contains(id) && !devices_[id].mount;
|
return devices_.contains(id) && !devices_[id].mount && !devices_[id].volume_root_uri.startsWith("mtp://");
|
||||||
}
|
}
|
||||||
|
|
||||||
int GioLister::MountDevice(const QString &id) {
|
int GioLister::MountDevice(const QString &id) {
|
||||||
|
|
||||||
const int request_id = next_mount_request_id_++;
|
const int request_id = next_mount_request_id_++;
|
||||||
metaObject()->invokeMethod(this, "DoMountDevice", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(int, request_id));
|
metaObject()->invokeMethod(this, "DoMountDevice", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(int, request_id));
|
||||||
return request_id;
|
return request_id;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class DeviceManager;
|
|||||||
|
|
||||||
GPodDevice::GPodDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time)
|
GPodDevice::GPodDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time)
|
||||||
: ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time),
|
: ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time),
|
||||||
loader_thread_(new QThread(this)),
|
loader_thread_(new QThread()),
|
||||||
loader_(nullptr),
|
loader_(nullptr),
|
||||||
db_(nullptr) {}
|
db_(nullptr) {}
|
||||||
|
|
||||||
@@ -68,7 +68,13 @@ bool GPodDevice::Init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GPodDevice::~GPodDevice() {}
|
GPodDevice::~GPodDevice() {
|
||||||
|
if (loader_) {
|
||||||
|
loader_thread_->exit();
|
||||||
|
loader_->deleteLater();
|
||||||
|
loader_thread_->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GPodDevice::ConnectAsync() {
|
void GPodDevice::ConnectAsync() {
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ Itdb_iTunesDB *GPodLoader::TryLoad() {
|
|||||||
for (GList *tracks = db->tracks; tracks != nullptr; tracks = tracks->next) {
|
for (GList *tracks = db->tracks; tracks != nullptr; tracks = tracks->next) {
|
||||||
Itdb_Track *track = static_cast<Itdb_Track*>(tracks->data);
|
Itdb_Track *track = static_cast<Itdb_Track*>(tracks->data);
|
||||||
|
|
||||||
Song song;
|
Song song(Song::Source_Device);
|
||||||
song.InitFromItdb(track, prefix);
|
song.InitFromItdb(track, prefix);
|
||||||
song.set_directory_id(1);
|
song.set_directory_id(1);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -105,3 +106,42 @@ MtpConnection::~MtpConnection() {
|
|||||||
if (device_) LIBMTP_Release_Device(device_);
|
if (device_) LIBMTP_Release_Device(device_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MtpConnection::GetSupportedFiletypes(QList<Song::FileType> *ret) {
|
||||||
|
|
||||||
|
if (!device_) return false;
|
||||||
|
|
||||||
|
uint16_t *list = nullptr;
|
||||||
|
uint16_t length = 0;
|
||||||
|
|
||||||
|
if (LIBMTP_Get_Supported_Filetypes(device_, &list, &length) || !list || !length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
switch (LIBMTP_filetype_t(list[i])) {
|
||||||
|
case LIBMTP_FILETYPE_WAV: *ret << Song::FileType_WAV; break;
|
||||||
|
case LIBMTP_FILETYPE_MP2:
|
||||||
|
case LIBMTP_FILETYPE_MP3: *ret << Song::FileType_MPEG; break;
|
||||||
|
case LIBMTP_FILETYPE_WMA: *ret << Song::FileType_ASF; break;
|
||||||
|
case LIBMTP_FILETYPE_MP4:
|
||||||
|
case LIBMTP_FILETYPE_M4A:
|
||||||
|
case LIBMTP_FILETYPE_AAC: *ret << Song::FileType_MP4; break;
|
||||||
|
case LIBMTP_FILETYPE_FLAC:
|
||||||
|
*ret << Song::FileType_FLAC;
|
||||||
|
*ret << Song::FileType_OggFlac;
|
||||||
|
break;
|
||||||
|
case LIBMTP_FILETYPE_OGG:
|
||||||
|
*ret << Song::FileType_OggVorbis;
|
||||||
|
*ret << Song::FileType_OggSpeex;
|
||||||
|
*ret << Song::FileType_OggFlac;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qLog(Error) << "Unknown MTP file format" << LIBMTP_Get_Filetype_Description(LIBMTP_filetype_t(list[i]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(list);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,18 +25,23 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <memory>
|
||||||
#include <libmtp.h>
|
#include <libmtp.h>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
class MtpConnection {
|
#include "core/song.h"
|
||||||
|
|
||||||
|
class MtpConnection : public QObject, public std::enable_shared_from_this<MtpConnection> {
|
||||||
public:
|
public:
|
||||||
MtpConnection(const QUrl &url);
|
MtpConnection(const QUrl &url);
|
||||||
~MtpConnection();
|
~MtpConnection();
|
||||||
|
|
||||||
bool is_valid() const { return device_; }
|
bool is_valid() const { return device_; }
|
||||||
LIBMTP_mtpdevice_t *device() const { return device_; }
|
LIBMTP_mtpdevice_t *device() const { return device_; }
|
||||||
|
bool GetSupportedFiletypes(QList<Song::FileType> *ret);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DISABLE_COPY(MtpConnection);
|
Q_DISABLE_COPY(MtpConnection);
|
||||||
@@ -43,4 +49,4 @@ private:
|
|||||||
LIBMTP_mtpdevice_t *device_;
|
LIBMTP_mtpdevice_t *device_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MTPCONNECTION_H
|
#endif // MTPCONNECTION_H
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -50,7 +51,7 @@ class DeviceManager;
|
|||||||
bool MtpDevice::sInitialisedLibMTP = false;
|
bool MtpDevice::sInitialisedLibMTP = false;
|
||||||
|
|
||||||
MtpDevice::MtpDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time)
|
MtpDevice::MtpDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time)
|
||||||
: ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), loader_thread_(new QThread(this)), loader_(nullptr) {
|
: ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), loader_thread_(new QThread()), loader_(nullptr) {
|
||||||
|
|
||||||
if (!sInitialisedLibMTP) {
|
if (!sInitialisedLibMTP) {
|
||||||
LIBMTP_Init();
|
LIBMTP_Init();
|
||||||
@@ -59,7 +60,15 @@ MtpDevice::MtpDevice(const QUrl &url, DeviceLister *lister, const QString &uniqu
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MtpDevice::~MtpDevice() {}
|
MtpDevice::~MtpDevice() {
|
||||||
|
if (loader_) {
|
||||||
|
loader_thread_->exit();
|
||||||
|
loader_->deleteLater();
|
||||||
|
loader_ = nullptr;
|
||||||
|
db_busy_.unlock();
|
||||||
|
loader_thread_->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MtpDevice::Init() {
|
bool MtpDevice::Init() {
|
||||||
|
|
||||||
@@ -79,6 +88,12 @@ bool MtpDevice::Init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MtpDevice::NewConnection() {
|
||||||
|
|
||||||
|
connection_.reset(new MtpConnection(url_));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void MtpDevice::ConnectAsync() {
|
void MtpDevice::ConnectAsync() {
|
||||||
|
|
||||||
db_busy_.lock();
|
db_busy_.lock();
|
||||||
@@ -96,7 +111,9 @@ void MtpDevice::LoadFinished(bool success) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MtpDevice::LoaderError(const QString& message) { app_->AddError(message); }
|
void MtpDevice::LoaderError(const QString& message) {
|
||||||
|
app_->AddError(message);
|
||||||
|
}
|
||||||
|
|
||||||
bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
|
bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
|
||||||
|
|
||||||
@@ -104,7 +121,8 @@ bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
|
|||||||
db_busy_.lock();
|
db_busy_.lock();
|
||||||
|
|
||||||
// Connect to the device
|
// Connect to the device
|
||||||
connection_.reset(new MtpConnection(url_));
|
if (!connection_.get() || !connection_->is_valid()) NewConnection();
|
||||||
|
if (!connection_.get() || !connection_->is_valid()) return false;
|
||||||
|
|
||||||
// Did the caller want a list of supported types?
|
// Did the caller want a list of supported types?
|
||||||
if (supported_types) {
|
if (supported_types) {
|
||||||
@@ -129,7 +147,7 @@ static int ProgressCallback(uint64_t const sent, uint64_t const total, void cons
|
|||||||
|
|
||||||
bool MtpDevice::CopyToStorage(const CopyJob &job) {
|
bool MtpDevice::CopyToStorage(const CopyJob &job) {
|
||||||
|
|
||||||
if (!connection_->is_valid()) return false;
|
if (!connection_.get() || !connection_->is_valid()) return false;
|
||||||
|
|
||||||
// Convert metadata
|
// Convert metadata
|
||||||
LIBMTP_track_t track;
|
LIBMTP_track_t track;
|
||||||
@@ -140,9 +158,11 @@ bool MtpDevice::CopyToStorage(const CopyJob &job) {
|
|||||||
if (ret != 0) return false;
|
if (ret != 0) return false;
|
||||||
|
|
||||||
// Add it to our CollectionModel
|
// Add it to our CollectionModel
|
||||||
Song metadata_on_device;
|
Song metadata_on_device(Song::Source_Device);
|
||||||
metadata_on_device.InitFromMTP(&track, url_.host());
|
metadata_on_device.InitFromMTP(&track, url_.host());
|
||||||
metadata_on_device.set_directory_id(1);
|
metadata_on_device.set_directory_id(1);
|
||||||
|
metadata_on_device.set_artist(metadata_on_device.effective_albumartist());
|
||||||
|
metadata_on_device.set_albumartist("");
|
||||||
songs_to_add_ << metadata_on_device;
|
songs_to_add_ << metadata_on_device;
|
||||||
|
|
||||||
// Remove the original if requested
|
// Remove the original if requested
|
||||||
@@ -164,8 +184,6 @@ void MtpDevice::FinishCopy(bool success) {
|
|||||||
songs_to_add_.clear();
|
songs_to_add_.clear();
|
||||||
songs_to_remove_.clear();
|
songs_to_remove_.clear();
|
||||||
|
|
||||||
connection_.reset();
|
|
||||||
|
|
||||||
db_busy_.unlock();
|
db_busy_.unlock();
|
||||||
|
|
||||||
ConnectedDevice::FinishCopy(success);
|
ConnectedDevice::FinishCopy(success);
|
||||||
@@ -176,6 +194,8 @@ void MtpDevice::StartDelete() { StartCopy(nullptr); }
|
|||||||
|
|
||||||
bool MtpDevice::DeleteFromStorage(const DeleteJob &job) {
|
bool MtpDevice::DeleteFromStorage(const DeleteJob &job) {
|
||||||
|
|
||||||
|
if (!connection_.get() || !connection_->is_valid()) return false;
|
||||||
|
|
||||||
// Extract the ID from the song's URL
|
// Extract the ID from the song's URL
|
||||||
QString filename = job.metadata_.url().path();
|
QString filename = job.metadata_.url().path();
|
||||||
filename.remove('/');
|
filename.remove('/');
|
||||||
@@ -201,6 +221,7 @@ bool MtpDevice::GetSupportedFiletypes(QList<Song::FileType> *ret) {
|
|||||||
|
|
||||||
QMutexLocker l(&db_busy_);
|
QMutexLocker l(&db_busy_);
|
||||||
MtpConnection connection(url_);
|
MtpConnection connection(url_);
|
||||||
|
|
||||||
if (!connection.is_valid()) {
|
if (!connection.is_valid()) {
|
||||||
qLog(Warning) << "Error connecting to MTP device, couldn't get list of supported filetypes";
|
qLog(Warning) << "Error connecting to MTP device, couldn't get list of supported filetypes";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
class Application;
|
class Application;
|
||||||
class DeviceLister;
|
class DeviceLister;
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
|
class DeviceConnection;
|
||||||
class MtpConnection;
|
class MtpConnection;
|
||||||
class MtpLoader;
|
class MtpLoader;
|
||||||
struct LIBMTP_mtpdevice_struct;
|
struct LIBMTP_mtpdevice_struct;
|
||||||
@@ -54,6 +56,7 @@ class MtpDevice : public ConnectedDevice {
|
|||||||
static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; }
|
static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; }
|
||||||
|
|
||||||
bool Init();
|
bool Init();
|
||||||
|
void NewConnection();
|
||||||
void ConnectAsync();
|
void ConnectAsync();
|
||||||
|
|
||||||
bool GetSupportedFiletypes(QList<Song::FileType>* ret);
|
bool GetSupportedFiletypes(QList<Song::FileType>* ret);
|
||||||
@@ -68,6 +71,8 @@ class MtpDevice : public ConnectedDevice {
|
|||||||
bool DeleteFromStorage(const DeleteJob& job);
|
bool DeleteFromStorage(const DeleteJob& job);
|
||||||
void FinishDelete(bool success);
|
void FinishDelete(bool success);
|
||||||
|
|
||||||
|
MtpConnection *connection() { return connection_.get(); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void LoadFinished(bool success);
|
void LoadFinished(bool success);
|
||||||
void LoaderError(const QString& message);
|
void LoaderError(const QString& message);
|
||||||
@@ -87,7 +92,8 @@ class MtpDevice : public ConnectedDevice {
|
|||||||
SongList songs_to_add_;
|
SongList songs_to_add_;
|
||||||
SongList songs_to_remove_;
|
SongList songs_to_remove_;
|
||||||
|
|
||||||
std::unique_ptr<MtpConnection> connection_;
|
std::shared_ptr<MtpConnection> connection_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MTPDEVICE_H
|
#endif // MTPDEVICE_H
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,17 +28,19 @@
|
|||||||
|
|
||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
|
#include "connecteddevice.h"
|
||||||
|
#include "mtpdevice.h"
|
||||||
#include "mtpconnection.h"
|
#include "mtpconnection.h"
|
||||||
#include "mtploader.h"
|
#include "mtploader.h"
|
||||||
|
|
||||||
MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr<ConnectedDevice> device)
|
MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr<ConnectedDevice> device)
|
||||||
: QObject(nullptr),
|
: QObject(nullptr),
|
||||||
device_(device),
|
|
||||||
url_(url),
|
url_(url),
|
||||||
task_manager_(task_manager),
|
task_manager_(task_manager),
|
||||||
backend_(backend),
|
backend_(backend),
|
||||||
connection_(nullptr) {
|
device_(device) {
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,23 +64,28 @@ void MtpLoader::LoadDatabase() {
|
|||||||
|
|
||||||
bool MtpLoader::TryLoad() {
|
bool MtpLoader::TryLoad() {
|
||||||
|
|
||||||
MtpConnection dev(url_);
|
MtpDevice *device = dynamic_cast<MtpDevice*>(device_.get()); // FIXME
|
||||||
if (!dev.is_valid()) {
|
|
||||||
|
if (!device->connection() || !device->connection()->is_valid())
|
||||||
|
device->NewConnection();
|
||||||
|
|
||||||
|
if (!device->connection() || !device->connection()->is_valid()) {
|
||||||
emit Error(tr("Error connecting MTP device %1").arg(url_.toString()));
|
emit Error(tr("Error connecting MTP device %1").arg(url_.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the list of songs on the device
|
// Load the list of songs on the device
|
||||||
SongList songs;
|
SongList songs;
|
||||||
LIBMTP_track_t* tracks = LIBMTP_Get_Tracklisting_With_Callback(dev.device(), nullptr, nullptr);
|
LIBMTP_track_t* tracks = LIBMTP_Get_Tracklisting_With_Callback(device->connection()->device(), nullptr, nullptr);
|
||||||
while (tracks) {
|
while (tracks) {
|
||||||
LIBMTP_track_t *track = tracks;
|
LIBMTP_track_t *track = tracks;
|
||||||
|
|
||||||
Song song;
|
Song song(Song::Source_Device);
|
||||||
song.InitFromMTP(track, url_.host());
|
song.InitFromMTP(track, url_.host());
|
||||||
song.set_directory_id(1);
|
if (song.is_valid() && !song.artist().isEmpty() && !song.title().isEmpty()) {
|
||||||
songs << song;
|
song.set_directory_id(1);
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
tracks = tracks->next;
|
tracks = tracks->next;
|
||||||
LIBMTP_destroy_track_t(track);
|
LIBMTP_destroy_track_t(track);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
class TaskManager;
|
class TaskManager;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class ConnectedDevice;
|
class ConnectedDevice;
|
||||||
|
class MtpDevice;
|
||||||
class MtpConnection;
|
class MtpConnection;
|
||||||
|
|
||||||
class MtpLoader : public QObject {
|
class MtpLoader : public QObject {
|
||||||
@@ -57,13 +59,13 @@ class MtpLoader : public QObject {
|
|||||||
bool TryLoad();
|
bool TryLoad();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<ConnectedDevice> device_;
|
|
||||||
QThread *original_thread_;
|
|
||||||
|
|
||||||
QUrl url_;
|
QUrl url_;
|
||||||
TaskManager *task_manager_;
|
TaskManager *task_manager_;
|
||||||
CollectionBackend *backend_;
|
CollectionBackend *backend_;
|
||||||
MtpConnection *connection_;
|
std::shared_ptr<ConnectedDevice> device_;
|
||||||
|
std::shared_ptr<MtpConnection> connection_;
|
||||||
|
QThread *original_thread_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MTPLOADER_H
|
#endif // MTPLOADER_H
|
||||||
|
|||||||
@@ -125,10 +125,11 @@ QList<QUrl> Udisks2Lister::MakeDeviceUrls(const QString &id) {
|
|||||||
QList<QUrl> ret;
|
QList<QUrl> ret;
|
||||||
if (!device_data_.contains(id)) return ret;
|
if (!device_data_.contains(id)) return ret;
|
||||||
// Special case for Apple
|
// Special case for Apple
|
||||||
if(id.contains("iPod")) {
|
if (id.contains("iPod")) {
|
||||||
ret << MakeUrlFromLocalPath(device_data_[id].mount_paths.at(0));
|
ret << MakeUrlFromLocalPath(device_data_[id].mount_paths.at(0));
|
||||||
} else {
|
}
|
||||||
ret << QUrl::fromLocalFile(device_data_[id].mount_paths.at(0));
|
else {
|
||||||
|
ret << QUrl::fromLocalFile(device_data_[id].mount_paths.at(0));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> dest
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Organise::~Organise() {
|
||||||
|
if (thread_) {
|
||||||
|
thread_->quit();
|
||||||
|
thread_->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Organise::Start() {
|
void Organise::Start() {
|
||||||
|
|
||||||
if (thread_) return;
|
if (thread_) return;
|
||||||
@@ -95,6 +102,7 @@ void Organise::Start() {
|
|||||||
|
|
||||||
moveToThread(thread_);
|
moveToThread(thread_);
|
||||||
thread_->start();
|
thread_->start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Organise::ProcessSomeFiles() {
|
void Organise::ProcessSomeFiles() {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class Organise : public QObject {
|
|||||||
typedef QList<NewSongInfo> NewSongInfoList;
|
typedef QList<NewSongInfo> NewSongInfoList;
|
||||||
|
|
||||||
Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, bool albumcover, const NewSongInfoList &songs, bool eject_after);
|
Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, bool albumcover, const NewSongInfoList &songs, bool eject_after);
|
||||||
|
~Organise();
|
||||||
|
|
||||||
static const int kBatchSize;
|
static const int kBatchSize;
|
||||||
#ifdef HAVE_GSTREAMER
|
#ifdef HAVE_GSTREAMER
|
||||||
|
|||||||
Reference in New Issue
Block a user