Copy files to afc devices. Doesn't quite work yet

This commit is contained in:
David Sansome 2010-08-08 17:41:06 +00:00
parent d29fb119a4
commit a228e2b806
17 changed files with 267 additions and 69 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -29,6 +29,8 @@ namespace Utilities {
QString MakeTempDir();
void RemoveRecursive(const QString& path);
void CopyStr(const QString& str, char** dest_p);
}
#endif // UTILITIES_H

View File

@ -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 <QThread>
@ -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) {

View File

@ -17,7 +17,7 @@
#ifndef AFCDEVICE_H
#define AFCDEVICE_H
#include "connecteddevice.h"
#include "gpoddevice.h"
#include <QMutex>
#include <QWaitCondition>
@ -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

View File

@ -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());
}

View File

@ -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_;

View File

@ -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),

View File

@ -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_;

View File

@ -469,6 +469,8 @@ boost::shared_ptr<ConnectedDevice> 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)));

View File

@ -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();
}

View File

@ -34,6 +34,8 @@ public:
int database_id, bool first_time);
~FilesystemDevice();
void Init();
static QStringList url_schemes() { return QStringList() << "file"; }
private:

View File

@ -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_);

View File

@ -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_;

View File

@ -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;
}

View File

@ -17,9 +17,12 @@
#ifndef IMOBILEDEVICECONNECTION_H
#define IMOBILEDEVICECONNECTION_H
#include "core/song.h"
#include <libimobiledevice/afc.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <gpod/itdb.h>
#include <QDir>
#include <QStringList>
@ -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_;