From a228e2b806ce42df2af053abbbeec32dc2cf4c6f Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sun, 8 Aug 2010 17:41:06 +0000 Subject: [PATCH] Copy files to afc devices. Doesn't quite work yet --- src/core/song.cpp | 19 ++--- src/core/song.h | 2 +- src/core/utilities.cpp | 10 +++ src/core/utilities.h | 2 + src/devices/afcdevice.cpp | 102 +++++++++++++++++++----- src/devices/afcdevice.h | 18 ++--- src/devices/afctransfer.cpp | 46 +++++++++-- src/devices/afctransfer.h | 2 + src/devices/connecteddevice.cpp | 3 +- src/devices/connecteddevice.h | 3 + src/devices/devicemanager.cpp | 2 + src/devices/filesystemdevice.cpp | 5 +- src/devices/filesystemdevice.h | 2 + src/devices/gpoddevice.cpp | 44 ++++++---- src/devices/gpoddevice.h | 12 ++- src/devices/imobiledeviceconnection.cpp | 55 +++++++++++++ src/devices/imobiledeviceconnection.h | 9 ++- 17 files changed, 267 insertions(+), 69 deletions(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index 838d60957..d40de6f42 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -415,7 +415,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { } #ifdef HAVE_LIBGPOD - void Song::InitFromItdb(Itdb_Track* track) { + void Song::InitFromItdb(const Itdb_Track* track) { d->valid_ = true; d->title_ = QString::fromUtf8(track->title); @@ -438,23 +438,14 @@ void Song::InitFromLastFM(const lastfm::Track& track) { d->filesize_ = track->size; d->filetype_ = track->type2 ? Type_Mpeg : Type_Mp4; - itdb_filename_ipod2fs(track->ipod_path); - d->filename_ = QString::fromLocal8Bit(track->ipod_path); + d->filename_.replace(':', '/'); d->basefilename_ = QFileInfo(d->filename_).fileName(); } - static void CopyStr(const QString& str, gchar** dest_p) { - Q_ASSERT(*dest_p == NULL); - const QByteArray data = str.toUtf8(); - const int size = data.size() + 1; - - gchar* dest = new gchar[size]; - std::copy(data.constData(), data.constData() + size, dest); - *dest_p = dest; - } - void Song::ToItdb(Itdb_Track *track) const { + using Utilities::CopyStr; + CopyStr(d->title_, &track->title); CopyStr(d->album_, &track->album); CopyStr(d->artist_, &track->artist); @@ -473,7 +464,9 @@ void Song::InitFromLastFM(const lastfm::Track& track) { track->time_modified = d->mtime_; track->time_added = d->ctime_; track->size = d->filesize_; + track->type1 = 0; track->type2 = d->filetype_ == Type_Mp4 ? 0 : 1; + track->mediatype = 1; // Audio } #endif diff --git a/src/core/song.h b/src/core/song.h index 023768b99..fc088dc87 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -106,7 +106,7 @@ class Song { void MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle& bundle); #ifdef HAVE_LIBGPOD - void InitFromItdb(Itdb_Track* track); + void InitFromItdb(const Itdb_Track* track); void ToItdb(Itdb_Track* track) const; #endif diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp index b34d8bece..17db6beb9 100644 --- a/src/core/utilities.cpp +++ b/src/core/utilities.cpp @@ -135,4 +135,14 @@ void RemoveRecursive(const QString& path) { dir.rmdir(path); } +void CopyStr(const QString& str, char** dest_p) { + Q_ASSERT(*dest_p == NULL); + const QByteArray data = str.toUtf8(); + const int size = data.size() + 1; + + char* dest = new char[size]; + std::copy(data.constData(), data.constData() + size, dest); + *dest_p = dest; +} + } // namespace diff --git a/src/core/utilities.h b/src/core/utilities.h index 726caf731..dd4101d4b 100644 --- a/src/core/utilities.h +++ b/src/core/utilities.h @@ -29,6 +29,8 @@ namespace Utilities { QString MakeTempDir(); void RemoveRecursive(const QString& path); + + void CopyStr(const QString& str, char** dest_p); } #endif // UTILITIES_H diff --git a/src/devices/afcdevice.cpp b/src/devices/afcdevice.cpp index 6fc1242b3..fc974fd0d 100644 --- a/src/devices/afcdevice.cpp +++ b/src/devices/afcdevice.cpp @@ -15,9 +15,11 @@ */ #include "afcdevice.h" +#include "afcfile.h" #include "afctransfer.h" #include "devicemanager.h" #include "gpodloader.h" +#include "imobiledeviceconnection.h" #include "core/utilities.h" #include @@ -25,19 +27,23 @@ AfcDevice::AfcDevice( 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), - loader_thread_(new QThread(this)), - transfer_(NULL), - loader_(NULL), - db_(NULL) + : GPodDevice(url, lister, unique_id, manager, database_id, first_time), + transfer_(NULL) { +} + +AfcDevice::~AfcDevice() { + Utilities::RemoveRecursive(local_path_); +} + +void AfcDevice::Init() { // Make a new temporary directory for the iTunesDB. We copy it off the iPod // so that libgpod can have a local directory to use. local_path_ = Utilities::MakeTempDir(); - InitBackendDirectory(local_path_, first_time, false); + InitBackendDirectory(local_path_, first_time_, false); model_->Init(); - transfer_ = new AfcTransfer(url.host(), local_path_, manager_->task_manager()); + transfer_ = new AfcTransfer(url_.host(), local_path_, manager_->task_manager()); transfer_->moveToThread(loader_thread_); connect(transfer_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); @@ -46,10 +52,6 @@ AfcDevice::AfcDevice( loader_thread_->start(); } -AfcDevice::~AfcDevice() { - Utilities::RemoveRecursive(local_path_); -} - void AfcDevice::CopyFinished() { transfer_->deleteLater(); transfer_ = NULL; @@ -66,19 +68,77 @@ void AfcDevice::CopyFinished() { QMetaObject::invokeMethod(loader_, "LoadDatabase"); } -void AfcDevice::LoadFinished(Itdb_iTunesDB* db) { - QMutexLocker l(&db_mutex_); - db_ = db; - db_wait_cond_.wakeAll(); +bool AfcDevice::CopyToStorage( + const QString& source, const QString&, + const Song& metadata, bool, bool remove_original) { + Q_ASSERT(db_); - loader_->deleteLater(); - loader_ = NULL; + Itdb_Track* track = AddTrackToITunesDb(metadata); + + // Get an unused filename on the device + iMobileDeviceConnection connection(url_.host()); + QString dest = connection.GetUnusedFilename(db_, metadata); + if (dest.isEmpty()) { + itdb_track_remove(track); + return false; + } + + // Copy the file + { + QFile source_file(source); + AfcFile dest_file(&connection, dest); + source_file.open(QIODevice::ReadOnly); + dest_file.open(QIODevice::WriteOnly); + dest_file.write(source_file.readAll()); + } + + track->transferred = 1; + + // Set the filetype_marker + QString suffix = dest.section('.', -1, -1).toUpper(); + track->filetype_marker = 0; + for (int i=0 ; i<4 ; ++i) { + track->filetype_marker = track->filetype_marker << 8; + if (i >= suffix.length()) + track->filetype_marker |= ' '; + else + track->filetype_marker |= suffix[i].toAscii(); + } + qDebug() << track->filetype_marker; + + // Set the filename + Utilities::CopyStr(dest, &track->ipod_path); + itdb_filename_fs2ipod(track->ipod_path); + qDebug() << track->ipod_path; + + AddTrackToModel(track, "afc://" + url_.host()); + + // Remove the original if it was requested + if (remove_original) { + QFile::remove(source); + } + + return true; } -bool AfcDevice::CopyToStorage(const QString &source, const QString &destination, - const Song &metadata, bool overwrite, - bool remove_original) { - return false; +void AfcDevice::FinishCopy() { + // Temporarily unset the GUID so libgpod doesn't lock the device for syncing + itdb_device_set_sysinfo(db_->device, "FirewireGuid", NULL); + + GPodDevice::FinishCopy(); +} + +void AfcDevice::FinaliseDatabase() { + // Set the GUID again to lock the device for syncing + itdb_device_set_sysinfo(db_->device, "FirewireGuid", url_.host().toUtf8().constData()); + + // Copy the files back to the iPod + // No need to start another thread since we're already in the organiser thread + AfcTransfer transfer(url_.host(), local_path_, NULL); + + itdb_start_sync(db_); + transfer.CopyToDevice(); + itdb_stop_sync(db_); } bool AfcDevice::DeleteFromStorage(const Song &metadata) { diff --git a/src/devices/afcdevice.h b/src/devices/afcdevice.h index c8c526703..f56e5a98c 100644 --- a/src/devices/afcdevice.h +++ b/src/devices/afcdevice.h @@ -17,7 +17,7 @@ #ifndef AFCDEVICE_H #define AFCDEVICE_H -#include "connecteddevice.h" +#include "gpoddevice.h" #include #include @@ -27,7 +27,7 @@ class AfcTransfer; class GPodLoader; -class AfcDevice : public ConnectedDevice { +class AfcDevice : public GPodDevice { Q_OBJECT public: @@ -36,29 +36,29 @@ public: int database_id, bool first_time); ~AfcDevice(); + void Init(); + static QStringList url_schemes() { return QStringList() << "afc"; } bool CopyToStorage(const QString &source, const QString &destination, const Song &metadata, bool overwrite, bool remove_original); + void FinishCopy(); + bool DeleteFromStorage(const Song &metadata); +protected: + void FinaliseDatabase(); + private slots: void CopyFinished(); - void LoadFinished(Itdb_iTunesDB* db); private: void RemoveRecursive(const QString& path); private: - QThread* loader_thread_; AfcTransfer* transfer_; - GPodLoader* loader_; QString local_path_; - - QWaitCondition db_wait_cond_; - QMutex db_mutex_; - Itdb_iTunesDB* db_; }; #endif // AFCDEVICE_H diff --git a/src/devices/afctransfer.cpp b/src/devices/afctransfer.cpp index 2ab4b4781..157310f35 100644 --- a/src/devices/afctransfer.cpp +++ b/src/devices/afctransfer.cpp @@ -33,18 +33,24 @@ AfcTransfer::AfcTransfer(const QString& uuid, const QString& local_destination, } void AfcTransfer::CopyFromDevice() { - int task_id = task_manager_->StartTask(tr("Copying iPod database")); - emit TaskStarted(task_id); + int task_id = 0; + if (task_manager_) { + task_id = task_manager_->StartTask(tr("Copying iPod database")); + emit TaskStarted(task_id); + } // Connect to the device iMobileDeviceConnection c(uuid_); + CopyDirFromDevice(&c, "/iTunes_Control/Artwork"); CopyDirFromDevice(&c, "/iTunes_Control/Device"); CopyDirFromDevice(&c, "/iTunes_Control/iTunes"); - moveToThread(original_thread_); - task_manager_->SetTaskFinished(task_id); - emit CopyFinished(); + if (task_manager_) { + moveToThread(original_thread_); + task_manager_->SetTaskFinished(task_id); + emit CopyFinished(); + } } void AfcTransfer::CopyDirFromDevice(iMobileDeviceConnection* c, const QString& path) { @@ -74,5 +80,35 @@ void AfcTransfer::CopyFileFromDevice(iMobileDeviceConnection *c, const QString & } void AfcTransfer::CopyToDevice() { + // Connect to the device + iMobileDeviceConnection c(uuid_); + CopyDirToDevice(&c, "/iTunes_Control/Artwork"); + CopyDirToDevice(&c, "/iTunes_Control/Device"); + CopyDirToDevice(&c, "/iTunes_Control/iTunes"); +} + +void AfcTransfer::CopyDirToDevice(iMobileDeviceConnection* c, const QString& path) { + QDir dir(local_destination_ + path); + + foreach (const QString& filename, dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { + CopyFileToDevice(c, path + "/" + filename); + } + + foreach (const QString& dir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + CopyDirToDevice(c, path + "/" + dir); + } +} + +void AfcTransfer::CopyFileToDevice(iMobileDeviceConnection *c, const QString &path) { + QString local_filename = local_destination_ + path; + qDebug() << "Copying file" << path; + + QFile source(local_filename); + AfcFile dest(c, path); + + dest.open(QIODevice::WriteOnly); + source.open(QIODevice::ReadOnly); + + dest.write(source.readAll()); } diff --git a/src/devices/afctransfer.h b/src/devices/afctransfer.h index c2cba8221..80c5979e6 100644 --- a/src/devices/afctransfer.h +++ b/src/devices/afctransfer.h @@ -40,6 +40,8 @@ signals: private: void CopyDirFromDevice(iMobileDeviceConnection* c, const QString& path); void CopyFileFromDevice(iMobileDeviceConnection* c, const QString& path); + void CopyDirToDevice(iMobileDeviceConnection* c, const QString& path); + void CopyFileToDevice(iMobileDeviceConnection* c, const QString& path); private: QThread* original_thread_; diff --git a/src/devices/connecteddevice.cpp b/src/devices/connecteddevice.cpp index 4d456aaf1..6cf9edd19 100644 --- a/src/devices/connecteddevice.cpp +++ b/src/devices/connecteddevice.cpp @@ -25,9 +25,10 @@ ConnectedDevice::ConnectedDevice(const QUrl& url, DeviceLister* lister, const QString& unique_id, DeviceManager* manager, - int database_id, bool) + int database_id, bool first_time) : QObject(manager), url_(url), + first_time_(first_time), lister_(lister), unique_id_(unique_id), database_id_(database_id), diff --git a/src/devices/connecteddevice.h b/src/devices/connecteddevice.h index c1f69071b..ff1f1b98e 100644 --- a/src/devices/connecteddevice.h +++ b/src/devices/connecteddevice.h @@ -38,6 +38,8 @@ public: int database_id, bool first_time); ~ConnectedDevice(); + virtual void Init() = 0; + DeviceLister* lister() const { return lister_; } QString unique_id() const { return unique_id_; } LibraryModel* model() const { return model_; } @@ -54,6 +56,7 @@ protected: protected: QUrl url_; + bool first_time_; DeviceLister* lister_; QString unique_id_; int database_id_; diff --git a/src/devices/devicemanager.cpp b/src/devices/devicemanager.cpp index d0bde39de..6165ef9b6 100644 --- a/src/devices/devicemanager.cpp +++ b/src/devices/devicemanager.cpp @@ -469,6 +469,8 @@ boost::shared_ptr DeviceManager::Connect(int row) { if (!ret) { qWarning() << "Could not create device for" << device_url.toString(); } else { + ret->Init(); + info.device_ = ret; emit dataChanged(index(row), index(row)); connect(info.device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); diff --git a/src/devices/filesystemdevice.cpp b/src/devices/filesystemdevice.cpp index 13a14d1fd..346d97e6e 100644 --- a/src/devices/filesystemdevice.cpp +++ b/src/devices/filesystemdevice.cpp @@ -58,9 +58,10 @@ FilesystemDevice::FilesystemDevice( connect(watcher, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations())); connect(watcher, SIGNAL(ScanStarted(int)), SIGNAL(TaskStarted(int))); +} - InitBackendDirectory(url.toLocalFile(), first_time); - +void FilesystemDevice::Init() { + InitBackendDirectory(url_.toLocalFile(), first_time_); model_->Init(); } diff --git a/src/devices/filesystemdevice.h b/src/devices/filesystemdevice.h index 0306683c0..ebe33271a 100644 --- a/src/devices/filesystemdevice.h +++ b/src/devices/filesystemdevice.h @@ -34,6 +34,8 @@ public: int database_id, bool first_time); ~FilesystemDevice(); + void Init(); + static QStringList url_schemes() { return QStringList() << "file"; } private: diff --git a/src/devices/gpoddevice.cpp b/src/devices/gpoddevice.cpp index b680f97a8..e935cc570 100644 --- a/src/devices/gpoddevice.cpp +++ b/src/devices/gpoddevice.cpp @@ -31,12 +31,16 @@ GPodDevice::GPodDevice( int database_id, bool first_time) : ConnectedDevice(url, lister, unique_id, manager, database_id, first_time), loader_thread_(new QThread(this)), - loader_(new GPodLoader(url.path(), manager->task_manager(), backend_)), + loader_(), db_(NULL) { - InitBackendDirectory(url.path(), first_time); +} + +void GPodDevice::Init() { + InitBackendDirectory(url_.path(), first_time_); model_->Init(); + loader_ = new GPodLoader(url_.path(), manager_->task_manager(), backend_); loader_->moveToThread(loader_thread_); connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); @@ -70,12 +74,7 @@ void GPodDevice::StartCopy() { db_busy_.lock(); } -bool GPodDevice::CopyToStorage( - const QString& source, const QString&, - const Song& metadata, bool, bool remove_original) -{ - Q_ASSERT(db_); - +Itdb_Track* GPodDevice::AddTrackToITunesDb(const Song& metadata) { // Create the track Itdb_Track* track = itdb_track_new(); metadata.ToItdb(track); @@ -86,6 +85,26 @@ bool GPodDevice::CopyToStorage( Itdb_Playlist* mpl = itdb_playlist_mpl(db_); itdb_playlist_add_track(mpl, track, -1); + return track; +} + +void GPodDevice::AddTrackToModel(Itdb_Track* track, const QString& prefix) { + // Add it to our LibraryModel + Song metadata_on_device; + metadata_on_device.InitFromItdb(track); + metadata_on_device.set_directory_id(1); + metadata_on_device.set_filename(prefix + metadata_on_device.filename()); + songs_to_add_ << metadata_on_device; +} + +bool GPodDevice::CopyToStorage( + const QString& source, const QString&, + const Song& metadata, bool, bool remove_original) +{ + Q_ASSERT(db_); + + Itdb_Track* track = AddTrackToITunesDb(metadata); + // Copy the file GError* error = NULL; itdb_cp_track_to_ipod(track, source.toLocal8Bit().constData(), &error); @@ -99,12 +118,7 @@ bool GPodDevice::CopyToStorage( return false; } - // Add it to our LibraryModel - Song metadata_on_device; - metadata_on_device.InitFromItdb(track); - metadata_on_device.set_directory_id(1); - metadata_on_device.set_filename(url_.path() + metadata_on_device.filename()); - songs_to_add_ << metadata_on_device; + AddTrackToModel(track, url_.path()); // Remove the original if it was requested if (remove_original) { @@ -123,6 +137,8 @@ void GPodDevice::FinishCopy() { emit Error(QString::fromUtf8(error->message)); g_error_free(error); } else { + FinaliseDatabase(); + // Update the library model if (!songs_to_add_.isEmpty()) backend_->AddOrUpdateSongs(songs_to_add_); diff --git a/src/devices/gpoddevice.h b/src/devices/gpoddevice.h index e757a5b33..a9e243d77 100644 --- a/src/devices/gpoddevice.h +++ b/src/devices/gpoddevice.h @@ -37,6 +37,8 @@ public: int database_id, bool first_time); ~GPodDevice(); + void Init(); + static QStringList url_schemes() { return QStringList() << "ipod"; } void StartCopy(); @@ -48,10 +50,16 @@ public: bool DeleteFromStorage(const Song& metadata); void FinishDelete(); -private slots: +protected slots: void LoadFinished(Itdb_iTunesDB* db); -private: +protected: + Itdb_Track* AddTrackToITunesDb(const Song& metadata); + void AddTrackToModel(Itdb_Track* track, const QString& prefix); + + virtual void FinaliseDatabase() {} + +protected: QThread* loader_thread_; GPodLoader* loader_; diff --git a/src/devices/imobiledeviceconnection.cpp b/src/devices/imobiledeviceconnection.cpp index e035f6fc8..8caa54b66 100644 --- a/src/devices/imobiledeviceconnection.cpp +++ b/src/devices/imobiledeviceconnection.cpp @@ -141,3 +141,58 @@ QString iMobileDeviceConnection::GetFileInfo(const QString& path, const QString& free(infolist); return ret; } + +bool iMobileDeviceConnection::Exists(const QString& path) { + return !GetFileInfo(path, "st_ifmt").isNull(); +} + +QString iMobileDeviceConnection::GetUnusedFilename( + Itdb_iTunesDB* itdb, const Song& metadata) { + // This function does the same as itdb_cp_get_dest_filename, except it + // accesses the device's filesystem through imobiledevice. + + // Get the total number of F.. directories + int total_musicdirs = 0; + for ( ; ; ++total_musicdirs) { + QString dir; + dir.sprintf("/iTunes_Control/Music/F%02d", total_musicdirs); + + if (!Exists(dir)) + break; + } + + if (total_musicdirs <= 0) { + qWarning() << "No 'F..'' directories found on iPod"; + return QString(); + } + + // Pick one at random + const int dir_num = qrand() % total_musicdirs; + QString dir; + dir.sprintf("/iTunes_Control/Music/F%02d", dir_num); + + if (!Exists(dir)) { + qWarning() << "Music directory doesn't exist:" << dir; + return QString(); + } + + // Use the same file extension as the original file, default to mp3. + QString extension = metadata.filename().section('.', -1, -1).toLower(); + if (extension.isEmpty()) + extension = "mp3"; + + // Loop until we find an unused filename. + // Use the same naming convention as libgpod, which is + // "libgpod" + 6-digit random number + static const int kRandMax = 999999; + QString filename; + forever { + filename.sprintf("libgpod%06d", qrand() % kRandMax); + filename += "." + extension; + + if (!Exists(dir + "/" + filename)) + break; + } + + return dir + "/" + filename; +} diff --git a/src/devices/imobiledeviceconnection.h b/src/devices/imobiledeviceconnection.h index a24222d44..46f367c19 100644 --- a/src/devices/imobiledeviceconnection.h +++ b/src/devices/imobiledeviceconnection.h @@ -17,9 +17,12 @@ #ifndef IMOBILEDEVICECONNECTION_H #define IMOBILEDEVICECONNECTION_H +#include "core/song.h" + #include #include #include +#include #include #include @@ -35,10 +38,14 @@ public: quint64 GetInfoLongLong(const QString& key); QStringList ReadDirectory(const QString& path, QDir::Filters filters = QDir::NoFilter); + QString GetFileInfo(const QString& path, const QString& key); + bool Exists(const QString& path); + + QString GetUnusedFilename(Itdb_iTunesDB* itdb, const Song& metadata); + private: Q_DISABLE_COPY(iMobileDeviceConnection); - QString GetFileInfo(const QString& path, const QString& key); idevice_t device_; lockdownd_client_t lockdown_;