Sunday, May 3, 2009

Amarok: Quick Status Report on Refactoring, Git Rocks

Between the business of school &c. I've been at work making Media Device code smaller and easier to write, as promised. Here is a preview of the difference in the amount of code required before, and in my git branch after. Let's just look at the MediaDeviceCollectionFactory subclass for Ipods, which keeps track of Ipods connected and talks to Solid.

Before:


AMAROK_EXPORT_PLUGIN( IpodCollectionFactory )

IpodCollectionFactory::IpodCollectionFactory()
: Amarok::CollectionFactory()
{
//nothing to do
}

IpodCollectionFactory::~IpodCollectionFactory()
{
DEBUG_BLOCK
}

void
IpodCollectionFactory::init()
{
DEBUG_BLOCK

// connect to the monitor

// connect( this, SIGNAL( ipodDetected( const MediaDeviceInfo & ) ),
// MediaDeviceMonitor::instance(), SIGNAL( deviceDetected( const MediaDeviceInfo & ) ) );

connect( MediaDeviceMonitor::instance(), SIGNAL( ipodReadyToConnect( const QString &, const QString & ) ),
SLOT( ipodDetected( const QString &, const QString & ) ) );

// HACK: emitting old signal to avoid refactoring applet yet
connect( this, SIGNAL( tellIpodDetected( const QString &, const QString & ) ),
MediaDeviceMonitor::instance(), SIGNAL( ipodDetected( const QString &, const QString & ) ) );

connect( MediaDeviceMonitor::instance(), SIGNAL( ipodReadyToDisconnect( const QString & ) ),
SLOT( deviceRemoved( const QString & ) ) );

connect( MediaDeviceMonitor::instance(), SIGNAL( deviceRemoved( const QString & ) ), SLOT( deviceRemoved( const QString & ) ) );

// HACK: Usability: Force auto-connection of device upon detection
checkDevicesForIpod();
}

void
IpodCollectionFactory::ipodDetected( const QString &mountPoint, const QString &udi )
{
DEBUG_BLOCK
IpodCollection* coll = 0;
if( !m_collectionMap.contains( udi ) )
{
debug() << "New Ipod not seen before";
coll = new IpodCollection( mountPoint, udi );
if( coll )
{
// TODO: connect to MediaDeviceMonitor signals
connect( coll, SIGNAL( collectionDisconnected( const QString &) ),
this, SLOT( slotCollectionDisconnected( const QString & ) ) );
m_collectionMap.insert( udi, coll );
emit newCollection( coll );
debug() << "emitting new ipod collection";
}
}
}

void
IpodCollectionFactory::deviceRemoved( const QString &udi )
{
DEBUG_BLOCK
if( m_collectionMap.contains( udi ) )
{
IpodCollection* coll = m_collectionMap[ udi ];
if( coll )
{
m_collectionMap.remove( udi ); // remove from map
coll->deviceRemoved(); //collection will be deleted by collectionmanager
}
else
warning() << "collection already null";
}
else
warning() << "removing non-existent device";

return;
}

void
IpodCollectionFactory::slotCollectionDisconnected( const QString & udi)
{
m_collectionMap.remove( udi ); // remove from map
}

void
IpodCollectionFactory::slotCollectionReady()
{
DEBUG_BLOCK
IpodCollection *collection = dynamic_cast( sender() );
if( collection )
{
debug() << "emitting ipod collection newcollection";
emit newCollection( collection );
}
}

void
IpodCollectionFactory::checkDevicesForIpod()
{
QStringList udiList = MediaDeviceMonitor::instance()->getDevices();

/* poll udi list for supported devices */
foreach( const QString &udi, udiList )
{
/* if ipod device found, emit signal */
if( isIpod( udi ) )
{
// HACK: Usability: Force auto-connection of device upon detection
QString mountpoint = MediaDeviceCache::instance()->volumeMountPoint(udi);
ipodDetected( mountpoint, udi );

//MediaDeviceInfo *deviceinfo =
new IpodDeviceInfo( mountpoint, udi );
//emit ipodDetected( deviceinfo );
// HACK: emit old signal to avoid refactor of applet yet
emit tellIpodDetected( mountpoint, udi );
}
}
}

bool
IpodCollectionFactory::isIpod( const QString &udi ) const
{
DEBUG_BLOCK

Solid::Device device;

device = Solid::Device(udi);
/* going until we reach a vendor, e.g. Apple */
while ( device.isValid() && device.vendor().isEmpty() )
{
device = Solid::Device( device.parentUdi() );
}

debug() << "Device udi: " << udi;
debug() << "Device name: " <<>deviceName(udi);
debug() << "Mount point: " <<>volumeMountPoint(udi);
if ( device.isValid() )
{
debug() << "vendor: " << device.vendor() << ", product: " << device.product();
}

/* if iPod found, return true */
return device.product() == "iPod";
}

After:

AMAROK_EXPORT_PLUGIN( IpodCollectionFactory )

IpodCollectionFactory::IpodCollectionFactory()
: MediaDeviceCollectionFactory ( new IpodConnectionAssistant() )
{
//nothing to do
}

IpodCollectionFactory::~IpodCollectionFactory()
{
DEBUG_BLOCK
}

----------------------------------------------------------------------------------------------

As you can see (excuse the bad formatting, blogger doesn't take kindly to source code), the amount of code required has been greatly reduced, and the other required classes are soon to follow. Also, things are going to be implemented more in the predictably correct classes now, and after refactoring is done and I've ported both Ipods and MTPs to it, I will write a small tutorial on how to go about implementing your own device. No, UMS will be done by me, heh, but it should be a piece of cake afterward to implement support for some of the other devices some of you miss from Amarok 1.4, so yay!

Unfortunately I'm fairly busy with school too so this proceeds in big bumps every 4-7 days, might take a bit, but getting there. I'm going to Boston next weekend for the KDE GSoC meetup, which should be fun. If you're around, do say hello!

3 comments:

cassiano leal said...

Doesn't blogger have a < code > tag or similar?

Maybe it's time to try wordpress out: it's FREE (as in speech -- the software is GPL'ed)!

(The software is in wordpress.org -- to get a free blog using it, head to wordpress.com)

Sylvain said...

Yep
I am really interested in a tutorial for implementing our device
like old doc
http://amarok.kde.org/wiki/Media_Device
http://amarok.kde.org/wiki/Media_Device:Considerations

awainzin said...

@cassiano: I googled for it and it looks like people have gone so far as to import css and javascript stuff to make it work... wordpress might not be a bad idea at some point.

@Sylvain: Good to hear! The sad truth is I dont have every type of device 1.4 supported to test with, so after making things easier, I can only rely on the community with devices to help out.