Commit 9bfe4313 authored by Matěj Laitl's avatar Matěj Laitl
Browse files

Complete iPod collection rewrite (also supports iPad, iPhone)

This is a result of 3-month effort to make Amarok iPod-like device
support future-proof and less buggy by using more modern MemoryMeta
framework to manage tracks internally.

The new plugin still uses libgpod [1] to access the devices and
supports all devices supported by it. The newest models may need the
infamous libashab.so library.

FEATURES:
 * Small configuration dialog for iPods that shows troubleshooting information
   and allows to change iPod name.
 * Improved usability of iPod playlists: iPod collection automatically transfers
   tracks dropped to iPod playlists to iPod when it is needed.
 * Tracks can now be transcoded when transferring them to iPod.

CHANGES:
 * optional libgpod dependency raised to 0.8.2 to support newest iPods.
 * Amarok now prevents accidental unmounting of iPods in (small) time-frames
   when iTunes database on iPod is not yet updated.
 * Amarok detects when iPod is to be ejected from system and gracefully
   disconnects it when it occurs.
 * Hitting the eject button on iPod collection ejects it also from the system.
 * iPod collection now detects whether iPod is safe to write and marks iPod
   as read-only if not. This prevents "iPod shows 0 tracks" problem.
 * Correct progress bar advancement when transferring tracks to iPod.
 * iPod Collection supports multiple simultaneous cancellable transfers.
 * Improved dialog to initialize iPod.

BUGFIXES:
 * Detection and elimination of stale and orphaned iPod tracks now works
   correctly; users are notified about these when iPod is plugged in.
 * iPod playlists now work correctly.
 * Show correct error when transferring unsupported files to iPod.

[1] http://www.gtkpod.org/wiki/Libgpod

FEATURE: 291722
FEATURE: 139454
FEATURE: 219963
BUG: 279797
BUG: 289304
BUG: 234876
FIXED-IN: 2.6
DIGEST: Amarok's iPod support is completely rewritten fixing many bugs
        and adding features
parent bf72e3f9
......@@ -145,10 +145,11 @@ if( WITH_PLAYER )
if( WITH_IPOD )
find_package(Ipod)
SET(IPOD_MIN_VERSION "0.8.2")
if( IPOD_FOUND AND NOT WIN32 )
macro_ensure_version("0.7.93" ${IPOD_VERSION} IPOD_FOUND)
macro_ensure_version(${IPOD_MIN_VERSION} ${IPOD_VERSION} IPOD_FOUND)
endif( IPOD_FOUND AND NOT WIN32 )
macro_log_feature( IPOD_FOUND "libgpod" "Support Apple iPod audio devices" "http://sourceforge.net/projects/gtkpod/" FALSE "0.7.93" "" )
macro_log_feature( IPOD_FOUND "libgpod" "Support Apple iPod/iPad/iPhone audio devices" "http://sourceforge.net/projects/gtkpod/" FALSE ${IPOD_MIN_VERSION} "" )
macro_optional_find_package(GDKPixBuf)
macro_log_feature( GDKPIXBUF_FOUND "GDK-PixBuf" "Support for artwork on iPod audio devices via GDK-PixBuf" "http://developer.gnome.org/arch/imaging/gdkpixbuf.html" FALSE "2.0.x" "" )
endif( WITH_IPOD )
......
......@@ -4,6 +4,11 @@ Amarok ChangeLog
Version 2.6-Beta 1
FEATURES:
* Small configuration dialog for iPods that shows troubleshooting information
and allows to change iPod name.
* Improved usability of iPod playlists: iPod collection automatically transfers
tracks dropped to iPod playlists to iPod when it is needed.
* Tracks can now be transcoded when transferring them to iPod. (BR 291722)
* A diagnostic dialog reporting versions of Amarok, Qt, KDE and phonon libraries
as well as the used phonon backend and status of plugins and scripts.
Patch by Andrzej Hunt.
......@@ -33,6 +38,18 @@ Version 2.6-Beta 1
vice versa. (BR 142579)
CHANGES:
* optional libgpod dependency raised to 0.8.2 to support newest iPods.
* Amarok now prevents accidental unmounting of iPods in (small) time-frames
when iTunes database on iPod is not yet updated.
* Amarok detects when iPod is to be ejected from system and gracefully
disconnects it when it occurs.
* Hitting the eject button on iPod collection ejects it also from the system.
* iPod collection now detects whether iPod is safe to write and marks iPod
as read-only if not. This prevents "iPod shows 0 tracks" problem.
* Correct progress bar advancement when transferring tracks to iPod. (BR 139454)
* iPod Collection supports multiple simultaneous cancellable transfers.
(BR 219963)
* Improved dialog to initialize iPod. (BR 279797)
* Load tracks in playlists asynchronously using proxy tracks. (BR 295199)
* It is now possible to transcode tracks when moving them. (BR 280526)
* Drag & drop of tracks and playlists to Saved Playlists works in all cases.
......@@ -51,6 +68,10 @@ Version 2.6-Beta 1
"1.2 GB free" is shown instead of "85% used"; thicker capacity bar.
BUGFIXES:
* Detection and elimination of stale and orphaned iPod tracks now works
correctly; users are notified about these when iPod is plugged in.
* iPod playlists now work correctly. (BR 289304)
* Show correct error when transferring unsupported files to iPod. (BR 234876)
* Fix m4a tracks transferred as mp4 videos to iPods. Affected users need to
fully rescan their collection. (BR 268238)
* Combination of double click and context menu actions caused multiple
......
......@@ -65,10 +65,10 @@ Required
Optional
* iPod support requires:
* libgpod 0.7.93 (or newer)
* libgpod 0.8.2 (or newer)
http://www.gtkpod.org/libgpod/
* GDKPixBuf 2.0 (or newer)
(For artwork support)
(For optional artwork support)
http://library.gnome.org/devel/gdk-pixbuf/stable/
* libmtp 1.0.0 (or newer)
......
......@@ -10,7 +10,7 @@ add_subdirectory( daap )
# NOTE: disabled until ported to new framework
add_subdirectory( audiocd )
#add_subdirectory( ipodcollection )
add_subdirectory( ipodcollection )
add_subdirectory( mtpcollection )
add_subdirectory( umscollection )
add_subdirectory( db )
......
if(NOT GDKPIXBUF_FOUND)
set(GDKPIXBUF_INCLUDE_DIR "")
set(GDKPIXBUF_LIBRARY "")
endif(NOT GDKPIXBUF_FOUND)
if(IPOD_FOUND AND WITH_IPOD)
include_directories(
${CMAKE_BINARY_DIR}/src
${KDE4_INCLUDE_DIR}
${QT_INCLUDES}
${GLIB2_INCLUDE_DIR}
${GDKPIXBUF_INCLUDE_DIR}
${GOBJECT_INCLUDE_DIR}
${IPOD_INCLUDE_DIRS}
)
########### set macros for the ipod collection plugin ##########
# Generate config-ipodcollection.h
configure_file(config-ipodcollection.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/config-ipodcollection.h
)
########### next target ################
set(amarok_collection-ipodcollection_PART_SRCS
IpodCollection.cpp
IpodCollectionFactory.cpp
IpodCollectionLocation.cpp
IpodMeta.cpp
IpodMetaEditCapability.cpp
IpodPlaylist.cpp
IpodPlaylistProvider.cpp
jobs/IpodCopyTracksJob.cpp
jobs/IpodDeleteTracksJob.cpp
jobs/IpodParseTracksJob.cpp
jobs/IpodWriteDatabaseJob.cpp
support/IphoneMountPoint.cpp
support/IpodDeviceHelper.cpp
support/IpodTranscodeCapability.cpp
)
kde4_add_ui_files(amarok_collection-ipodcollection_PART_SRCS
support/IpodConfiguration.ui
)
link_directories(${IPOD_LIBRARY_DIRS})
kde4_add_plugin(amarok_collection-ipodcollection ${amarok_collection-ipodcollection_PART_SRCS})
target_link_libraries(amarok_collection-ipodcollection
amarokcore
amaroklib
amarok-transcoding
${KDE4_KDECORE_LIBS}
${KDE4_KIO_LIBS}
${KDE4_SOLID_LIBRARY}
${KDE4_THREADWEAVER_LIBRARIES}
${QT_QTGUI_LIBRARY}
${GLIB2_LIBRARIES}
${GDKPIXBUF_LIBRARY}
${IPOD_LIBRARIES}
)
install(TARGETS
amarok_collection-ipodcollection
DESTINATION
${PLUGIN_INSTALL_DIR}
)
########### install files ###############
install(FILES
amarok_collection-ipodcollection.desktop
DESTINATION
${SERVICES_INSTALL_DIR}
)
endif (IPOD_FOUND AND WITH_IPOD)
This diff is collapsed.
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz *
* *
* This program 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 2 of the License, or (at your option) any later *
* version. *
* *
* This program 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 *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef IPODCOLLECTION_H
#define IPODCOLLECTION_H
#include "ui_IpodConfiguration.h"
#include "core/collections/Collection.h"
#include <QMutex>
#include <QSharedPointer>
#include <QTimer>
namespace Collections { class MemoryCollection; }
namespace IpodMeta { class Track; }
class IphoneMountPoint;
class IpodPlaylistProvider;
class QTemporaryFile;
struct _Itdb_iTunesDB;
typedef _Itdb_iTunesDB Itdb_iTunesDB;
class IpodCollection : public Collections::Collection, public Meta::Observer
{
Q_OBJECT
public:
static const QString s_uidUrlProtocol;
/**
* Creates an iPod collection on top of already-mounted filesystem.
*
* @param mountPoint actual iPod mount point to use, must be already mounted and
* accessible. When eject is requested, solid StorageAccess with this mount point
* is searched for to perform unmounting.
*/
explicit IpodCollection( const QDir &mountPoint );
/**
* Creates an iPod collection on top of not-mounted iPhone/iPad by accessing it
* using libimobiledevice by its 40-digit device UUID. UUID may be empty which
* means "any connected iPhone/iPad".
*/
explicit IpodCollection( const QString &uuid );
virtual ~IpodCollection();
// TrackProvider methods:
virtual bool possiblyContainsTrack( const KUrl &url ) const;
virtual Meta::TrackPtr trackForUrl( const KUrl &url );
// CollectionBase methods:
virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const;
virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type );
// Collection methods:
virtual Collections::QueryMaker *queryMaker();
virtual QString uidUrlProtocol() const;
virtual QString collectionId() const;
virtual QString prettyName() const;
virtual KIcon icon() const;
virtual bool hasCapacity() const;
virtual float usedCapacity() const;
virtual float totalCapacity() const;
virtual Collections::CollectionLocation *location();
virtual bool isWritable() const;
virtual bool isOrganizable() const;
// Observer methods:
virtual void metadataChanged( Meta::TrackPtr track );
// so that the compiler doesn't complain about hidden virtual functions:
using Meta::Observer::metadataChanged;
// IpodCollection methods:
/**
* Get local mount point. Can return QString() in case no reasonamble mountpoint
* is available
*/
QString mountPoint();
/**
* Return number of bytes that should be kept free in iPod for database operations.
* CollectionLocation should try hard not to occupy this safety margin.
*/
float capacityMargin() const;
/**
* Return a list of file formats (compatible with Meta::Track::type()) current iPod
* is able to play.
*/
QStringList supportedFormats() const;
/**
* Return pointer to playlist provider associated with this iPod. May be null in
* special cases (iPod not yet initialised etc.)
*/
Playlists::UserPlaylistProvider *playlistProvider() const;
Meta::TrackPtr trackForUidUrl( const QString &uidUrl );
signals:
/**
* Start a count-down that emits updated() signal after it expires.
* Resets the timer to original timeout if already running. This is to ensure
* that we emit update() max. once per <timeout> for batch updates.
*
* Timers can only be started from "their" thread so use signals & slots for that.
*/
void startUpdateTimer();
/**
* Start a count-down that initiates iTunes database wrtiging after it expires.
* Resets the timer to original timeout if already running. This is to ensure
* that we dont write the database all the time for batch updates.
*
* Timers can only be started from "their" thread so use signals & slots for that.
*/
void startWriteDatabaseTimer();
public slots:
/**
* Destroy the collection, try to write back iTunes database (if dirty)
*/
void slotDestroy();
/**
* Destroy the collection, write back iTunes db (if dirty) and try to eject the
* iPod from system
*/
void slotEject();
/**
* Shows the configuration dialog in a non-modal window. If m_itdb is null, shows
* some info and a button to try to initialize iPod.
*/
void slotShowConfigureDialog( const QString &errorMessage = QString() );
private slots:
/**
* Tries to initialize iPod, read the database, add tracks. (Re)shows the
* configuration dialog with info about initialization.
*/
void slotInitialize();
/**
* Sets iPod name to the name in configure dialog.
*/
void slotApplyConfiguration();
/**
* Starts a timer that emits updated() signal after 2 seconds.
*/
void slotStartUpdateTimer();
/**
* Starts a timer that initiates iTunes database writing after 30 seconds.
*/
void slotStartWriteDatabaseTimer();
/**
* Enqueues a job in a thread that writes iTunes database back to iPod. Should
* only be called from m_writeDatabaseTimer's timeout() signal. (with exception
* when IpodCollection is about to destroy itself)
*/
void slotInitiateDatabaseWrite();
/**
* Tries to unmount underlying solid device. You must try to write database before
* calling this. Emits remove() before returning.
*/
void slotPerformTeardownAndRemove();
private:
friend class IpodCopyTracksJob;
friend class IpodDeleteTracksJob;
friend class IpodParseTracksJob;
friend class IpodWriteDatabaseJob;
friend class IpodPlaylistProvider;
static const QStringList s_audioFileTypes;
static const QStringList s_videoFileTypes;
static const QStringList s_audioVideoFileTypes;
/**
* In-fact construcor used to share code between different constructors.
*/
void init();
// method for IpodParseTracksJob and IpodCopyTracksJob:
/**
* Add an iPod track to the collection.
*
* This method adds it to the collection, master playlist (if not already there)
* etc. The file must be already physically copied to iPod. (Re)Sets track's
* collection to this collection. Takes ownership of the track (passes it to
* KSharedPtr)
*
* This method is thread-safe.
*
* @return pointer to newly added track if successful, null pointer otherwise
*/
Meta::TrackPtr addTrack( IpodMeta::Track *track );
// method for IpodDeleteTracksJob:
/**
* Removes a track from iPod collection. Does not delete the file physically,
* caller must do it after calling this method.
*
* @param track a track from associated MemoryCollection to delete. Accepts also
* underlying IpodMeta::Track, this is treated as if MemoryMeta::Track track
* proxy it was passed.
*
* This method is thread-safe.
*/
void removeTrack( const Meta::TrackPtr &track );
// method for IpodWriteDatabaseJob and destructor:
/**
* Calls itdb_write() directly. Logs a message about success/failure in Amarok
* interface.
*/
bool writeDatabase();
KDialog *m_configureDialog;
Ui::IpodConfiguration m_configureDialogUi;
QSharedPointer<Collections::MemoryCollection> m_mc;
/**
* pointer to libgpod iTunes database. If null, this collection is invalid
* (not yet initialised). Can only be changed with m_itdbMutex hold.
*/
Itdb_iTunesDB *m_itdb;
QMutex m_itdbMutex;
QTimer m_updateTimer;
QTimer m_writeDatabaseTimer;
QTemporaryFile *m_preventUnmountTempFile;
QString m_mountPoint;
IphoneMountPoint *m_iphoneAutoMountpoint;
QString m_prettyName;
IpodPlaylistProvider *m_playlistProvider;
QAction *m_configureAction;
QAction *m_ejectAction;
};
#endif // IPODCOLLECTION_H
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz *
* *
* This program 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 2 of the License, or (at your option) any later *
* version. *
* *
* This program 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 *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodCollectionFactory.h"
#include "IpodCollection.h"
#include "core/support/Debug.h"
#include <solid/device.h>
#include <solid/devicenotifier.h>
#include <solid/portablemediaplayer.h>
#include <solid/storageaccess.h>
AMAROK_EXPORT_COLLECTION( IpodCollectionFactory, ipodcollection )
IpodCollectionFactory::IpodCollectionFactory( QObject *parent, const QVariantList &args )
: CollectionFactory( parent, args )
{
m_info = KPluginInfo( "amarok_collection-ipodcollection.desktop", "services" );
}
IpodCollectionFactory::~IpodCollectionFactory()
{
}
void
IpodCollectionFactory::init()
{
connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
SLOT(slotAddSolidDevice(QString)) );
connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
SLOT(slotRemoveSolidDevice(QString)) );
// detect iPods that were already connected on startup
QString query( "[IS StorageAccess OR IS PortableMediaPlayer]" );
QList<Solid::Device> ipodDevices = Solid::Device::listFromQuery( query );
foreach( Solid::Device device, ipodDevices )
{
if( identifySolidDevice( device.udi() ) )
createCollectionForSolidDevice( device.udi() );
}
m_initialized = true;
}
void
IpodCollectionFactory::slotAddSolidDevice( const QString &udi )
{
if( m_collectionMap.contains( udi ) )
return; // a device added twice (?)
if( identifySolidDevice( udi ) )
createCollectionForSolidDevice( udi );
}
void
IpodCollectionFactory::slotAccessibilityChanged( bool accessible, const QString &udi )
{
if( accessible )
slotAddSolidDevice( udi );
else
slotRemoveSolidDevice( udi );
}
void
IpodCollectionFactory::slotRemoveSolidDevice( const QString &udi )
{
IpodCollection *collection = m_collectionMap.take( udi );
if( collection )
collection->slotDestroy();
}
void
IpodCollectionFactory::slotRemoveAndTeardownSolidDevice( const QString &udi )
{
IpodCollection *collection = m_collectionMap.take( udi );
if( collection )
collection->slotEject();
}
void
IpodCollectionFactory::slotCollectionDestroyed( QObject *collection )
{
// remove destroyed collection from m_collectionMap
QMutableMapIterator<QString, IpodCollection *> it( m_collectionMap );
while( it.hasNext() )
{
it.next();
if( (QObject *) it.value() == collection )
it.remove();
}
}
/**
* @brief Return true if device is identified as iPod-compatible using product and vendor.
*
* @param device Solid device to identify
* @return true if the device is iPod-like, false if it cannot be proved.
**/
static bool
deviceIsRootIpodDevice( const Solid::Device &device )
{
if( !device.vendor().contains( "Apple", Qt::CaseInsensitive ) )
return false;
return device.product().startsWith( "iPod" )
|| device.product().startsWith( "iPhone" )
|| device.product().startsWith( "iPad" );
}
/**
* @brief Returns true if device is identified as iPod-compatible using
* PortableMediaPlayer interface.
*
* @param device Solid device to identify
* @return true if the device is iPod-like, false if it cannot be proved.
**/
static bool
deviceIsPMPIpodDevice( const Solid::Device &device )
{
/* This should be the one and only way to identify iPod-likes, but as of KDE 4.7.2,
* solid does not attach PortableMediaPlayer to iPods at all and its
* PortableMediaPlayer implementations is just a stub. This would also need
* media-device-info package to be installed.
*/
const Solid::PortableMediaPlayer *pmp = device.as<Solid::PortableMediaPlayer>();
if( !pmp )
return false;
debug() << "Device supported PMP protocols:" << pmp->supportedProtocols();
return pmp->supportedProtocols().contains( "ipod", Qt::CaseInsensitive );
}
bool
IpodCollectionFactory::identifySolidDevice( const QString &udi ) const
{
DEBUG_BLOCK
Solid::Device device( udi );
/* Start with device to identify, opportunistically try to identify it as
* iPod-compatible. If found not, try its parent. Repeat until parent device is
* valid.
*
* This method DOES return false positives for iPod-like devices which are itself
* storage drives, but not storage volumes (such as sdc). This is needed for
* iPhone-like devices that have extra code to mount them in IpodHandler. IpodHandler
* gracefully fails in case this is old-school iPod, so this shouldn't hurt, only side
* effect is that other connection assistant are not tried for these false-identified
* devices. It would be of course great if iPhone-like devices could be distinguished
* right here - KDE's Solid should be able to help us with it in future, but as of
* KDE 4.7.4 it tells us nothing.
*
* @see MediaDeviceCache::slotAddSolidDevice() for a quirk that is currently also
* needed for proper identification of iPhone-like devices. THIS IS CURRENTLY NOT
* NEEDED FOR IPOD COLLECTION REWRITE, BUT LEFT FOR REFERENCE.
*/
while ( device.isValid() )
{
if( deviceIsPMPIpodDevice( device ) )
{
debug() << "Device" << device.udi() << "identified iPod-like using "
"PortableMediaPlayer interface";
return true;
}
if( deviceIsRootIpodDevice( device ) )
{
debug() << "Device" << device.udi()