You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/mediadevice/ifp/ifpmediadevice.cpp

715 lines
20 KiB

/***************************************************************************
* copyright : (C) 2005-2006 Seb Ruiz <me@sebruiz.net> *
* *
* With some code helpers from KIO_IFP *
* (c) 2004 Thomas Loeber <ifp@loeber1.de> *
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
/**
* iRiver ifp media device code
* @author Seb Ruiz <me@sebruiz.net>
* @see http://ifp-driver.sourceforge.net/libifp/docs/ifp_8h.html
* @note ifp uses a backslash '\' as a directory delimiter for _remote_ files
*/
#define DEBUG_PREFIX "IfpMediaDevice"
#include "ifpmediadevice.h"
AMAROK_EXPORT_PLUGIN( IfpMediaDevice )
#include "debug.h"
#include "metabundle.h"
#include "collectiondb.h"
#include "statusbar/statusbar.h"
#include "transferdialog.h"
#include <kapplication.h>
#include <tdeconfig.h> //download saveLocation
#include <kiconloader.h> //smallIcon
#include <kmessagebox.h>
#include <tdepopupmenu.h>
#include <kurlrequester.h> //downloadSelectedItems()
#include <kurlrequesterdlg.h> //downloadSelectedItems()
#include <tqfile.h>
#include <tqcstring.h>
#include <tqregexp.h>
namespace Amarok {
extern TDEConfig *config( const TQString& );
extern TQString cleanPath( const TQString&, bool );
}
/**
* IfpMediaItem Class
*/
class IfpMediaItem : public MediaItem
{
public:
IfpMediaItem( TQListView *parent, TQListViewItem *after = 0 )
: MediaItem( parent, after )
{}
IfpMediaItem( TQListViewItem *parent, TQListViewItem *after = 0 )
: MediaItem( parent, after )
{}
void
setEncodedName( TQString &name )
{
m_encodedName = TQFile::encodeName( name );
}
void
setEncodedName( TQCString &name ) { m_encodedName = name; }
TQCString
encodedName() { return m_encodedName; }
// List directories first, always
int
compare( TQListViewItem *i, int col, bool ascending ) const
{
#define i static_cast<IfpMediaItem *>(i)
switch( type() )
{
case MediaItem::DIRECTORY:
if( i->type() == MediaItem::DIRECTORY )
break;
return -1;
default:
if( i->type() == MediaItem::DIRECTORY )
return 1;
}
#undef i
return MediaItem::compare(i, col, ascending);
}
private:
bool m_dir;
TQCString m_encodedName;
};
/**
* IfpMediaDevice Class
*/
IfpMediaDevice::IfpMediaDevice()
: MediaDevice()
, m_dev( 0 )
, m_dh( 0 )
, m_connected( false )
, m_last( 0 )
, m_tmpParent( 0 )
, m_td( 0 )
{
m_name = "iRiver";
m_hasMountPoint = false;
m_spacesToUnderscores = configBool("spacesToUnderscores");
m_firstSort = configString( "firstGrouping", i18n("None") );
m_secondSort = configString( "secondGrouping", i18n("None") );
m_thirdSort = configString( "thirdGrouping", i18n("None") );
}
void
IfpMediaDevice::init( MediaBrowser* parent )
{
MediaDevice::init( parent );
}
IfpMediaDevice::~IfpMediaDevice()
{
setConfigString( "firstGrouping" , m_firstSort );
setConfigString( "secondGrouping" , m_secondSort );
setConfigString( "thirdGrouping" , m_thirdSort );
setConfigBool( "spacesToUnderscores", m_spacesToUnderscores );
closeDevice();
}
bool
IfpMediaDevice::checkResult( int result, TQString message )
{
if( result == 0 )
return true;
error() << result << ": " << message << endl;
return false;
}
bool
IfpMediaDevice::openDevice( bool /*silent*/ )
{
DEBUG_BLOCK
usb_init();
m_dh = (usb_dev_handle*)ifp_find_device();
TQString genericError = i18n( "Could not connect to iFP device" );
if( m_dh == NULL )
{
error() << "A suitable iRiver iFP device couldn't be found" << endl;
Amarok::StatusBar::instance()->shortLongMessage( genericError,
i18n("iFP: A suitable iRiver iFP device could not be found")
, KDE::StatusBar::Error );
return false;
}
m_dev = usb_device( m_dh );
if( m_dev == NULL )
{
error() << "Could not get usb_device()" << endl;
Amarok::StatusBar::instance()->shortLongMessage( genericError,
i18n("iFP: Could not get a USB device handle"), KDE::StatusBar::Error );
if( ifp_release_device( m_dh ) )
error() << "warning: release_device failed." << endl;
return false;
}
/* "must be called" written in the libusb documentation */
if( usb_claim_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber ) )
{
error() << "Device is busy. (I was unable to claim its interface.)" << endl;
Amarok::StatusBar::instance()->shortLongMessage( genericError,
i18n("iFP: Device is busy"), KDE::StatusBar::Error );
if( ifp_release_device( m_dh ) )
error() << "warning: release_device failed." << endl;
return false;
}
int i = ifp_init( &m_ifpdev, m_dh );
if( i )
{
error() << "iFP device: Device cannot be opened." << endl;
Amarok::StatusBar::instance()->shortLongMessage( genericError,
i18n("iFP: Could not open device"), KDE::StatusBar::Error );
usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber );
return false;
}
m_connected = true;
char info[20];
ifp_model( &m_ifpdev, info, 20 );
m_transferDir = TQString(info);
debug() << "Successfully connected to: " << info << endl;
listDir( "" );
return true;
}
bool
IfpMediaDevice::closeDevice() //SLOT
{
DEBUG_BLOCK
if( m_connected )
{
if( m_dh )
{
usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber );
if( ifp_release_device( m_dh ) )
error() << "warning: release_device failed." << endl;
ifp_finalize( &m_ifpdev );
m_dh = 0;
}
m_view->clear();
m_connected = false;
}
return true;
}
void
IfpMediaDevice::runTransferDialog()
{
m_td = new TransferDialog( this );
m_td->exec();
}
/// Renaming
void
IfpMediaDevice::renameItem( TQListViewItem *item ) // SLOT
{
if( !item )
return;
#define item static_cast<IfpMediaItem*>(item)
TQCString src = TQFile::encodeName( getFullPath( item, false ) );
src.append( item->encodedName() );
//the rename line edit has already changed the TQListViewItem text
TQCString dest = TQFile::encodeName( getFullPath( item ) );
debug() << "Renaming " << src << " to: " << dest << endl;
if( ifp_rename( &m_ifpdev, src, dest ) ) //success == 0
//rename failed
item->setText( 0, item->encodedName() );
#undef item
}
/// Creating a directory
MediaItem *
IfpMediaDevice::newDirectory( const TQString &name, MediaItem *parent )
{
if( !m_connected || name.isEmpty() ) return 0;
TQString cleanedName = cleanPath( name );
const TQCString dirPath = TQFile::encodeName( getFullPath( parent ) + "\\" + cleanedName );
debug() << "Creating directory: " << dirPath << endl;
int err = ifp_mkdir( &m_ifpdev, dirPath );
if( err ) //failed
return 0;
m_tmpParent = parent;
addTrackToList( IFP_DIR, cleanedName );
return m_last;
}
MediaItem *
IfpMediaDevice::newDirectoryRecursive( const TQString &name, MediaItem *parent )
{
TQStringList folders = TQStringList::split( '\\', name );
TQString progress = "";
if( parent )
progress += getFullPath( parent ) + "\\";
else
progress += "\\";
foreach( folders )
{
debug() << "Checking folder: " << progress << endl;
progress += *it;
const TQCString dirPath = TQFile::encodeName( progress );
if( ifp_exists( &m_ifpdev, dirPath ) == IFP_DIR )
{
m_tmpParent = parent;
parent = findChildItem( *it, parent );
if( !parent )
{
addTrackToList( IFP_DIR, *it );
parent = m_last;
}
}
else
{
parent = newDirectory( *it, parent );
if( !parent ) //failed
return 0;
}
progress += "\\";
}
return parent;
}
MediaItem *
IfpMediaDevice::findChildItem( const TQString &name, MediaItem *parent )
{
TQListViewItem *child;
parent ?
child = parent->firstChild():
child = m_view->firstChild();
while( child )
{
if( child->text(0) == name )
return static_cast<MediaItem*>(child);
child = child->nextSibling();
}
return 0;
}
void
IfpMediaDevice::addToDirectory( MediaItem *directory, TQPtrList<MediaItem> items )
{
if( !directory || items.isEmpty() ) return;
m_tmpParent = directory;
for( TQPtrListIterator<MediaItem> it(items); *it; ++it )
{
TQCString src = TQFile::encodeName( getFullPath( *it ) );
TQCString dest = TQFile::encodeName( getFullPath( directory ) + "\\" + (*it)->text(0) );
debug() << "Moving: " << src << " to: " << dest << endl;
int err = ifp_rename( &m_ifpdev, src, dest );
if( err ) //failed
continue;
m_view->takeItem( *it );
directory->insertItem( *it );
}
}
/// Uploading
MediaItem *
IfpMediaDevice::copyTrackToDevice( const MetaBundle& bundle )
{
if( !m_connected ) return 0;
m_transferring = true;
const TQCString src = TQFile::encodeName( bundle.url().path() );
TQString directory = "\\"; //root
bool cleverFilename = false;
bool addFileToView = true;
if( m_firstSort != i18n("None") )
{
addFileToView = false;
directory += bundle.prettyText( bundle.columnIndex(m_firstSort) ) + "\\";
if( m_secondSort != i18n("None") )
{
directory += bundle.prettyText( bundle.columnIndex(m_secondSort) ) + "\\";
if( m_thirdSort != i18n("None") )
directory += bundle.prettyText( bundle.columnIndex(m_thirdSort) ) + "\\";
}
if( m_firstSort == i18n("Album") || m_secondSort == i18n("Album") || m_thirdSort == i18n("Album") )
cleverFilename = true;
}
m_tmpParent = newDirectoryRecursive( directory, 0 ); // recursively create folders as required.
TQString newFilename;
// we don't put this in cleanPath because of remote directory delimiters
const TQString title = bundle.title().replace( '\\', '-' );
if( cleverFilename && !title.isEmpty() )
{
if( bundle.track() > 0 )
newFilename = cleanPath( TQString::number(bundle.track()) + " - " + title ) + '.' + bundle.type();
else
newFilename = cleanPath( title ) + '.' + bundle.type();
}
else
newFilename = cleanPath( bundle.prettyTitle() ) + '.' + bundle.type();
const TQCString dest = TQFile::encodeName( cleanPath(directory + newFilename) );
kapp->processEvents( 100 );
int result = uploadTrack( src, dest );
if( !result ) //success
{
addTrackToList( IFP_FILE, cleanPath( newFilename ) );
return m_last;
}
return 0;
}
/// File transfer methods
int
IfpMediaDevice::uploadTrack( const TQCString& src, const TQCString& dest )
{
debug() << "Transferring " << src << " to: " << dest << endl;
return ifp_upload_file( &m_ifpdev, src, dest, filetransferCallback, this );
}
int
IfpMediaDevice::downloadTrack( const TQCString& src, const TQCString& dest )
{
debug() << "Downloading " << src << " to: " << dest << endl;
return ifp_download_file( &m_ifpdev, src, dest, filetransferCallback, this );
}
void
IfpMediaDevice::downloadSelectedItems()
{
// TDEConfig *config = Amarok::config( "MediaDevice" );
// TQString save = config->readEntry( "DownloadLocation", TQString() ); //restore the save directory
TQString save = TQString();
KURLRequesterDlg dialog( save, 0, 0 );
dialog.setCaption( kapp->makeStdCaption( i18n( "Choose a Download Directory" ) ) );
dialog.urlRequester()->setMode( KFile::Directory | KFile::ExistingOnly );
dialog.exec();
KURL destDir = dialog.selectedURL();
if( destDir.isEmpty() )
return;
destDir.adjustPath( 1 ); //add trailing slash
// if( save != destDir.path() )
// config->writeEntry( "DownloadLocation", destDir.path() );
TQListViewItemIterator it( m_view, TQListViewItemIterator::Selected );
for( ; it.current(); ++it )
{
TQCString dest = TQFile::encodeName( destDir.path() + (*it)->text(0) );
TQCString src = TQFile::encodeName( getFullPath( *it ) );
downloadTrack( src, dest );
}
hideProgress();
}
int
IfpMediaDevice::filetransferCallback( void *pData, struct ifp_transfer_status *progress )
{
// will be called by 'ifp_upload_file' by callback
kapp->processEvents( 100 );
IfpMediaDevice *that = static_cast<IfpMediaDevice *>(pData);
if( that->isCanceled() )
{
debug() << "Canceling transfer operation" << endl;
that->setCanceled( false );
that->setProgress( progress->file_bytes, progress->file_bytes );
return 1; //see ifp docs, return 1 for user cancel request
}
return that->setProgressInfo( progress );
}
int
IfpMediaDevice::setProgressInfo( struct ifp_transfer_status *progress )
{
setProgress( progress->file_bytes, progress->file_total );
return 0;
}
/// Deleting
int
IfpMediaDevice::deleteItemFromDevice( MediaItem *item, int /*flags*/ )
{
if( !item || !m_connected ) return -1;
TQString path = getFullPath( item );
TQCString encodedPath = TQFile::encodeName( path );
int err;
int count = 0;
switch( item->type() )
{
case MediaItem::DIRECTORY:
err = ifp_delete_dir_recursive( &m_ifpdev, encodedPath );
debug() << "Deleting folder: " << encodedPath << endl;
checkResult( err, i18n("Directory cannot be deleted: '%1'").arg(encodedPath.data()) );
break;
default:
err = ifp_delete( &m_ifpdev, encodedPath );
debug() << "Deleting file: " << encodedPath << endl;
count += 1;
checkResult( err, i18n("File does not exist: '%1'").arg(encodedPath.data()) );
break;
}
if( err == 0 ) //success
delete item;
return (err == 0) ? count : -1;
}
/// Directory Reading
void
IfpMediaDevice::expandItem( TQListViewItem *item ) // SLOT
{
if( !item || !item->isExpandable() || m_transferring ) return;
while( item->firstChild() )
delete item->firstChild();
m_tmpParent = item;
TQString path = getFullPath( item );
listDir( path );
m_tmpParent = 0;
}
void
IfpMediaDevice::listDir( const TQString &dir )
{
int err = ifp_list_dirs( &m_ifpdev, TQFile::encodeName( dir ), listDirCallback, this );
checkResult( err, i18n("Cannot enter directory: '%1'").arg(dir) );
}
// will be called by 'ifp_list_dirs'
int
IfpMediaDevice::listDirCallback( void *pData, int type, const char *name, int size )
{
TQString qName = TQFile::decodeName( name );
return static_cast<IfpMediaDevice *>(pData)->addTrackToList( type, qName, size );
}
int
IfpMediaDevice::addTrackToList( int type, TQString name, int /*size*/ )
{
m_tmpParent ?
m_last = new IfpMediaItem( m_tmpParent ):
m_last = new IfpMediaItem( m_view );
if( type == IFP_DIR ) //directory
m_last->setType( MediaItem::DIRECTORY );
else if( type == IFP_FILE ) //file
{
if( name.endsWith( "mp3", false ) || name.endsWith( "wma", false ) ||
name.endsWith( "wav", false ) || name.endsWith( "ogg", false ) ||
name.endsWith( "asf", false ) )
m_last->setType( MediaItem::TRACK );
else
m_last->setType( MediaItem::UNKNOWN );
}
m_last->setEncodedName( name );
m_last->setText( 0, name );
return 0;
}
/// Capacity, in kB
bool
IfpMediaDevice::getCapacity( TDEIO::filesize_t *total, TDEIO::filesize_t *available )
{
if( !m_connected ) return false;
int totalBytes = ifp_capacity( &m_ifpdev );
int freeBytes = ifp_freespace( &m_ifpdev );
*total = totalBytes;
*available = freeBytes;
return totalBytes > 0;
}
/// Helper functions
TQString
IfpMediaDevice::getFullPath( const TQListViewItem *item, const bool getFilename )
{
if( !item ) return TQString();
TQString path;
if( getFilename ) path = item->text(0);
TQListViewItem *parent = item->parent();
while( parent )
{
path.prepend( "\\" );
path.prepend( parent->text(0) );
parent = parent->parent();
}
path.prepend( "\\" );
return path;
}
void
IfpMediaDevice::rmbPressed( TQListViewItem* qitem, const TQPoint& point, int )
{
enum Actions { DOWNLOAD, DIRECTORY, RENAME, DELETE };
MediaItem *item = static_cast<MediaItem *>(qitem);
if ( item )
{
TDEPopupMenu menu( m_view );
menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "Download" ), DOWNLOAD );
menu.insertSeparator();
menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY );
menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME );
menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete" ), DELETE );
int id = menu.exec( point );
switch( id )
{
case DOWNLOAD:
downloadSelectedItems();
break;
case DIRECTORY:
if( item->type() == MediaItem::DIRECTORY )
m_view->newDirectory( static_cast<MediaItem*>(item) );
else
m_view->newDirectory( static_cast<MediaItem*>(item->parent()) );
break;
case RENAME:
m_view->rename( item, 0 );
break;
case DELETE:
deleteFromDevice();
break;
}
return;
}
if( isConnected() )
{
TDEPopupMenu menu( m_view );
menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY );
int id = menu.exec( point );
switch( id )
{
case DIRECTORY:
m_view->newDirectory( 0 );
break;
}
}
}
TQString IfpMediaDevice::cleanPath( const TQString &component )
{
TQString result = Amarok::asciiPath( component );
result.simplifyWhiteSpace();
result.remove( "?" ).replace( "*", " " ).replace( ":", " " );
// if( m_spacesToUnderscores )
// result.replace( TQRegExp( "\\s" ), "_" );
result.replace( "/", "-" );
return result;
}
#include "ifpmediadevice.moc"