Adding CD devices in devices tab. Fixes issue 701.

This commit is contained in:
Arnaud Bienner 2011-08-05 02:15:16 +02:00
parent 3ded9f29d2
commit f7859f591c
10 changed files with 424 additions and 10 deletions

View File

@ -66,6 +66,7 @@ pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(INDICATEQT indicate-qt)
pkg_check_modules(ARCHIVE libarchive)
pkg_check_modules(SPOTIFY libspotify>=0.0.8)
pkg_check_modules(CDIO libcdio)
if (WIN32)
find_package(ZLIB REQUIRED)

View File

@ -101,6 +101,8 @@ set(SOURCES
covers/coversearchstatisticsdialog.cpp
covers/kittenloader.cpp
devices/cddalister.cpp
devices/cddadevice.cpp
devices/connecteddevice.cpp
devices/devicedatabasebackend.cpp
devices/devicelister.cpp
@ -324,6 +326,8 @@ set(HEADERS
covers/coversearchstatisticsdialog.h
covers/kittenloader.h
devices/cddalister.h
devices/cddadevice.h
devices/connecteddevice.h
devices/devicedatabasebackend.h
devices/devicelister.h
@ -967,6 +971,7 @@ target_link_libraries(clementine_lib
${QTSINGLECOREAPPLICATION_LIBRARIES}
${QTIOCOMPRESSOR_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${CDIO_LIBRARIES}
dl
z
)

168
src/devices/cddadevice.cpp Normal file
View File

@ -0,0 +1,168 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QMutexLocker>
#include "core/logging.h"
#include "library/librarybackend.h"
#include "library/librarymodel.h"
#include "cddadevice.h"
CddaDevice::CddaDevice(const QUrl& url, DeviceLister* lister,
const QString& unique_id, DeviceManager* manager,
int database_id, bool first_time)
: ConnectedDevice(url, lister, unique_id, manager, database_id, first_time),
cdda_(NULL),
cdio_(NULL)
{
}
CddaDevice::~CddaDevice(){
if (cdio_)
cdio_destroy(cdio_);
if (cdda_)
gst_object_unref(GST_OBJECT(cdda_));
}
void CddaDevice::Init() {
QMutexLocker locker(&mutex_init_);
cdio_ = cdio_open (unique_id_.toLocal8Bit().constData(), DRIVER_DEVICE);
if (cdio_ == NULL) {
return;
}
// Create gstreamer cdda element
cdda_ = gst_element_make_from_uri (GST_URI_SRC, "cdda://", unique_id_.toLocal8Bit().constData());
if (cdda_ == NULL) {
model_->Reset();
return;
}
// Change the element's state to ready and paused, to be able to query it
if (gst_element_set_state(cdda_, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE ||
gst_element_set_state(cdda_, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
model_->Reset();
gst_element_set_state(cdda_, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(cdda_));
return;
}
// Get number of tracks
GstFormat fmt = gst_format_get_by_nick ("track");
GstFormat out_fmt = fmt;
gint64 num_tracks = 0;
if (!gst_element_query_duration (cdda_, &out_fmt, &num_tracks) || out_fmt != fmt) {
qLog(Error) << "Error while querying cdda GstElement";
model_->Reset();
gst_object_unref(GST_OBJECT(cdda_));
return;
}
SongList songs;
for (int track_number = 1; track_number <= num_tracks; track_number++) {
// Init song
Song song;
guint64 duration = 0;
// quint64 == ulonglong and guint64 == ulong, therefore we must cast
if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(cdda_)->tracks[track_number-1].tags,
GST_TAG_DURATION, &duration)) {
song.set_length_nanosec((quint64)duration);
}
song.set_id(track_number);
song.set_valid(true);
song.set_filetype(Song::Type_Cdda);
song.set_url(QUrl(QString("cdda://%1").arg(track_number)));
song.set_title(QString("Track %1").arg(track_number));
song.set_track(track_number);
songs << song;
}
song_count_ = num_tracks;
connect(this, SIGNAL(SongsDiscovered(const SongList&)), model_, SLOT(SongsDiscovered(const SongList&)));
emit SongsDiscovered(songs);
//emit SongCountUpdated(num_tracks);
// Generate MusicBrainz DiscId
gst_tag_register_musicbrainz_tags();
GstElement *pipe = gst_pipeline_new ("pipeline");
gst_bin_add (GST_BIN (pipe), cdda_);
gst_element_set_state (pipe, GST_STATE_READY);
gst_element_set_state (pipe, GST_STATE_PAUSED);
GstMessage *msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe),
GST_CLOCK_TIME_NONE,
GST_MESSAGE_TAG);
GstTagList *tags = NULL;
gst_message_parse_tag (msg, &tags);
char *string_mb = NULL;
if (gst_tag_list_get_string (tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) {
QString musicbrainz_discid(string_mb);
qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;
MusicBrainzClient *musicbrainz_client = new MusicBrainzClient(this);
connect(musicbrainz_client,
SIGNAL(Finished(const QString&, const QString&, MusicBrainzClient::ResultList)),
SLOT(AudioCDTagsLoaded(const QString&, const QString&, MusicBrainzClient::ResultList)));
musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
g_free(string_mb);
}
// Clean all the Gstreamer objects we have used: we don't need them anymore
gst_element_set_state (pipe, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(pipe));
gst_object_unref(GST_OBJECT(msg));
gst_object_unref(GST_OBJECT(tags));
}
void CddaDevice::AudioCDTagsLoaded(const QString& artist, const QString& album,
const MusicBrainzClient::ResultList& results) {
MusicBrainzClient *musicbrainz_client = qobject_cast<MusicBrainzClient*>(sender());
musicbrainz_client->deleteLater();
SongList songs;
int track_number = 1;
if (results.size() == 0)
return;
model_->Reset();
foreach (const MusicBrainzClient::Result& ret, results) {
Song song;
song.set_artist(artist);
song.set_album(album);
song.set_title(ret.title_);
song.set_length_nanosec(ret.duration_msec_ * kNsecPerMsec);
song.set_id(track_number);
song.set_track(track_number);
// We need to set url: that's how playlist will find the correct item to update
song.set_url(QUrl(QString("cdda://%1").arg(track_number++)));
songs << song;
}
connect(this, SIGNAL(SongsDiscovered(const SongList&)), model_, SLOT(SongsDiscovered(const SongList&)));
emit SongsDiscovered(songs);
}
void CddaDevice::Refresh() {
if ((cdio_ && cdda_) && /* already init... */
!cdio_get_media_changed(cdio_) /* ...and hasn't change since last time */) {
return;
}
// Check if mutex is already token (i.e. init is already taking place)
if (!mutex_init_.tryLock()) {
return;
}
mutex_init_.unlock();
Init();
}

59
src/devices/cddadevice.h Normal file
View File

@ -0,0 +1,59 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CDDADEVICE_H
#define CDDADEVICE_H
#include <cdio/cdio.h>
#include <gst/cdda/gstcddabasesrc.h>
#include <QMutex>
#include "connecteddevice.h"
#include "core/song.h"
#include "musicbrainz/musicbrainzclient.h"
class CddaDevice: public ConnectedDevice {
Q_OBJECT
public:
Q_INVOKABLE CddaDevice(const QUrl& url, DeviceLister* lister,
const QString& unique_id, DeviceManager* manager,
int database_id, bool first_time);
~CddaDevice();
void Init();
void Refresh();
bool CopyToStorage(const MusicStorage::CopyJob&) { return true; }
bool DeleteFromStorage(const MusicStorage::DeleteJob&) { return true; }
static QStringList url_schemes() { return QStringList() << "cdda"; }
signals:
void SongsDiscovered(const SongList& songs);
private slots:
void AudioCDTagsLoaded(const QString& artist, const QString& album,
const MusicBrainzClient::ResultList& results);
private:
GstElement *cdda_;
CdIo_t *cdio_;
QMutex mutex_init_;
};
#endif

104
src/devices/cddalister.cpp Normal file
View File

@ -0,0 +1,104 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cdio/cdio.h>
#include <QMutex>
#include <QThread>
#include <QWaitCondition>
#include "cddalister.h"
#include "core/logging.h"
#include "core/song.h"
QStringList CddaLister::DeviceUniqueIDs() {
return devices_list_;
}
QVariantList CddaLister::DeviceIcons(const QString&) {
QVariantList icons;
icons << QString("media-optical");
return icons;
}
QString CddaLister::DeviceManufacturer(const QString& id) {
CdIo_t *cdio = cdio_open (id.toLocal8Bit().constData(), DRIVER_DEVICE);
cdio_hwinfo_t cd_info;
if (cdio_get_hwinfo(cdio, &cd_info)) {
cdio_destroy(cdio);
return QString(cd_info.psz_vendor);
}
cdio_destroy(cdio);
return QString();
}
QString CddaLister::DeviceModel(const QString& id) {
CdIo_t *cdio = cdio_open (id.toLocal8Bit().constData(), DRIVER_DEVICE);
cdio_hwinfo_t cd_info;
if (cdio_get_hwinfo(cdio, &cd_info)) {
cdio_destroy(cdio);
return QString(cd_info.psz_model);
}
cdio_destroy(cdio);
return QString();
}
quint64 CddaLister::DeviceCapacity(const QString&) {
return 0;
}
quint64 CddaLister::DeviceFreeSpace(const QString&) {
return 0;
}
QVariantMap CddaLister::DeviceHardwareInfo(const QString&) {
return QVariantMap();
}
QString CddaLister::MakeFriendlyName(const QString& id) {
CdIo_t *cdio = cdio_open (id.toLocal8Bit().constData(), DRIVER_DEVICE);
cdio_hwinfo_t cd_info;
if (cdio_get_hwinfo(cdio, &cd_info)) {
cdio_destroy(cdio);
return QString(cd_info.psz_model);
}
cdio_destroy(cdio);
return QString();
return QString("CD (") + id + ")";
}
QList<QUrl> CddaLister::MakeDeviceUrls(const QString& id) {
return QList<QUrl>() << QUrl("cdda://" + id);
}
void CddaLister::UnmountDevice(const QString& id) {
cdio_eject_media_drive(id.toLocal8Bit().constData());
}
void CddaLister::UpdateDeviceFreeSpace(const QString&) {
}
void CddaLister::Init() {
char **devices = cdio_get_devices(DRIVER_DEVICE);
for (; *devices != NULL; ++devices) {
if (strcmp("/dev/cdrom", *devices) == 0)
continue;
QString device(*devices);
devices_list_ << device;
emit DeviceAdded(device);
}
}

51
src/devices/cddalister.h Normal file
View File

@ -0,0 +1,51 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CDDALISTER_H
#define CDDALISTER_H
#include <gst/cdda/gstcddabasesrc.h>
#include <QStringList>
#include "devicelister.h"
class CddaLister : public DeviceLister {
Q_OBJECT
public:
CddaLister() {}
QStringList DeviceUniqueIDs();
QVariantList DeviceIcons(const QString& id);
QString DeviceManufacturer(const QString& id);
QString DeviceModel(const QString& id);
quint64 DeviceCapacity(const QString& id);
quint64 DeviceFreeSpace(const QString& id);
QVariantMap DeviceHardwareInfo(const QString& id);
QString MakeFriendlyName(const QString&);
QList<QUrl> MakeDeviceUrls(const QString&);
void UnmountDevice(const QString&);
void UpdateDeviceFreeSpace(const QString&);
void Init();
private:
QStringList devices_list_;
};
#endif // CDDALISTER_H

View File

@ -44,6 +44,9 @@ public:
~ConnectedDevice();
virtual void Init() = 0;
// 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
virtual void Refresh() { }
virtual TranscodeMode GetTranscodeMode() const;
virtual Song::FileType GetTranscodeFormat() const;

View File

@ -27,6 +27,9 @@
#include "core/utilities.h"
#include "ui/iconloader.h"
#include "cddalister.h"
#include "cddadevice.h"
#ifdef Q_OS_DARWIN
# include "macdevicelister.h"
#endif
@ -183,6 +186,7 @@ DeviceManager::DeviceManager(BackgroundThread<Database>* database,
connected_devices_model_ = new DeviceStateFilterModel(this);
connected_devices_model_->setSourceModel(this);
AddLister(new CddaLister);
#ifdef HAVE_DEVICEKIT
AddLister(new DeviceKitLister);
#endif
@ -201,6 +205,7 @@ DeviceManager::DeviceManager(BackgroundThread<Database>* database,
AddDeviceClass<AfcDevice>();
#endif
AddDeviceClass<CddaDevice>();
AddDeviceClass<FilesystemDevice>();
#ifdef HAVE_LIBGPOD
@ -241,6 +246,8 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const {
if (info.size_)
text = text + QString(" (%1)").arg(Utilities::PrettySize(info.size_));
if (info.device_.get())
info.device_->Refresh();
return text;
}
@ -303,16 +310,19 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const {
if (!info.device_) {
if (info.database_id_ == -1 &&
!info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) {
boost::scoped_ptr<QMessageBox> dialog(new QMessageBox(
QMessageBox::Information, tr("Connect device"),
tr("This is the first time you have connected this device. Clementine will now scan the device to find music files - this may take some time."),
QMessageBox::Cancel));
QPushButton* connect =
dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole);
dialog->exec();
// Don't ask user if it is a CD device
if (info.device_ && !dynamic_cast<CddaDevice*>(info.device_.get())) {
boost::scoped_ptr<QMessageBox> dialog(new QMessageBox(
QMessageBox::Information, tr("Connect device"),
tr("This is the first time you have connected this device. Clementine will now scan the device to find music files - this may take some time."),
QMessageBox::Cancel));
QPushButton* connect =
dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole);
dialog->exec();
if (dialog->clickedButton() != connect)
return QVariant();
if (dialog->clickedButton() != connect)
return QVariant();
}
}
const_cast<DeviceManager*>(this)->Connect(index.row());
@ -389,6 +399,10 @@ int DeviceManager::FindDeviceByUrl(const QList<QUrl>& urls) const {
void DeviceManager::PhysicalDeviceAdded(const QString &id) {
DeviceLister* lister = qobject_cast<DeviceLister*>(sender());
qLog(Info) << lister->MakeDeviceUrls(id);
if (dynamic_cast<GioLister*>(lister)) {
qLog(Debug) << "It's GIO lister";
}
qLog(Info) << "Device added:" << id;

View File

@ -217,6 +217,9 @@ void GioLister::VolumeAdded(GVolume* volume) {
info.ReadMountInfo(g_volume_get_mount(volume));
if (!info.is_suitable())
return;
if (info.volume_root_uri.startsWith("cdda"))
// Audio CD devices are already handled by CDDA lister
return;
{
QMutexLocker l(&mutex_);
@ -249,6 +252,9 @@ void GioLister::MountAdded(GMount* mount) {
info.ReadDriveInfo(g_mount_get_drive(mount));
if (!info.is_suitable())
return;
if (info.volume_root_uri.startsWith("cdda"))
// Audio CD devices are already handled by CDDA lister
return;
QString old_id;
{
@ -275,8 +281,9 @@ void GioLister::MountAdded(GMount* mount) {
if (!old_id.isEmpty())
emit DeviceChanged(old_id);
else
else {
emit DeviceAdded(info.unique_id());
}
}
void GioLister::MountChanged(GMount* mount) {

View File

@ -856,6 +856,8 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type,
}
FinishItem(type, signal, create_divider, parent, item);
if (s.url().scheme() == "cdda")
item->lazy_loaded = true;
return item;
}