/*************************************************************************** * Copyright (C) 2004 by Michael Schulze * * mike.s@genion.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. * * * * 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, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ipod.h" #include using namespace itunesdb; IPod::IPod(const QString& mountPoint, const QString& device) : ipodBase(mountPoint), devicename(device), itunesdb(true), deviceDetails(NULL), sysInfo(NULL), replayingLog( false ) { } IPod::~IPod() { itunesdb.clear(); delete deviceDetails; delete sysInfo; kdDebug() << "IPod at " << ipodBase << " cleaned up" << endl; } bool IPod::open() { kdDebug() << "IPod::open(" << ipodBase << ", " << devicename << ");" << endl; kdDebug() << "loading sysinfo" << endl; sysInfo = new IPodSysInfo(ipodBase); if ( !sysInfo->load() ) { kdDebug() << "failed to open sysinfo" << endl; delete sysInfo; sysInfo = NULL; return false; } kdDebug() << "about to open ipodbase" << endl; if (!itunesdb.open(ipodBase)) { kdDebug() << "failed to open ipodbase" << endl; return false; } kdDebug() << "about to look up device details" << endl; deviceDetails = new IPodDeviceDetails(ipodBase + IPodSysInfo::iPodControlDir + "/iTunes/DeviceInfo"); if ( ! deviceDetails->load() ) { kdDebug() << "coudlnt' load device details" << endl; delete deviceDetails; deviceDetails = NULL; } logfileentrypos = 0; locked = false; dirty = false; kdDebug() << "about to show replay log" << endl; replayLog(); kdDebug() << "ipod::open() complete" << endl; return true; } void IPod::setDirty() { dirty = true; } bool IPod::isOpen() { return itunesdb.isOpen(); } bool IPod::isStillConnected() { return !itunesdb.dbFileChanged(); } void IPod::close() { flushLog(); itunesdb.clear(); delete sysInfo; delete deviceDetails; sysInfo = NULL; deviceDetails = NULL; } bool IPod::ensureConsistency() { kdDebug() << "IPod::ensureConsistency()" << endl; if(!isStillConnected()) { flushLog(); return false; } replayLog(); return true; } QString IPod::getRealPath( QString pathinfo ) const { QFile realPath( ipodBase + pathinfo.replace( ":", "/") ); if ( !realPath.exists() ) { // umm, try to fix the path the simple way, and let the slave gen an error msg if this fails QString pathToFix( realPath.name() ); QDir musicDir( getSysInfo()->getMusicDir() ); QString replacement( musicDir.path() + "/" + musicDir[0][0] ); realPath.setName( pathToFix.replace(0, replacement.length(), replacement) ); } // TODO if that still doesn't work try to do it by path components return realPath.name(); } TrackMetadata IPod::createNewTrackMetadata() { Q_UINT32 trackid = itunesdb.getMaxTrackID(); while (itunesdb.getTrackByID( ++trackid) != NULL) { // look for the next free number if (trackid >= 0xEFFFFFFF) trackid = 2000; // start anew when max tracknumber is reached } TrackMetadata track( trackid); // calculate the directory QString dir = getSysInfo()->getMusicDir()[ trackid % sysInfo->getNumTrackFileDirs() ]; // form the trackpath QString trackpath; trackpath.sprintf( ":iPod_Control:Music:%s:%s", dir.latin1(), (QString("kpod") + QString::number(trackid)).latin1()); track.setPath(trackpath); return track; } IPod::IPodError IPod::deleteArtist(const QString& artistname, bool log) { if (!itunesdb.removeArtist(artistname)) { return Err_NotEmpty; // dirty since false is also emmitted if the artist doesn't exist } if (log) { QStringList actions; actions << artistname; appendLogEntry(ACT_DELETE_ARTIST, actions); } return Err_None; } /*! \fn IPod::renameAlbum(const QString& name, const QString& newname) */ IPod::IPodError IPod::renameAlbum(const QString& artistname, const QString& title, const QString& newartistname, const QString& newtitle, bool log) { kdDebug() << "IPod::renameAlbum() " << title << endl; if (!itunesdb.isOpen()) { return Err_NotOpen; } if (itunesdb.getAlbum(newartistname, newtitle)) { // does it already exist? return Err_AlreadyExists; } TrackList * album = itunesdb.getAlbum(artistname, title); if (album == NULL) { return Err_DoesNotExist; } if(!itunesdb.renameAlbum(*album, newartistname, newtitle)) { kdDebug() << "IPod::renameAlbum() issued an internal error" << endl; return Err_Internal; } if (log) { QStringList actions; actions << artistname << title << newartistname << newtitle; appendLogEntry(ACT_RENAME_ALBUM, actions); } setDirty(); kdDebug() << "IPod::renameAlbum() finished" << endl; return Err_None; } IPod::IPodError IPod::renameArtist(const QString& artistname, const QString& newartistname, bool log) { kdDebug() << "IPod::renameArtist() " << artistname << endl; if (!itunesdb.isOpen()) { return Err_NotOpen; } if ( artistname == newartistname ) { return Err_None; } Artist * oldartist = itunesdb.getArtistByName(artistname); if ( oldartist == NULL ) { return Err_DoesNotExist; } for( ArtistIterator albumiterator( *oldartist ); albumiterator.current(); ) { kdDebug() << "renaming album " << (*albumiterator.current()).getTitle() << endl; if ( ! itunesdb.renameAlbum( *albumiterator.current(), newartistname, QString::null ) ) { kdDebug() << "IPod::renameAlbum() issued an internal error" << endl; return Err_Internal; } } itunesdb.removeArtist( artistname ); if ( log ) { QStringList actions; actions << artistname << newartistname; appendLogEntry( ACT_RENAME_ARTIST, actions ); } setDirty(); kdDebug() << "IPod::renameArtist() finished" << endl; return Err_None; } IPod::IPodError IPod::deleteAlbum(const QString& artistname, const QString& title, bool log) { TrackList * album = getAlbum(artistname, title); if (album == NULL) return Err_DoesNotExist; TrackList::Iterator trackiter = album->getTrackIDs(); while (trackiter.hasNext()) { TrackMetadata * track = getTrackByID(trackiter.next()); album->removeTrackAt(trackiter); if (track == NULL) { continue; } QString filename = getRealPath(track->getPath()); if (QFile::exists(filename)) QFile::remove(filename); itunesdb.removeTrack(track->getID()); } Artist * artist = getArtistByName(artistname); if ( artist != NULL ) { artist->remove(album->getTitle()); } if (log) { QStringList actions; actions << artistname << title; appendLogEntry(ACT_REM_ALBUM, actions); sysInfo->refreshDiskUsageStats(); } return Err_None; } TrackList * IPod::getAlbum(const QString &artistname, const QString &albumname) const { return itunesdb.getAlbum(artistname, albumname); } /** * returns the Track found by the given information or NULL if no such Track could be found * @param artistname the name of the artist * @param albumname the name of the album * @param title the title of the track */ TrackMetadata * IPod::findTrack(const QString& artistname, const QString& albumname, const QString& title) const { return itunesdb.findTrack( artistname, albumname, title ); } /*! \fn IPod::renamePlaylist(const QString& name, const QString& newname) */ IPod::IPodError IPod::renamePlaylist(const QString& title, const QString& newtitle, bool log) { if( !itunesdb.isOpen()) { return Err_NotOpen; } if( itunesdb.getPlaylistByTitle(newtitle)) { // does it already exist? return Err_AlreadyExists; } TrackList * playlist = itunesdb.getPlaylistByTitle(title); if( playlist == NULL) { return Err_DoesNotExist; } itunesdb.removePlaylist(title, FALSE); playlist->setTitle(newtitle); itunesdb.handlePlaylist(*playlist); delete playlist; if (log) { QStringList actions; actions << title << newtitle; appendLogEntry( ACT_RENAME_PLAYLIST, actions); } setDirty(); return Err_None; } /*! \fn IPod::deletePlaylist(const QString& name) */ IPod::IPodError IPod::deletePlaylist(const QString& title, bool log) { if(!itunesdb.removePlaylist(title, true)) { return Err_DoesNotExist; } if (log) appendLogEntry(ACT_REM_PLAYLIST, QStringList(title)); setDirty(); return Err_None; } QStringList * IPod::getPlaylistTitles(QStringList& buffer) { for(Playlist * playlist= itunesdb.firstPlaylist(); playlist != NULL; playlist= itunesdb.nextPlaylist()) { buffer.append(playlist->getTitle()); } return &buffer; } /*! \fn IPod::getPlaylistByTitle(const QString& title) */ TrackList * IPod::getPlaylistByTitle(const QString& title) const { return itunesdb.getPlaylistByTitle(title); } QStringList * IPod::getArtists( QStringList &buffer) const { return itunesdb.getArtists(buffer); } QString IPod::getName() const { if ( deviceDetails != NULL ) { return deviceDetails->getName(); } return itunesdb.getMainListTitle(); } void IPod::setName(const QString& name) { Playlist * mainlist = itunesdb.getMainplaylist(); if(mainlist != NULL) { mainlist->setTitle(name); setDirty(); } } const QString& IPod::getItunesDBError() const { return itunesdb.error; } IPod::IPodError IPod::createPlaylist(const QString& playlisttitle, bool log) { if(itunesdb.getPlaylistByTitle(playlisttitle) != NULL) { return Err_AlreadyExists; } Playlist playlist; playlist.setTitle(playlisttitle); itunesdb.handlePlaylist(playlist); if ( log ) { appendLogEntry( ACT_ADD_PLAYLIST, QStringList(playlist.getTitle())); } setDirty(); return Err_None; } IPod::IPodError IPod::createArtist( const QString& artistname, bool log ) { if ( itunesdb.getArtistByName( artistname ) ) { return Err_AlreadyExists; } Artist * artist = itunesdb.getArtistByName( artistname, true ); if ( !artist ) { return Err_Internal; } if ( log ) { appendLogEntry( ACT_CREATE_ARTIST, QStringList( artistname )); } setDirty(); return Err_None; } IPod::IPodError IPod::createAlbum(const QString& artistName, const QString& albumName, bool log ) { Artist * artist = getArtistByName( artistName ); if ( !artist ) { return Err_DoesNotExist; } if ( artist->find ( albumName ) ) { return Err_AlreadyExists; } TrackList * album = new TrackList(); album->setTitle( albumName ); artist->insert( albumName, album ); if ( log ) { QStringList actions; actions << artistName << albumName; appendLogEntry( ACT_CREATE_ALBUM, actions); } setDirty(); return Err_None; } void IPod::writeItunesDB() { lock(true); itunesdb.writeDatabase(); flushLog(); unlock(); } void IPod::writeItunesDB(const QString& filename) { lock(true); itunesdb.writeDatabase(filename); flushLog(); unlock(); } TrackMetadata * IPod::getTrackByID(const Q_UINT32 id) const { return itunesdb.getTrackByID(id); } Artist * IPod::getArtistByName(const QString& artist) const { return itunesdb.getArtistByName(artist); } IPod::IPodError IPod::addTrackToPlaylist(const TrackMetadata& track, const QString& playlisttitle, bool log) { TrackList * playlist = itunesdb.getPlaylistByTitle(playlisttitle); if(playlist == NULL) { return Err_DoesNotExist; } playlist->addPlaylistItem(track); setDirty(); if (log) { QStringList actions; actions << playlist->getTitle() << QString::number( track.getID(), 36 ); appendLogEntry(ACT_ADD_TO_PLAYLIST, actions); } return Err_None; } IPod::IPodError IPod::removeFromPlaylist(Q_UINT32 position, const QString& playlisttitle, bool log) { TrackList * playlist = itunesdb.getPlaylistByTitle(playlisttitle); if(playlist == NULL) { return Err_DoesNotExist; } playlist->setTrackIDAt(position, LISTITEM_DELETED); setDirty(); if (log) { QStringList actions; actions << playlist->getTitle() << QString::number( position, 36 ); appendLogEntry(ACT_REM_FROM_PLAYLIST, actions); } return Err_None; } void IPod::addTrack(TrackMetadata& track, bool log) { itunesdb.addTrack(track); if (log) { QStringList actions; actions = track.toLogEntry(actions); appendLogEntry( ACT_ADD_TRACK, actions); sysInfo->refreshDiskUsageStats(); } setDirty(); } IPod::IPodError IPod::moveTrack(TrackMetadata& track, const QString& newartist, const QString& newalbum, bool log) { if (!itunesdb.moveTrack(track, newartist, newalbum)) { return Err_DoesNotExist; } if (log) { QStringList actions; actions << QString::number( track.getID(), 36 ) << newartist << newalbum; appendLogEntry(ACT_MOV_TRACK, actions); } setDirty(); return Err_None; } IPod::IPodError IPod::deleteTrack(Q_UINT32 trackid, bool log) { if (!itunesdb.removeTrack(trackid, true)) { return Err_DoesNotExist; } if (log) { QStringList actions; actions << QString::number( trackid, 36 ); appendLogEntry(ACT_REM_TRACK, actions); sysInfo->refreshDiskUsageStats(); } setDirty(); return Err_None; } void IPod::lock(bool write_lock) { // if(write_lock) { itunesdb.lock(write_lock); locked = true; // } } bool IPod::isLocked() { return locked; } void IPod::unlock() { // if(locked) { itunesdb.unlock(); locked = false; // } } /*! \fn IPod::appendLogEntry( int type, QStringList& values) */ bool IPod::appendLogEntry(IPod::LogActionType type, const QStringList& values) { bool _unlock_ = false; QFile logfile(getLogfileName()); if (!logfile.open(QIODevice::ReadWrite | QIODevice::Append)) { return false; } if (!isLocked()) { lock(true); _unlock_ = true; } QByteArray logentry; QDataStream stream(&logentry, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); for (QStringList::const_iterator value_it = values.constBegin(); value_it != values.constEnd(); ++value_it) { stream << *value_it; } QDataStream logstream(&logfile); logstream.setByteOrder(QDataStream::LittleEndian); logstream << type; logstream << logentry; logstream.unsetDevice(); logfileentrypos++; logfile.flush(); logfile.close(); if(_unlock_) { unlock(); } return true; } /*! \fn kio_ipodslaveProtocol::replayLog() */ void IPod::replayLog() { kdDebug() << "IPod::replayLog()" << endl; bool _unlock_ = false; if (!isLocked()) { lock(false); _unlock_ = true; } // kdDebug() << "IPod::replayLog() locked!" << endl; QFile logfile(getLogfileName()); if (!logfile.open(QIODevice::ReadOnly)) { if(_unlock_) unlock(); return; } replayingLog = true; // kdDebug() << "IPod::replayLog() logfile opened!" << endl; QDataStream logstream(&logfile); logstream.setByteOrder(QDataStream::LittleEndian); // ignore the changes we already know about for (uint i= 0; i< logfileentrypos; i++) { Q_UINT32 type; QByteArray buffer; if( logstream.atEnd()) { // ick logfileentrypos= i; break; } logstream >> type; logstream >> buffer; } while (!logstream.atEnd()) { // read new log entries QByteArray entrydata; Q_UINT32 type; QStringList values; logstream >> type; logstream >> entrydata; if (type >= NUM_ACTIONS) { continue; } logfileentrypos++; if( entrydata.isEmpty()) continue; // parse entry elements QDataStream entrystream(&entrydata, QIODevice::ReadOnly); entrystream.setByteOrder( QDataStream::LittleEndian); while(!entrystream.atEnd()) { QString value; entrystream >> value; values.push_back(value); } switch( type) { // handle logfile entry case ACT_ADD_PLAYLIST: // add playlist if (values.size()> 0) { createPlaylist(values[0], false); } break; case ACT_REM_PLAYLIST: if (values.size()> 0) { deletePlaylist( values[ 0], false); } break; case ACT_RENAME_PLAYLIST: if (values.size()> 1) { renamePlaylist(values[0], values[1], false); } break; case ACT_REM_ALBUM: if (values.size()> 1) { deleteAlbum(values[0], values[1], false); } break; case ACT_RENAME_ALBUM: if (values.size()> 3) { renameAlbum(values[0], values[1], values[2], values[3], false); } break; case ACT_ADD_TO_PLAYLIST: { if ( values.size() > 1 ) { bool conversion_ok= true; Q_UINT32 trackid= values[ 1].toUInt( &conversion_ok, 36 ); if(conversion_ok) { TrackMetadata * track = getTrackByID( trackid); if(track != NULL) addTrackToPlaylist(*track, values[0], false); } } } break; case ACT_REM_FROM_PLAYLIST: if (values.size()> 1) { bool conversion_ok = true; int tracknum = values[ 1].toUInt( &conversion_ok, 36 ); if(conversion_ok) removeFromPlaylist(tracknum, values[0], false); } break; case ACT_ADD_TRACK: if (values.size() > 0) { TrackMetadata track; if ( track.readFromLogEntry( values ) ) { addTrack( track, false ); } } // ignore otherwise break; case ACT_MOV_TRACK: if (values.size() > 2) { bool conversion_ok = true; int trackid = values[0].toUInt( &conversion_ok, 36 ); if (!conversion_ok) break; TrackMetadata * track = getTrackByID(trackid); if (track == NULL) break; moveTrack(*track, values[1], values[2], false); } break; case ACT_REM_TRACK: { if (values.size() > 0) { bool conversion_ok = true; int trackid = values[ 0].toUInt( &conversion_ok, 36 ); if(conversion_ok) deleteTrack(trackid, false); } // ignore otherwise } break; case ACT_DELETE_ARTIST: { if (values.size() > 0) { deleteArtist(values[0], false); } // ignore otherwise } break; case ACT_RENAME_ARTIST: { if ( values.size() > 1 ) { renameArtist( values[ 0 ], values[ 1 ], false ); } } break; case ACT_CREATE_ARTIST: { if ( values.size() > 0 ) { createArtist( values[ 0 ], false ); } } break; case ACT_CREATE_ALBUM: { if ( values.size() > 1 ) { createAlbum( values[ 0 ], values[ 1 ], false ); } } break; default: break; } } replayingLog = false; if(_unlock_) { unlock(); } } /*! \fn kio_ipodslaveProtocol::flushLog */ void IPod::flushLog() { if( QFile::exists(getLogfileName())) { QFile::remove(getLogfileName()); } logfileentrypos = 0; } uint IPod::getNumPlaylists() { return itunesdb.getNumPlaylists(); } uint IPod::getNumTracks() { return itunesdb.getNumTracks(); } QString IPod::getITunesDbFilename() { return itunesdb.getFilename(); } QString IPod::getLogfileName() { return ipodBase + LOGFILEPREFIX + QString::number(itunesdb.lastModified()); }