mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
fix: use hause_arrest or afc2 client whenever possible
This commit is contained in:
+71
-46
@@ -45,11 +45,11 @@
|
||||
|
||||
AfcExplorerWidget::AfcExplorerWidget(
|
||||
const std::shared_ptr<iDescriptorDevice> device, bool favEnabled,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, QString root,
|
||||
QWidget *parent)
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, bool useAfc2,
|
||||
QString root, QWidget *parent)
|
||||
: QWidget(parent), m_device(device), m_favEnabled(favEnabled),
|
||||
m_hauseArrest(hause_arrest), m_errorMessage("Failed to load directory"),
|
||||
m_root(root)
|
||||
m_root(root), m_useAfc2(useAfc2)
|
||||
{
|
||||
|
||||
QVBoxLayout *rootLayout = new QVBoxLayout(this);
|
||||
@@ -74,7 +74,26 @@ AfcExplorerWidget::AfcExplorerWidget(
|
||||
rootLayout->addWidget(m_loadingWidget);
|
||||
m_loadingWidget->setupContentWidget(contentContainer);
|
||||
|
||||
if (m_hauseArrest.has_value() && m_hauseArrest.value() != nullptr) {
|
||||
connect(m_loadingWidget, &ZLoadingWidget::retryClicked, this, [this]() {
|
||||
m_loadingWidget->showLoading();
|
||||
QTimer::singleShot(100, this, [this]() { loadPath(m_history.top()); });
|
||||
});
|
||||
|
||||
if (m_useAfc2) {
|
||||
bool is_available = m_device->afc2_backend->is_available();
|
||||
if (!is_available) {
|
||||
qDebug()
|
||||
<< "[AfcExplorerWidget] AFC2 is not available on this device.";
|
||||
m_loadingWidget->showError("AFC2 is not available on this device.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_useAfc2) {
|
||||
connect(m_device->afc2_backend,
|
||||
&CXX::Afc2Backend::check_is_dir_and_list_finished, this,
|
||||
&AfcExplorerWidget::onLoadPathFinished);
|
||||
} else if (m_hauseArrest.has_value() && m_hauseArrest.value() != nullptr) {
|
||||
connect(m_hauseArrest.value().get(),
|
||||
&CXX::HauseArrest::check_is_dir_and_list_finished, this,
|
||||
&AfcExplorerWidget::onLoadPathFinished);
|
||||
@@ -203,11 +222,26 @@ void AfcExplorerWidget::updateAddressBar(const QString &path)
|
||||
void AfcExplorerWidget::loadPath(const QString &path)
|
||||
{
|
||||
m_loadingWidget->showLoading();
|
||||
|
||||
if (m_useAfc2) {
|
||||
bool is_available = m_device->afc2_backend->is_available();
|
||||
if (!is_available) {
|
||||
qDebug()
|
||||
<< "[AfcExplorerWidget] AFC2 is not available on this device.";
|
||||
m_loadingWidget->showError("AFC2 is not available on this device.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressBar(path);
|
||||
updateNavigationButtons();
|
||||
|
||||
// FIXME: we need a better approach to this
|
||||
// similar code is repeated in some places
|
||||
/* use the correct afc client */
|
||||
if (m_hauseArrest.has_value() && m_hauseArrest.value() != nullptr) {
|
||||
if (m_useAfc2) {
|
||||
m_device->afc2_backend->check_is_dir_and_list(path);
|
||||
} else if (m_hauseArrest.has_value() && m_hauseArrest.value() != nullptr) {
|
||||
m_hauseArrest.value()->check_is_dir_and_list(path);
|
||||
} else {
|
||||
m_device->afc_backend->check_is_dir_and_list(path);
|
||||
@@ -286,31 +320,8 @@ void AfcExplorerWidget::onFileListContextMenu(const QPoint &pos)
|
||||
if (filesToExport.isEmpty())
|
||||
return;
|
||||
|
||||
QString dir =
|
||||
QFileDialog::getExistingDirectory(this, "Select Export Directory");
|
||||
if (dir.isEmpty())
|
||||
return;
|
||||
handleExport(filesToExport);
|
||||
|
||||
// FIXME
|
||||
// Convert to ExportItem list
|
||||
// QList<ExportItem> exportItems;
|
||||
// QString currPath = "/";
|
||||
// if (!m_history.isEmpty())
|
||||
// currPath = m_history.top();
|
||||
// if (!currPath.endsWith("/"))
|
||||
// currPath += "/";
|
||||
|
||||
// for (QListWidgetItem *selItem : filesToExport) {
|
||||
// QString fileName = selItem->text();
|
||||
// QString devicePath =
|
||||
// currPath == "/" ? "/" + fileName : currPath + fileName;
|
||||
// exportItems.append(
|
||||
// ExportItem(devicePath, fileName, m_device->udid));
|
||||
// }
|
||||
|
||||
// ExportManager::sharedInstance()->startExport(
|
||||
// m_device, exportItems, dir, "Exporting from file Explorer",
|
||||
// m_afc);
|
||||
} else if (selectedAction == openAction) {
|
||||
onItemDoubleClicked(item);
|
||||
} else if (selectedAction == openNativeAction) {
|
||||
@@ -333,31 +344,45 @@ void AfcExplorerWidget::onExportClicked()
|
||||
if (filesToExport.isEmpty())
|
||||
return;
|
||||
|
||||
handleExport(filesToExport);
|
||||
}
|
||||
|
||||
void AfcExplorerWidget::handleExport(QList<QListWidgetItem *> filesToExport)
|
||||
{
|
||||
|
||||
// Ask user for a directory to save all files
|
||||
QString dir =
|
||||
QFileDialog::getExistingDirectory(this, "Select Export Directory");
|
||||
if (dir.isEmpty())
|
||||
return;
|
||||
|
||||
// FIXME
|
||||
// // Convert to ExportItem list
|
||||
// QList<ExportItem> exportItems;
|
||||
// QString currPath = "/";
|
||||
// if (!m_history.isEmpty())
|
||||
// currPath = m_history.top();
|
||||
// if (!currPath.endsWith("/"))
|
||||
// currPath += "/";
|
||||
QList<QString> exportItems;
|
||||
QString currPath = "/";
|
||||
if (!m_history.isEmpty())
|
||||
currPath = m_history.top();
|
||||
if (!currPath.endsWith("/"))
|
||||
currPath += "/";
|
||||
|
||||
// for (QListWidgetItem *item : filesToExport) {
|
||||
// QString fileName = item->text();
|
||||
// QString devicePath =
|
||||
// currPath == "/" ? "/" + fileName : currPath + fileName;
|
||||
// exportItems.append(ExportItem(devicePath, fileName, m_device->udid));
|
||||
// }
|
||||
for (QListWidgetItem *selItem : filesToExport) {
|
||||
QString fileName = selItem->text();
|
||||
QString devicePath =
|
||||
currPath == "/" ? "/" + fileName : currPath + fileName;
|
||||
exportItems.append(devicePath);
|
||||
}
|
||||
|
||||
// // Start export
|
||||
// ExportManager::sharedInstance()->startExport(
|
||||
// m_device, exportItems, dir, "Exporting from file Explorer", m_afc);
|
||||
if (m_useAfc2) {
|
||||
IOManagerClient::sharedInstance()->startExport(
|
||||
m_device, exportItems, dir, "Exporting from File Explorer <AFC2>",
|
||||
true);
|
||||
} else if (m_hauseArrest.has_value() && m_hauseArrest.value() != nullptr) {
|
||||
IOManagerClient::sharedInstance()->startExport(
|
||||
m_device, exportItems, dir,
|
||||
"Exporting from File Explorer (App Container)",
|
||||
m_hauseArrest.value()->get_bundle_id());
|
||||
} else {
|
||||
IOManagerClient::sharedInstance()->startExport(
|
||||
m_device, exportItems, dir, "Exporting from File Explorer", true);
|
||||
}
|
||||
}
|
||||
|
||||
void AfcExplorerWidget::exportAndOpenSelectedFile(QListWidgetItem *item,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "iomanagerclient.h"
|
||||
#include "zloadingwidget.h"
|
||||
#include <QAction>
|
||||
#include <QEvent>
|
||||
@@ -50,7 +51,8 @@ public:
|
||||
bool favEnabled = false,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>>
|
||||
hause_arrest = std::nullopt,
|
||||
QString root = "/", QWidget *parent = nullptr);
|
||||
bool useAfc2 = false, QString root = "/",
|
||||
QWidget *parent = nullptr);
|
||||
void navigateToPath(const QString &path);
|
||||
void goHome();
|
||||
signals:
|
||||
@@ -95,6 +97,7 @@ private:
|
||||
QString m_errorMessage;
|
||||
QString m_root;
|
||||
ZLoadingWidget *m_loadingWidget;
|
||||
bool m_useAfc2;
|
||||
|
||||
// Export system
|
||||
ExportManager *m_exportManager;
|
||||
@@ -119,6 +122,8 @@ private:
|
||||
void onLoadPathFinished(bool success,
|
||||
const QMap<QString, QVariant> &entries);
|
||||
|
||||
void handleExport(QList<QListWidgetItem *> filesToExport);
|
||||
|
||||
#ifndef WIN32
|
||||
void updateNavStyles();
|
||||
|
||||
|
||||
+5
-10
@@ -61,6 +61,7 @@ void AppContext::cachePairedDevices()
|
||||
#endif
|
||||
}
|
||||
|
||||
/* addDevice is only called with udid from backend */
|
||||
void AppContext::addDevice(iDescriptor::Uniq uniq,
|
||||
iDescriptor::IdeviceConnectionType conn_type,
|
||||
AddType addType, QString info,
|
||||
@@ -73,10 +74,7 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
|
||||
}
|
||||
|
||||
std::shared_ptr<iDescriptorDevice> existingDevice = nullptr;
|
||||
// existingDevice = getDeviceByMacAddress(uniq.get());
|
||||
if (!existingDevice) {
|
||||
existingDevice = getDevice(uniq.get());
|
||||
}
|
||||
existingDevice = getDevice(uniq.get());
|
||||
|
||||
if (existingDevice) {
|
||||
uniq.isMac() ? emit deviceAlreadyExistsMAC(uniq)
|
||||
@@ -88,7 +86,7 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
|
||||
}
|
||||
|
||||
if (addType == AddType::Pairing) {
|
||||
// handlePairing(uniq, true);
|
||||
handlePairing(uniq, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,7 +128,8 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
|
||||
.ios_version = deviceInfo.parsedDeviceVersion.major,
|
||||
.service_manager = new CXX::ServiceManager(
|
||||
uniq.get(), deviceInfo.parsedDeviceVersion.major),
|
||||
.afc_backend = new CXX::AfcBackend(uniq.get())};
|
||||
.afc_backend = new CXX::AfcBackend(uniq.get()),
|
||||
.afc2_backend = new CXX::Afc2Backend(uniq.get())};
|
||||
|
||||
m_devices[device.udid] = std::make_shared<iDescriptorDevice>(device);
|
||||
|
||||
@@ -284,10 +283,6 @@ void AppContext::addRecoveryDevice(uint64_t ecid)
|
||||
|
||||
AppContext::~AppContext()
|
||||
{
|
||||
for (auto device : m_devices) {
|
||||
// freeDevice(device);
|
||||
}
|
||||
|
||||
m_devices.clear();
|
||||
|
||||
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
|
||||
@@ -45,7 +45,7 @@ AppDownloadDialog::AppDownloadDialog(const QString &appName,
|
||||
setFixedWidth(500);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_loadingWidget = new ZLoadingWidget(true, this);
|
||||
m_loadingWidget = new ZLoadingWidget(false, this);
|
||||
layout()->addWidget(m_loadingWidget);
|
||||
QVBoxLayout *contentLayout = new QVBoxLayout();
|
||||
contentLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
@@ -31,7 +31,7 @@ FileExplorerWidget::FileExplorerWidget(
|
||||
QVBoxLayout *rootLayout = new QVBoxLayout(this);
|
||||
rootLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_loadingWidget = new ZLoadingWidget(true, this);
|
||||
m_loadingWidget = new ZLoadingWidget(false, this);
|
||||
rootLayout->addWidget(m_loadingWidget);
|
||||
}
|
||||
|
||||
@@ -60,20 +60,18 @@ void FileExplorerWidget::init()
|
||||
|
||||
// Add normal AFC explorer (index 0)
|
||||
AfcExplorerWidget *afcExplorer =
|
||||
new AfcExplorerWidget(m_device, true, std::nullopt, "/", this);
|
||||
new AfcExplorerWidget(m_device, true, std::nullopt, false, "/", this);
|
||||
connect(afcExplorer, &AfcExplorerWidget::favoritePlaceAdded, this,
|
||||
&FileExplorerWidget::saveFavoritePlace);
|
||||
|
||||
m_stackedWidget->addWidget(afcExplorer);
|
||||
|
||||
// FIXME: AFC2
|
||||
// // Add AFC2 explorer (index 1)
|
||||
// AfcExplorerWidget *afc2Explorer =
|
||||
// new AfcExplorerWidget(m_device, true, m_device->afc2Client, "/",
|
||||
// this);
|
||||
// connect(afc2Explorer, &AfcExplorerWidget::favoritePlaceAdded, this,
|
||||
// &FileExplorerWidget::saveFavoritePlaceAfc2);
|
||||
// m_stackedWidget->addWidget(afc2Explorer);
|
||||
// Add AFC2 explorer (index 1)
|
||||
AfcExplorerWidget *afc2Explorer =
|
||||
new AfcExplorerWidget(m_device, true, std::nullopt, true, "/", this);
|
||||
connect(afc2Explorer, &AfcExplorerWidget::favoritePlaceAdded, this,
|
||||
&FileExplorerWidget::saveFavoritePlaceAfc2);
|
||||
m_stackedWidget->addWidget(afc2Explorer);
|
||||
|
||||
// Start with normal AFC client
|
||||
m_stackedWidget->setCurrentIndex(0);
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
#endif
|
||||
|
||||
// rust codebase
|
||||
#include "idescriptor_rust_codebase/src/afc2_services.cxxqt.h"
|
||||
#include "idescriptor_rust_codebase/src/afc_services.cxxqt.h"
|
||||
#include "idescriptor_rust_codebase/src/hause_arrest.cxxqt.h"
|
||||
#include "idescriptor_rust_codebase/src/io_manager.cxxqt.h"
|
||||
@@ -226,6 +227,7 @@ struct iDescriptorDevice {
|
||||
unsigned int ios_version;
|
||||
CXX::ServiceManager *service_manager;
|
||||
CXX::AfcBackend *afc_backend;
|
||||
CXX::Afc2Backend *afc2_backend;
|
||||
};
|
||||
|
||||
void fullDeviceInfo(const pugi::xml_document &doc, DeviceInfo &d);
|
||||
|
||||
+24
-7
@@ -63,14 +63,15 @@ void ImageLoader::requestThumbnail(
|
||||
void ImageLoader::requestImageWithCallback(
|
||||
const std::shared_ptr<iDescriptorDevice> device, const QString &path,
|
||||
int priority, std::function<void(const QPixmap &)> callback,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest)
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, bool useAfc2)
|
||||
{
|
||||
|
||||
/*
|
||||
FIXME: row is passed as priority
|
||||
nothing dangerous but a bit hacky, could be handled better
|
||||
*/ //scale=false
|
||||
auto *task = new ImageTask(device, path, priority, false, hause_arrest);
|
||||
auto *task =
|
||||
new ImageTask(device, path, priority, false, hause_arrest, useAfc2);
|
||||
|
||||
/*
|
||||
TODO: should we do this ?
|
||||
@@ -174,7 +175,7 @@ void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap,
|
||||
// almost a copy of loadThumbnailFromDevice but without any scaling logic
|
||||
QPixmap ImageLoader::loadImage(
|
||||
const std::shared_ptr<iDescriptorDevice> device, const QString &filePath,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest)
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, bool useAfc2)
|
||||
{
|
||||
if (QCoreApplication::closingDown()) {
|
||||
qDebug() << "Application is closing, aborting loadImage for"
|
||||
@@ -183,7 +184,9 @@ QPixmap ImageLoader::loadImage(
|
||||
}
|
||||
QByteArray imageData;
|
||||
|
||||
if (hause_arrest.has_value() && hause_arrest.value()) {
|
||||
if (useAfc2) {
|
||||
imageData = device->afc2_backend->file_to_buffer(filePath);
|
||||
} else if (hause_arrest.has_value() && hause_arrest.value()) {
|
||||
qDebug() << "Loading image using HauseArrest for:" << filePath;
|
||||
imageData = hause_arrest.value()->file_to_buffer(filePath);
|
||||
} else {
|
||||
@@ -226,7 +229,7 @@ QPixmap ImageLoader::loadImage(
|
||||
QPixmap ImageLoader::loadThumbnailFromDevice(
|
||||
const std::shared_ptr<iDescriptorDevice> device, const QString &filePath,
|
||||
const QSize &size,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest)
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, bool useAfc2)
|
||||
{
|
||||
if (QCoreApplication::closingDown()) {
|
||||
qDebug()
|
||||
@@ -235,7 +238,16 @@ QPixmap ImageLoader::loadThumbnailFromDevice(
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray imageData = device->afc_backend->file_to_buffer(filePath);
|
||||
QByteArray imageData;
|
||||
|
||||
if (useAfc2) {
|
||||
device->afc2_backend->file_to_buffer(filePath);
|
||||
} else if (hause_arrest.has_value() && hause_arrest.value()) {
|
||||
qDebug() << "Loading thumbnail using HauseArrest for:" << filePath;
|
||||
imageData = hause_arrest.value()->file_to_buffer(filePath);
|
||||
} else {
|
||||
imageData = device->afc_backend->file_to_buffer(filePath);
|
||||
}
|
||||
|
||||
if (imageData.isEmpty()) {
|
||||
qDebug() << "Could not read from device:" << filePath;
|
||||
@@ -277,7 +289,7 @@ QPixmap ImageLoader::loadThumbnailFromDevice(
|
||||
QPixmap ImageLoader::generateVideoThumbnailFFmpeg(
|
||||
const std::shared_ptr<iDescriptorDevice> device, const QString &filePath,
|
||||
const QSize &requestedSize,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest)
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest, bool useAfc2)
|
||||
{
|
||||
QPixmap thumbnail;
|
||||
if (QCoreApplication::closingDown()) {
|
||||
@@ -287,6 +299,11 @@ QPixmap ImageLoader::generateVideoThumbnailFFmpeg(
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME: other afc clients are not respected here, we need to handle this
|
||||
better, currently only the normal afc client is used for video thumbnail
|
||||
generation
|
||||
*/
|
||||
CXX::AfcBackend *afc = device->afc_backend;
|
||||
|
||||
const qint64 fileSize = afc->get_file_size(filePath);
|
||||
|
||||
+8
-4
@@ -31,7 +31,8 @@ public:
|
||||
const std::shared_ptr<iDescriptorDevice> device, const QString &path,
|
||||
int priority, std::function<void(const QPixmap &)> callback,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest =
|
||||
std::nullopt);
|
||||
std::nullopt,
|
||||
bool useAfc2 = false);
|
||||
void cancelThumbnail(const QString &path);
|
||||
bool isLoading(const QString &path);
|
||||
void clear();
|
||||
@@ -40,16 +41,19 @@ public:
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QString &filePath, const QSize &size,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest =
|
||||
std::nullopt);
|
||||
std::nullopt,
|
||||
bool useAfc2 = false);
|
||||
static QPixmap generateVideoThumbnailFFmpeg(
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QString &filePath, const QSize &size,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest =
|
||||
std::nullopt);
|
||||
std::nullopt,
|
||||
bool useAfc2 = false);
|
||||
static QPixmap loadImage(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QString &filePath,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>>
|
||||
hause_arrest = std::nullopt);
|
||||
hause_arrest = std::nullopt,
|
||||
bool useAfc2 = false);
|
||||
signals:
|
||||
void thumbnailReady(const QString &path, const QPixmap &image,
|
||||
unsigned int row);
|
||||
|
||||
+9
-6
@@ -18,9 +18,10 @@ public:
|
||||
ImageTask(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QString &path, unsigned int row, bool scale = true,
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> hause_arrest =
|
||||
std::nullopt)
|
||||
std::nullopt,
|
||||
bool useAfc2 = false)
|
||||
: m_device(device), m_path(path), m_isThumbnail(scale), m_row(row),
|
||||
m_hause_arrest(hause_arrest)
|
||||
m_hause_arrest(hause_arrest), m_useAfc2(useAfc2)
|
||||
{
|
||||
setAutoDelete(true);
|
||||
}
|
||||
@@ -35,18 +36,19 @@ protected:
|
||||
|
||||
if (isVideo) {
|
||||
QPixmap thumbnail = ImageLoader::generateVideoThumbnailFFmpeg(
|
||||
m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest);
|
||||
m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest, m_useAfc2);
|
||||
|
||||
emit finished(m_path, thumbnail, m_row);
|
||||
} else {
|
||||
if (m_isThumbnail) {
|
||||
QPixmap image = ImageLoader::loadThumbnailFromDevice(
|
||||
m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest);
|
||||
m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest,
|
||||
m_useAfc2);
|
||||
emit finished(m_path, image, m_row);
|
||||
} else {
|
||||
qDebug() << "Loading full image for:" << m_path;
|
||||
QPixmap image =
|
||||
ImageLoader::loadImage(m_device, m_path, m_hause_arrest);
|
||||
QPixmap image = ImageLoader::loadImage(
|
||||
m_device, m_path, m_hause_arrest, m_useAfc2);
|
||||
emit finished(m_path, image, m_row);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +60,7 @@ private:
|
||||
bool m_isThumbnail;
|
||||
unsigned int m_row;
|
||||
std::optional<std::shared_ptr<CXX::HauseArrest>> m_hause_arrest;
|
||||
bool m_useAfc2;
|
||||
};
|
||||
|
||||
#endif // IMAGETASK_H
|
||||
|
||||
@@ -205,6 +205,7 @@ void InstalledAppsWidget::showLoadingState()
|
||||
|
||||
void InstalledAppsWidget::showErrorState(const QString &error)
|
||||
{
|
||||
m_zloadingWidget->stop(true);
|
||||
m_errorLabel->setText(QString("Error loading apps: %1").arg(error));
|
||||
m_stackedWidget->setCurrentWidget(m_errorWidget);
|
||||
}
|
||||
@@ -285,14 +286,13 @@ void InstalledAppsWidget::createContentWidget()
|
||||
|
||||
void InstalledAppsWidget::onAppsDataReady(const QMap<QString, QVariant> &result)
|
||||
{
|
||||
|
||||
m_zloadingWidget->stop(true);
|
||||
if (result.isEmpty()) {
|
||||
showErrorState("No apps found or failed to retrieve apps.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_stackedWidget->setCurrentWidget(m_contentWidget);
|
||||
m_zloadingWidget->stop(true);
|
||||
|
||||
// Clear existing tabs
|
||||
qDeleteAll(m_appTabs);
|
||||
@@ -493,7 +493,7 @@ void InstalledAppsWidget::onContainerDataReady(bool success)
|
||||
|
||||
// Create AfcExplorerWidget with the house arrest AFC client
|
||||
AfcExplorerWidget *explorer = new AfcExplorerWidget(
|
||||
m_device, true, m_houseArrestAfcClient, "/Documents", this);
|
||||
m_device, true, m_houseArrestAfcClient, false, "/Documents", this);
|
||||
explorer->setStyleSheet("border :none;");
|
||||
m_containerLayout->addWidget(explorer);
|
||||
}
|
||||
|
||||
+93
-1
@@ -30,7 +30,7 @@ IOManagerClient::IOManagerClient(QObject *parent) : QObject(parent) {}
|
||||
void IOManagerClient::startExport(
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items, const QString &destinationPath,
|
||||
const QString &exportTitle, std::optional<bool> altAfc)
|
||||
const QString &exportTitle)
|
||||
{
|
||||
qDebug() << "startExport() entry - items:" << items.size()
|
||||
<< "dest:" << destinationPath;
|
||||
@@ -72,6 +72,98 @@ void IOManagerClient::startExport(
|
||||
<< "items";
|
||||
}
|
||||
|
||||
/* hause_arrest */
|
||||
void IOManagerClient::startExport(
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items, const QString &destinationPath,
|
||||
const QString &exportTitle, const QString &bundleId)
|
||||
{
|
||||
qDebug() << "startExport() hause_arrest entry - items:" << items.size()
|
||||
<< "dest:" << destinationPath;
|
||||
if (!device) {
|
||||
qWarning() << "Invalid device provided to ExportManager";
|
||||
QMessageBox::critical(nullptr, "Export Error",
|
||||
"Invalid device specified for export.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.isEmpty()) {
|
||||
qWarning() << "No items provided for export";
|
||||
QMessageBox::information(nullptr, "Export Error",
|
||||
"No items selected for export.");
|
||||
return;
|
||||
}
|
||||
|
||||
QDir destDir(destinationPath);
|
||||
if (!destDir.exists()) {
|
||||
if (!destDir.mkpath(".")) {
|
||||
qWarning() << "Could not create destination directory:"
|
||||
<< destinationPath;
|
||||
QMessageBox::critical(nullptr, "Export Error",
|
||||
"Could not create destination directory.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QUuid jobId = QUuid::createUuid();
|
||||
|
||||
StatusBalloon::sharedInstance()->startProcess(
|
||||
exportTitle, items.size(), destinationPath, ProcessType::Export, jobId);
|
||||
|
||||
AppContext::sharedInstance()->ioManager->start_export_with_hause_arrest_afc(
|
||||
device->udid, jobId, items, destinationPath, bundleId);
|
||||
|
||||
qDebug() << "Started export job with hause_arrest_afc" << jobId << "for"
|
||||
<< items.size() << "items";
|
||||
}
|
||||
|
||||
/* afc2 */
|
||||
void IOManagerClient::startExport(
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items, const QString &destinationPath,
|
||||
const QString &exportTitle, bool useAfc2)
|
||||
{
|
||||
qDebug() << "startExport() hause_arrest entry - items:" << items.size()
|
||||
<< "dest:" << destinationPath;
|
||||
if (!device) {
|
||||
qWarning() << "Invalid device provided to ExportManager";
|
||||
QMessageBox::critical(nullptr, "Export Error",
|
||||
"Invalid device specified for export.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.isEmpty()) {
|
||||
qWarning() << "No items provided for export";
|
||||
QMessageBox::information(nullptr, "Export Error",
|
||||
"No items selected for export.");
|
||||
return;
|
||||
}
|
||||
|
||||
QDir destDir(destinationPath);
|
||||
if (!destDir.exists()) {
|
||||
if (!destDir.mkpath(".")) {
|
||||
qWarning() << "Could not create destination directory:"
|
||||
<< destinationPath;
|
||||
QMessageBox::critical(nullptr, "Export Error",
|
||||
"Could not create destination directory.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QUuid jobId = QUuid::createUuid();
|
||||
|
||||
StatusBalloon::sharedInstance()->startProcess(
|
||||
exportTitle, items.size(), destinationPath, ProcessType::Export, jobId);
|
||||
|
||||
AppContext::sharedInstance()->ioManager->start_export_with_afc2(
|
||||
device->udid, jobId, items, destinationPath);
|
||||
|
||||
qDebug() << "Started export job with afc2" << jobId << "for" << items.size()
|
||||
<< "items";
|
||||
}
|
||||
|
||||
void IOManagerClient::startImport(
|
||||
const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items, const QString &destinationPath,
|
||||
|
||||
+10
-2
@@ -38,14 +38,22 @@ class IOManagerClient : public QObject
|
||||
public:
|
||||
static IOManagerClient *sharedInstance();
|
||||
|
||||
// Delete copy and assignment operators
|
||||
IOManagerClient(const IOManagerClient &) = delete;
|
||||
IOManagerClient &operator=(const IOManagerClient &) = delete;
|
||||
|
||||
void startExport(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items,
|
||||
const QString &destinationPath, const QString &jobTitle);
|
||||
/* afc2 */
|
||||
void startExport(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items,
|
||||
const QString &destinationPath, const QString &jobTitle,
|
||||
std::optional<bool> altAfc = std::nullopt);
|
||||
bool useAfc2);
|
||||
/* hause_arrest_afc*/
|
||||
void startExport(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items,
|
||||
const QString &destinationPath, const QString &exportTitle,
|
||||
const QString &bundleId);
|
||||
|
||||
void startImport(const std::shared_ptr<iDescriptorDevice> device,
|
||||
const QList<QString> &items,
|
||||
|
||||
@@ -4,6 +4,7 @@ fn main() {
|
||||
CxxQtBuilder::new()
|
||||
.file("src/lib.rs")
|
||||
.file("src/afc_services.rs")
|
||||
.file("src/afc2_services.rs")
|
||||
.file("src/service_manager.rs")
|
||||
.file("src/screenshot.rs")
|
||||
.file("src/hause_arrest.rs")
|
||||
|
||||
@@ -0,0 +1,781 @@
|
||||
use cxx_qt::Threading;
|
||||
use cxx_qt_lib::{QByteArray, QList, QMap, QMapPair_QString_QVariant, QString};
|
||||
|
||||
use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, afc, run_sync};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
afc::{AfcClient, opcode::AfcFopenMode},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::{io::SeekFrom, pin::Pin};
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufWriter};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{Semaphore, oneshot};
|
||||
|
||||
#[cxx_qt::bridge(namespace = "CXX")]
|
||||
mod qobject {
|
||||
#[namespace = ""]
|
||||
unsafe extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
include!("cxx-qt-lib/qlist.h");
|
||||
include!("cxx-qt-lib/qbytearray.h");
|
||||
include!("cxx-qt-lib/qmap.h");
|
||||
include!("cxx-qt-lib/qvariant.h");
|
||||
|
||||
type QString = cxx_qt_lib::QString;
|
||||
type QList_QString = cxx_qt_lib::QList<QString>;
|
||||
type QByteArray = cxx_qt_lib::QByteArray;
|
||||
type QMap_QString_QVariant = cxx_qt_lib::QMap<cxx_qt_lib::QMapPair_QString_QVariant>;
|
||||
}
|
||||
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
type Afc2Backend = super::RAfc2Backend;
|
||||
|
||||
#[qinvokable]
|
||||
fn set_udid(self: Pin<&mut Afc2Backend>, udid: &QString);
|
||||
|
||||
#[qinvokable]
|
||||
fn load_album_list(self: Pin<&mut Afc2Backend>);
|
||||
|
||||
#[qinvokable]
|
||||
fn list_dir(self: &Afc2Backend, path: &QString) -> QList_QString;
|
||||
|
||||
#[qinvokable]
|
||||
fn file_to_buffer(self: &Afc2Backend, file_path: &QString) -> QByteArray;
|
||||
|
||||
#[qinvokable]
|
||||
fn is_directory(self: &Afc2Backend, path: &QString) -> bool;
|
||||
|
||||
#[qinvokable]
|
||||
fn get_file_size(self: &Afc2Backend, path: &QString) -> i64;
|
||||
|
||||
#[qinvokable]
|
||||
fn read_file_range(self: &Afc2Backend, path: &QString, offset: i64, len: i64)
|
||||
-> QByteArray;
|
||||
|
||||
#[qinvokable]
|
||||
fn check_is_dir_and_list(self: &Afc2Backend, path: &QString);
|
||||
#[qsignal]
|
||||
fn check_is_dir_and_list_finished(
|
||||
self: Pin<&mut Afc2Backend>,
|
||||
success: bool,
|
||||
entries: &QMap_QString_QVariant,
|
||||
);
|
||||
|
||||
#[qsignal]
|
||||
fn album_list_loaded(self: Pin<&mut Afc2Backend>, udid: QString, album_list: QList_QString);
|
||||
|
||||
#[qinvokable]
|
||||
fn get_dirs_item_count(self: &Afc2Backend, dir: &QList_QString) -> i64;
|
||||
|
||||
#[qinvokable]
|
||||
fn list_files_flat(self: &Afc2Backend, dir: &QString) -> QList_QString;
|
||||
|
||||
#[qinvokable]
|
||||
fn start_video_stream(self: &Afc2Backend, file_path: &QString) -> QString;
|
||||
|
||||
#[qinvokable]
|
||||
fn is_available(self: &Afc2Backend) -> bool;
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for Afc2Backend {}
|
||||
impl cxx_qt::Constructor<(QString,), NewArguments = (QString,)> for Afc2Backend {}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RAfc2Backend {
|
||||
udid: QString,
|
||||
}
|
||||
impl cxx_qt::Constructor<(QString,)> for qobject::Afc2Backend {
|
||||
type BaseArguments = ();
|
||||
type InitializeArguments = ();
|
||||
type NewArguments = (QString,);
|
||||
|
||||
fn route_arguments(
|
||||
args: (QString,),
|
||||
) -> (
|
||||
Self::NewArguments,
|
||||
Self::BaseArguments,
|
||||
Self::InitializeArguments,
|
||||
) {
|
||||
(args, (), ())
|
||||
}
|
||||
|
||||
fn new(args: (QString,)) -> RAfc2Backend {
|
||||
RAfc2Backend { udid: args.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl qobject::Afc2Backend {
|
||||
fn get_udid(&self) -> &QString {
|
||||
use cxx_qt::CxxQtType;
|
||||
&self.rust().udid
|
||||
}
|
||||
|
||||
fn set_udid(mut self: Pin<&mut Self>, udid: &QString) {
|
||||
use cxx_qt::CxxQtType;
|
||||
self.as_mut().rust_mut().udid = udid.clone();
|
||||
}
|
||||
|
||||
fn is_available(self: &Self) -> bool {
|
||||
let udid_string = self.get_udid().to_string();
|
||||
|
||||
run_sync(async move {
|
||||
let device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_string.as_str())
|
||||
.cloned();
|
||||
if let Some(d) = device {
|
||||
d.afc2.is_some()
|
||||
} else {
|
||||
eprintln!("Device with UDID {} not found", udid_string);
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_directory(self: &Self, path: &QString) -> bool {
|
||||
let udid_string = self.get_udid().to_string();
|
||||
let path_string = path.to_string();
|
||||
|
||||
run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_string.as_str())
|
||||
.cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid_string);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid_string);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
match afc.get_file_info(path_string.clone()).await {
|
||||
Ok(info) => info.st_ifmt == "S_IFDIR",
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get file info for {path_string}: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn list_dir(self: &Self, path: &QString) -> QList<QString> {
|
||||
let udid = self.get_udid().to_string();
|
||||
let path_str = path.to_string();
|
||||
let list = run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
match afc.list_dir(&path_str).await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read directory {path_str}: {e}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut qlist: QList<QString> = QList::default();
|
||||
for name in list {
|
||||
qlist.append(QString::from(name));
|
||||
}
|
||||
qlist
|
||||
}
|
||||
|
||||
fn check_is_dir_and_list(self: &Self, path: &QString) {
|
||||
let udid = self.get_udid().to_string();
|
||||
let path_str = path.to_string();
|
||||
let qt_t = self.qt_thread();
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
let qt_thread = qt_t.clone();
|
||||
let afc_arc = {
|
||||
let device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
qt_thread
|
||||
.queue(move |q| {
|
||||
q.check_is_dir_and_list_finished(
|
||||
false,
|
||||
&QMap::<QMapPair_QString_QVariant>::default(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
qt_thread
|
||||
.queue(move |q| {
|
||||
q.check_is_dir_and_list_finished(
|
||||
false,
|
||||
&QMap::<QMapPair_QString_QVariant>::default(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
let map_result = afc::check_is_dir_and_list(&mut afc, path_str).await;
|
||||
|
||||
qt_thread
|
||||
.queue(move |q| {
|
||||
q.check_is_dir_and_list_finished(true, &map_result);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
fn load_album_list(self: Pin<&mut Self>) {
|
||||
let qt_t = self.qt_thread();
|
||||
let udid_owned = self.get_udid().clone();
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
let udid_str = udid_owned.to_string();
|
||||
let afc_arc = {
|
||||
let device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_str.as_str())
|
||||
.cloned();
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid_str);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid_str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("Device found: {:?}", udid_str);
|
||||
|
||||
// list entries in /DCIM
|
||||
let mut afc = afc_arc.lock().await;
|
||||
let album_names = match afc.list_dir("/DCIM").await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load /DCIM directory: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Regexes: ^\d{3}APPLE$ and ^\d{8}$
|
||||
static RE_3DIGIT_APPLE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\d{3}APPLE$").unwrap());
|
||||
static RE_DATE_YYYYMMDD: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{8}$").unwrap());
|
||||
|
||||
let mut qlist_album: QList<QString> = QList::default();
|
||||
|
||||
for name in album_names {
|
||||
// skip . and ..
|
||||
if name == "." || name == ".." {
|
||||
continue;
|
||||
}
|
||||
|
||||
// name filter
|
||||
let matches_name = name.contains("APPLE")
|
||||
|| RE_3DIGIT_APPLE.is_match(&name)
|
||||
|| RE_DATE_YYYYMMDD.is_match(&name);
|
||||
|
||||
if !matches_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check it's a directory
|
||||
let full_path = format!("/DCIM/{name}");
|
||||
match afc.get_file_info(full_path).await {
|
||||
Ok(info) => {
|
||||
if info.st_ifmt != "S_IFDIR" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
qlist_album.append(QString::from(name));
|
||||
}
|
||||
let qt_thread = qt_t.clone();
|
||||
qt_thread
|
||||
.queue(move |backend_qobj| {
|
||||
backend_qobj.album_list_loaded(udid_owned.clone(), qlist_album);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn file_to_buffer(&self, album_path: &QString) -> QByteArray {
|
||||
let udid = self.get_udid().to_string();
|
||||
let album_path_string = album_path.to_string();
|
||||
|
||||
let data: Vec<u8> = run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("file_to_buffer: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
let mut fd = match afc
|
||||
.open(album_path_string.clone(), AfcFopenMode::RdOnly)
|
||||
.await
|
||||
{
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("file_to_buffer: failed to open {album_path_string}: {e}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut chunk = vec![0u8; 8192];
|
||||
|
||||
loop {
|
||||
let n = match fd.read(&mut chunk).await {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("file_to_buffer: failed to read {album_path_string}: {e}");
|
||||
buf.clear();
|
||||
break;
|
||||
}
|
||||
};
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
buf.extend_from_slice(&chunk[..n]);
|
||||
}
|
||||
fd.close().await.ok();
|
||||
buf
|
||||
});
|
||||
|
||||
if data.is_empty() {
|
||||
QByteArray::default()
|
||||
} else {
|
||||
QByteArray::from(&data[..])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_size(self: &Self, path: &QString) -> i64 {
|
||||
let udid = self.get_udid().to_string();
|
||||
let path_string = path.to_string();
|
||||
|
||||
run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("file_to_buffer: device {udid} not found");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
afc::get_file_size(&mut afc, path_string)
|
||||
.await
|
||||
.map(|v| v as i64)
|
||||
.unwrap_or(-1)
|
||||
})
|
||||
}
|
||||
|
||||
fn read_file_range(&self, path: &QString, offset: i64, len: i64) -> QByteArray {
|
||||
if offset < 0 || len <= 0 {
|
||||
return QByteArray::default();
|
||||
}
|
||||
|
||||
let udid = self.get_udid().to_string();
|
||||
let path_string = path.to_string();
|
||||
|
||||
let data: Vec<u8> = run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("read_file_range: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
let mut fd = match afc.open(path_string.clone(), AfcFopenMode::RdOnly).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("read_file_range: open({path_string}) failed: {e}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
if offset > 0 {
|
||||
if let Err(e) = fd.seek(SeekFrom::Start(offset as u64)).await {
|
||||
eprintln!("read_file_range: seek({path_string}, {offset}) failed: {e}");
|
||||
let _ = fd.close().await;
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut remaining = len as usize;
|
||||
let mut chunk = vec![0u8; 8192];
|
||||
|
||||
while remaining > 0 {
|
||||
let to_read = remaining.min(chunk.len());
|
||||
let n = match fd.read(&mut chunk[..to_read]).await {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("read_file_range: read({path_string}) failed: {e}");
|
||||
buf.clear();
|
||||
break;
|
||||
}
|
||||
};
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
buf.extend_from_slice(&chunk[..n]);
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
let _ = fd.close().await;
|
||||
buf
|
||||
});
|
||||
|
||||
if data.is_empty() {
|
||||
QByteArray::default()
|
||||
} else {
|
||||
QByteArray::from(&data[..])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_dirs_item_count(self: &Self, dirs: &QList<QString>) -> i64 {
|
||||
let udid = self.get_udid().to_string();
|
||||
|
||||
let mut dir_vec: Vec<String> = Vec::new();
|
||||
for i in 0..dirs.len() {
|
||||
if let Some(qdir) = dirs.get(i) {
|
||||
dir_vec.push(qdir.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("get_dirs_item_count: device {udid} not found");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
let mut total: i64 = 0;
|
||||
|
||||
for dir_str in dir_vec {
|
||||
let names = match afc.list_dir(&dir_str).await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("get_dirs_item_count: list_dir({dir_str}) failed: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let count = names
|
||||
.into_iter()
|
||||
.filter(|name| name != "." && name != "..")
|
||||
.count() as i64;
|
||||
|
||||
total += count;
|
||||
}
|
||||
|
||||
total
|
||||
})
|
||||
}
|
||||
|
||||
fn list_files_flat(self: &Self, dir: &QString) -> QList<QString> {
|
||||
let udid = self.get_udid().to_string();
|
||||
let dir_str = dir.to_string();
|
||||
|
||||
let entries = run_sync(async move {
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("list_files_flat: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
match device.afc2 {
|
||||
Some(afc) => afc.clone(),
|
||||
None => {
|
||||
eprintln!("AFC2 service not available for device {}", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
let names = match afc.list_dir(&dir_str).await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("list_files_flat: list_dir({dir_str}) failed: {e}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let mut files = Vec::new();
|
||||
for name in names {
|
||||
if name == "." || name == ".." {
|
||||
continue;
|
||||
}
|
||||
let full_path = format!("{}/{}", dir_str, name);
|
||||
|
||||
match afc.get_file_info(full_path.clone()).await {
|
||||
Ok(info) => {
|
||||
if info.st_ifmt != "S_IFDIR" {
|
||||
files.push(full_path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("list_files_flat: get_file_info({full_path}) failed: {e}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
files
|
||||
});
|
||||
|
||||
let mut qlist: QList<QString> = QList::default();
|
||||
for path in entries {
|
||||
qlist.append(QString::from(path));
|
||||
}
|
||||
qlist
|
||||
}
|
||||
|
||||
fn start_video_stream(&self, file_path: &QString) -> QString {
|
||||
let udid_str = self.get_udid().to_string();
|
||||
let path_str = file_path.to_string();
|
||||
let cloned_path = path_str.clone();
|
||||
|
||||
eprintln!(
|
||||
"start_video_stream: request udid={} path={}",
|
||||
udid_str, cloned_path
|
||||
);
|
||||
|
||||
// bind ephemeral port on localhost
|
||||
let listener = match std::net::TcpListener::bind("127.0.0.1:0") {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("start_video_stream: bind failed: {e}");
|
||||
return QString::default();
|
||||
}
|
||||
};
|
||||
let local_addr = match listener.local_addr() {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
eprintln!("start_video_stream: local_addr failed: {e}");
|
||||
return QString::default();
|
||||
}
|
||||
};
|
||||
listener.set_nonblocking(true).ok();
|
||||
|
||||
// create Tokio TcpListener inside runtime
|
||||
let std_listener = {
|
||||
let _guard = RUNTIME.handle().enter();
|
||||
match TcpListener::from_std(listener) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("start_video_stream: from_std failed: {e}");
|
||||
return QString::default();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let port = local_addr.port();
|
||||
|
||||
let encoded = urlencoding::encode(&cloned_path);
|
||||
let url = format!("http://127.0.0.1:{}/{}", port, encoded);
|
||||
let url_clone = url.clone();
|
||||
let url_clone_for_log = url.clone();
|
||||
let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
|
||||
{
|
||||
let mut map = VIDEO_STREAMS.lock().unwrap();
|
||||
map.insert(url.clone(), shutdown_tx);
|
||||
}
|
||||
eprintln!(
|
||||
"start_video_stream: serving {} for udid={} path={}",
|
||||
url_clone, udid_str, cloned_path
|
||||
);
|
||||
// accept-loop task
|
||||
RUNTIME.spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut shutdown_rx => {
|
||||
// shutdown requested
|
||||
eprintln!("start_video_stream: shutdown requested for {}", url_clone);
|
||||
break;
|
||||
}
|
||||
accept_res = std_listener.accept() => {
|
||||
let (socket, peer) = match accept_res {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("start_video_stream: accept error: {e} on {}", url_clone);
|
||||
break;
|
||||
}
|
||||
};
|
||||
eprintln!("start_video_stream: accepted connection from {} on {}", peer, url_clone);
|
||||
|
||||
let udid_clone = udid_str.clone();
|
||||
let path_clone = path_str.clone();
|
||||
|
||||
let mut afc_client = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_clone).cloned();
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
// FIXME
|
||||
// eprintln!(
|
||||
// "handle_http_connection: device {} not found for {}",
|
||||
// udid, path
|
||||
// );
|
||||
// let _ = socket
|
||||
// .write_all(
|
||||
// b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
|
||||
// )
|
||||
// .await;
|
||||
// let _ = socket.shutdown().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let provider = device.provider.lock().await;
|
||||
match AfcClient::new_afc2(provider.as_ref()).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
//FIXME
|
||||
// eprintln!(
|
||||
// "handle_http_connection: AfcClient::connect failed for {}: {:?}",
|
||||
// path, e
|
||||
// );
|
||||
// let _ = socket
|
||||
// .write_all(
|
||||
// b"HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
|
||||
// )
|
||||
// .await;
|
||||
// let _ = socket.shutdown().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
tokio::spawn(async move {
|
||||
afc::handle_http_connection(&mut afc_client, path_clone, socket).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("start_video_stream: accept-loop exiting for {}", url_clone);
|
||||
});
|
||||
|
||||
QString::from(url_clone_for_log)
|
||||
}
|
||||
}
|
||||
+145
-117
@@ -114,28 +114,31 @@ impl qobject::AfcBackend {
|
||||
use cxx_qt::CxxQtType;
|
||||
self.as_mut().rust_mut().udid = udid.clone();
|
||||
}
|
||||
|
||||
fn is_directory(self: &Self, path: &QString) -> bool {
|
||||
let udid_string = self.get_udid().to_string();
|
||||
let path_string = path.to_string();
|
||||
|
||||
run_sync(async move {
|
||||
// get device once
|
||||
let maybe_device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_string.as_str())
|
||||
.cloned();
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_string.as_str())
|
||||
.cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid_string);
|
||||
return false;
|
||||
}
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid_string);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
// use AFC inside the same async block, no nested block_on
|
||||
let mut afc = device.afc.lock().await;
|
||||
let mut afc = afc_arc.lock().await;
|
||||
match afc.get_file_info(path_string.clone()).await {
|
||||
Ok(info) => info.st_ifmt == "S_IFDIR",
|
||||
Err(e) => {
|
||||
@@ -150,21 +153,26 @@ impl qobject::AfcBackend {
|
||||
let udid = self.get_udid().to_string();
|
||||
let path_str = path.to_string();
|
||||
let list = run_sync(async move {
|
||||
let device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
match device.afc.lock().await.list_dir(&path_str).await {
|
||||
let mut afc = afc_arc.lock().await;
|
||||
match afc.list_dir(&path_str).await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read directory {path_str}: {e}");
|
||||
return Vec::new();
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -182,26 +190,29 @@ impl qobject::AfcBackend {
|
||||
let qt_t = self.qt_thread();
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
let device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let qt_thread = qt_t.clone();
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
qt_thread
|
||||
.queue(move |q| {
|
||||
q.check_is_dir_and_list_finished(
|
||||
false,
|
||||
&QMap::<QMapPair_QString_QVariant>::default(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
let afc = {
|
||||
let device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid);
|
||||
qt_thread
|
||||
.queue(move |q| {
|
||||
q.check_is_dir_and_list_finished(
|
||||
false,
|
||||
&QMap::<QMapPair_QString_QVariant>::default(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
let mut afc = device.afc.lock().await;
|
||||
|
||||
let mut afc = afc.lock().await;
|
||||
let map_result = afc::check_is_dir_and_list(&mut afc, path_str).await;
|
||||
|
||||
qt_thread
|
||||
@@ -218,70 +229,75 @@ impl qobject::AfcBackend {
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
let udid_str = udid_owned.to_string();
|
||||
let device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_str.as_str())
|
||||
.cloned();
|
||||
if let Some(device) = device {
|
||||
println!("Device found: {:?}", udid_str);
|
||||
|
||||
// list entries in /DCIM
|
||||
let album_names = match device.afc.lock().await.list_dir("/DCIM").await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load /DCIM directory: {e}");
|
||||
let afc_arc = {
|
||||
let device = APP_DEVICE_STATE
|
||||
.lock()
|
||||
.await
|
||||
.get(udid_str.as_str())
|
||||
.cloned();
|
||||
let device = match device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("Device with UDID {} not found", udid_str);
|
||||
return;
|
||||
}
|
||||
};
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
// Regexes: ^\d{3}APPLE$ and ^\d{8}$
|
||||
static RE_3DIGIT_APPLE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\d{3}APPLE$").unwrap());
|
||||
static RE_DATE_YYYYMMDD: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\d{8}$").unwrap());
|
||||
println!("Device found: {:?}", udid_str);
|
||||
|
||||
let mut qlist_album: QList<QString> = QList::default();
|
||||
// list entries in /DCIM
|
||||
let mut afc = afc_arc.lock().await;
|
||||
let album_names = match afc.list_dir("/DCIM").await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load /DCIM directory: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for name in album_names {
|
||||
// skip . and ..
|
||||
if name == "." || name == ".." {
|
||||
continue;
|
||||
}
|
||||
// Regexes: ^\d{3}APPLE$ and ^\d{8}$
|
||||
static RE_3DIGIT_APPLE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\d{3}APPLE$").unwrap());
|
||||
static RE_DATE_YYYYMMDD: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{8}$").unwrap());
|
||||
|
||||
// name filter
|
||||
let matches_name = name.contains("APPLE")
|
||||
|| RE_3DIGIT_APPLE.is_match(&name)
|
||||
|| RE_DATE_YYYYMMDD.is_match(&name);
|
||||
let mut qlist_album: QList<QString> = QList::default();
|
||||
|
||||
if !matches_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check it's a directory
|
||||
let full_path = format!("/DCIM/{name}");
|
||||
match device.afc.lock().await.get_file_info(full_path).await {
|
||||
Ok(info) => {
|
||||
if info.st_ifmt != "S_IFDIR" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
qlist_album.append(QString::from(name));
|
||||
for name in album_names {
|
||||
// skip . and ..
|
||||
if name == "." || name == ".." {
|
||||
continue;
|
||||
}
|
||||
|
||||
let qt_thread = qt_t.clone();
|
||||
qt_thread
|
||||
.queue(move |backend_qobj| {
|
||||
backend_qobj.album_list_loaded(udid_owned.clone(), qlist_album);
|
||||
})
|
||||
.unwrap();
|
||||
} else {
|
||||
eprintln!("Device with UDID {} not found", udid_str);
|
||||
return;
|
||||
// name filter
|
||||
let matches_name = name.contains("APPLE")
|
||||
|| RE_3DIGIT_APPLE.is_match(&name)
|
||||
|| RE_DATE_YYYYMMDD.is_match(&name);
|
||||
|
||||
if !matches_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check it's a directory
|
||||
let full_path = format!("/DCIM/{name}");
|
||||
match afc.get_file_info(full_path).await {
|
||||
Ok(info) => {
|
||||
if info.st_ifmt != "S_IFDIR" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
qlist_album.append(QString::from(name));
|
||||
}
|
||||
let qt_thread = qt_t.clone();
|
||||
qt_thread
|
||||
.queue(move |backend_qobj| {
|
||||
backend_qobj.album_list_loaded(udid_owned.clone(), qlist_album);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -380,17 +396,21 @@ impl qobject::AfcBackend {
|
||||
let path_string = path.to_string();
|
||||
|
||||
let data: Vec<u8> = run_sync(async move {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("read_file_range: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("read_file_range: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
let mut afc = device.afc.lock().await;
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
let mut fd = match afc.open(path_string.clone(), AfcFopenMode::RdOnly).await {
|
||||
Ok(f) => f,
|
||||
@@ -451,17 +471,21 @@ impl qobject::AfcBackend {
|
||||
}
|
||||
|
||||
run_sync(async move {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("get_dirs_item_count: device {udid} not found");
|
||||
return -1;
|
||||
}
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("get_dirs_item_count: device {udid} not found");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
let mut afc = device.afc.lock().await;
|
||||
let mut afc = afc_arc.lock().await;
|
||||
let mut total: i64 = 0;
|
||||
|
||||
for dir_str in dir_vec {
|
||||
@@ -490,17 +514,21 @@ impl qobject::AfcBackend {
|
||||
let dir_str = dir.to_string();
|
||||
|
||||
let entries = run_sync(async move {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
let afc_arc = {
|
||||
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
|
||||
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("list_files_flat: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
let device = match maybe_device {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("list_files_flat: device {udid} not found");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
device.afc.clone()
|
||||
};
|
||||
|
||||
let mut afc = device.afc.lock().await;
|
||||
let mut afc = afc_arc.lock().await;
|
||||
|
||||
let names = match afc.list_dir(&dir_str).await {
|
||||
Ok(list) => list,
|
||||
|
||||
@@ -31,6 +31,9 @@ mod qobject {
|
||||
#[qobject]
|
||||
type HauseArrest = super::RHauseArrestBackend;
|
||||
|
||||
#[qinvokable]
|
||||
fn get_bundle_id(self: &HauseArrest) -> &QString;
|
||||
|
||||
#[qinvokable]
|
||||
fn init_session(self: Pin<&mut HauseArrest>);
|
||||
#[qsignal]
|
||||
@@ -95,6 +98,12 @@ impl qobject::HauseArrest {
|
||||
use cxx_qt::CxxQtType;
|
||||
&self.rust().udid
|
||||
}
|
||||
|
||||
fn get_bundle_id(&self) -> &QString {
|
||||
use cxx_qt::CxxQtType;
|
||||
&self.rust().bundle_id
|
||||
}
|
||||
|
||||
fn init_session(self: Pin<&mut Self>) {
|
||||
let udid_str = self.udid.to_string();
|
||||
let bundle_id_str = self.bundle_id.to_string();
|
||||
|
||||
@@ -39,6 +39,25 @@ mod qobject {
|
||||
destination_dir: &QString,
|
||||
);
|
||||
|
||||
#[qinvokable]
|
||||
fn start_export_with_afc2(
|
||||
self: Pin<&mut IOManager>,
|
||||
udid: &QString,
|
||||
job_id: &QUuid,
|
||||
device_paths: &QList_QString,
|
||||
destination_dir: &QString,
|
||||
);
|
||||
|
||||
#[qinvokable]
|
||||
fn start_export_with_hause_arrest_afc(
|
||||
self: Pin<&mut IOManager>,
|
||||
udid: &QString,
|
||||
job_id: &QUuid,
|
||||
device_paths: &QList_QString,
|
||||
destination_dir: &QString,
|
||||
hause_arrest_afc: &QString,
|
||||
);
|
||||
|
||||
#[qinvokable]
|
||||
fn start_import(
|
||||
self: Pin<&mut IOManager>,
|
||||
@@ -300,7 +319,7 @@ impl qobject::IOManager {
|
||||
job_id: &qobject::QUuid,
|
||||
device_paths: &qobject::QList_QString,
|
||||
destination_dir: &qobject::QString,
|
||||
hause_arrest_afc: &String,
|
||||
hause_arrest_afc: &qobject::QString,
|
||||
) {
|
||||
let udid_str = udid.to_string();
|
||||
let dest_dir_str = destination_dir.to_string();
|
||||
|
||||
+106
-135
@@ -30,6 +30,7 @@ use once_cell::sync::Lazy;
|
||||
use plist::Value;
|
||||
mod afc;
|
||||
mod afc_services;
|
||||
mod afc2_services;
|
||||
mod hause_arrest;
|
||||
mod io_manager;
|
||||
mod screenshot;
|
||||
@@ -38,32 +39,22 @@ mod utils;
|
||||
|
||||
const POSSIBLE_ROOT: &str = "../../../../";
|
||||
const APP_LABEL: &str = "iDescriptor";
|
||||
const EV_CONNECTED: u32 = 1;
|
||||
const EV_DISCONNECTED: u32 = 2;
|
||||
const EV_PAIRING_PENDING: u32 = 3;
|
||||
const EV_FAIL_KIND: u32 = 4;
|
||||
|
||||
// #[derive(Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceServices {
|
||||
pub afc: Arc<Mutex<AfcClient>>,
|
||||
pub afc2: Option<Arc<Mutex<AfcClient>>>,
|
||||
pub diag: Arc<Mutex<DiagnosticsRelayClient>>,
|
||||
pub heartbeat_task: Option<JoinHandle<()>>,
|
||||
pub heartbeat_task: Option<Arc<JoinHandle<()>>>,
|
||||
pub video_streams: Arc<Mutex<HashMap<String, Arc<AtomicBool>>>>,
|
||||
pub provider: Arc<Mutex<Box<dyn idevice::provider::IdeviceProvider>>>,
|
||||
pub lockdown: Arc<Mutex<LockdownClient>>,
|
||||
}
|
||||
|
||||
impl Clone for DeviceServices {
|
||||
fn clone(&self) -> Self {
|
||||
DeviceServices {
|
||||
afc: self.afc.clone(),
|
||||
afc2: self.afc2.clone(),
|
||||
diag: self.diag.clone(),
|
||||
// FIXME?
|
||||
heartbeat_task: None,
|
||||
video_streams: self.video_streams.clone(),
|
||||
provider: self.provider.clone(),
|
||||
lockdown: self.lockdown.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static APP_DEVICE_STATE: Lazy<Mutex<HashMap<String, DeviceServices>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
@@ -205,27 +196,39 @@ impl qobject::Core {
|
||||
APP_LABEL,
|
||||
);
|
||||
|
||||
// FIXME: return if fails
|
||||
let info: (String, String) =
|
||||
init_idescriptor_device(
|
||||
provider,
|
||||
qt_thread.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let info = init_idescriptor_device(
|
||||
provider,
|
||||
qt_thread.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let udid_for_event = info.0.clone();
|
||||
let info_for_event = info.1.clone();
|
||||
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
1,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(info_for_event),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
match info {
|
||||
Some((udid_for_event, info_for_event)) => {
|
||||
qt_thread
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_CONNECTED,
|
||||
&QString::from(
|
||||
udid_for_event,
|
||||
),
|
||||
&QString::from(
|
||||
info_for_event,
|
||||
),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
// FIXME: sometimes happens
|
||||
/*
|
||||
init_idescriptor_device: Attempting to start Lockdown session.
|
||||
init_idescriptor_device: Lockdown session started.
|
||||
init_idescriptor_device: Attempting to get default values from Lockdown.
|
||||
init_idescriptor_device: Default values obtained.
|
||||
init_idescriptor_device: Attempting to connect to AFC client.
|
||||
AfcClient::connect failed: PasswordProtected
|
||||
*/
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
|
||||
if already_paired {
|
||||
@@ -233,19 +236,36 @@ impl qobject::Core {
|
||||
return;
|
||||
}
|
||||
|
||||
fn emit_pairing_failed(
|
||||
qt_thread: cxx_qt::CxxQtThread<Core>,
|
||||
udid: String,
|
||||
reason : &str,
|
||||
) {
|
||||
let reason_clone = reason.to_string();
|
||||
qt_thread
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_FAIL_KIND,
|
||||
&QString::from(udid),
|
||||
// FIXME: reason is not info
|
||||
&QString::from(reason_clone),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
// pairing pending
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
3,
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_PAIRING_PENDING,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
let ev_fail_kind = 4;
|
||||
|
||||
let mut uc2 = match UsbmuxdConnection::default()
|
||||
.await
|
||||
@@ -253,15 +273,7 @@ impl qobject::Core {
|
||||
Ok(u) => u,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to connect to usbmuxd");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -270,23 +282,13 @@ impl qobject::Core {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to get device from usbmuxd");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let provider = dev.to_provider(
|
||||
UsbmuxdAddr::default(),
|
||||
"iDescriptor",
|
||||
);
|
||||
let provider = dev
|
||||
.to_provider(UsbmuxdAddr::default(), APP_LABEL);
|
||||
|
||||
let mut lc = match LockdownClient::connect(
|
||||
&provider,
|
||||
@@ -296,15 +298,7 @@ impl qobject::Core {
|
||||
Ok(l) => l,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to connect to Lockdown");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -313,15 +307,7 @@ impl qobject::Core {
|
||||
Ok(b) => b,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to get BUID from usbmuxd");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -343,15 +329,9 @@ impl qobject::Core {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
// FIXME: we may not want to emit here
|
||||
// because if user doesnt accept pairing in time, it will be considered a failure
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to pair device");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -361,15 +341,7 @@ impl qobject::Core {
|
||||
Ok(b) => b,
|
||||
Err(_) => {
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to serialize pairing file");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -378,15 +350,7 @@ impl qobject::Core {
|
||||
uc2.save_pair_record(&udid, bytes).await
|
||||
{
|
||||
let udid_for_event = udid.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
ev_fail_kind,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to save pairing record to usbmuxd");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -400,14 +364,14 @@ impl qobject::Core {
|
||||
|
||||
let qt_thread = qt_t.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
2,
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_DISCONNECTED,
|
||||
&QString::from(udid),
|
||||
&QString::from(""),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -448,26 +412,33 @@ impl qobject::Core {
|
||||
Err(e) => {
|
||||
let qt_thread = qt_t.clone();
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.no_pairing_file(&QString::from(mac_address_owned));
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.no_pairing_file(&QString::from(mac_address_owned));
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
eprintln!("Failed to read pairing file: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let t = TcpProvider {
|
||||
addr: ip_owned.parse::<IpAddr>().unwrap(),
|
||||
pairing_file: pairing_file,
|
||||
label: APP_LABEL.to_string(),
|
||||
let addr = match ip_owned.parse::<IpAddr>() {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
//FIXME: emit event for failure
|
||||
eprintln!("Invalid IP address {}: {}", ip_owned, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let t = TcpProvider {
|
||||
addr,
|
||||
pairing_file,
|
||||
label: APP_LABEL.to_string(),
|
||||
};
|
||||
|
||||
|
||||
let result = tokio::select! {
|
||||
res = init_idescriptor_device(t, qt_t.clone()) => {
|
||||
res
|
||||
}
|
||||
let result = tokio::select! {
|
||||
res = init_idescriptor_device(t, qt_t.clone()) => res,
|
||||
// timeout
|
||||
_ = tokio::time::sleep(tokio::time::Duration::from_secs(10)) => {
|
||||
eprintln!("Timeout collecting device info for wireless device mac address: {mac_address_owned}");
|
||||
@@ -481,9 +452,9 @@ impl qobject::Core {
|
||||
let qt_thread = qt_t.clone();
|
||||
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
1,
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_CONNECTED,
|
||||
&QString::from(udid),
|
||||
&QString::from(info),
|
||||
);
|
||||
@@ -495,8 +466,8 @@ impl qobject::Core {
|
||||
let qt_thread = qt_t.clone();
|
||||
|
||||
qt_thread
|
||||
.queue(move |Core_qobj| {
|
||||
Core_qobj.init_failed(&QString::from(mac_address_owned));
|
||||
.queue(move |core_qobj| {
|
||||
core_qobj.init_failed(&QString::from(mac_address_owned));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -666,7 +637,7 @@ async fn init_idescriptor_device<
|
||||
if is_wireless {
|
||||
let mut hb_for_task = hb.take().unwrap();
|
||||
let udid_for_hb = udid.clone();
|
||||
hb_task = Some(RUNTIME.spawn(async move {
|
||||
hb_task = Some(Arc::new(RUNTIME.spawn(async move {
|
||||
eprintln!("heartbeat: starting heartbeat task ");
|
||||
let mut interval = 15u64;
|
||||
let mut fails = 0;
|
||||
@@ -686,9 +657,9 @@ async fn init_idescriptor_device<
|
||||
clean_device_from_app_state(&udid_for_hb).await;
|
||||
|
||||
let udid_for_event = udid_for_hb.clone();
|
||||
let _ = qt_thread_for_hb.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
2,
|
||||
let _ = qt_thread_for_hb.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_DISCONNECTED,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
@@ -708,9 +679,9 @@ async fn init_idescriptor_device<
|
||||
clean_device_from_app_state(&udid_for_hb).await;
|
||||
|
||||
let udid_for_event = udid_for_hb.clone();
|
||||
let _ = qt_thread_for_hb.queue(move |Core_qobj| {
|
||||
Core_qobj.device_event(
|
||||
2,
|
||||
let _ = qt_thread_for_hb.queue(move |core_qobj| {
|
||||
core_qobj.device_event(
|
||||
EV_DISCONNECTED,
|
||||
&QString::from(udid_for_event),
|
||||
&QString::from(""),
|
||||
);
|
||||
@@ -725,7 +696,7 @@ async fn init_idescriptor_device<
|
||||
}
|
||||
|
||||
eprintln!("heartbeat: heartbeat task ended.");
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
// FIXME: this cannot be done when paired wirelessly
|
||||
|
||||
+13
-2
@@ -1,7 +1,7 @@
|
||||
use crate::POSSIBLE_ROOT;
|
||||
use cxx_qt_lib::QString;
|
||||
use idevice::{
|
||||
IdeviceService, afc::AfcClient, diagnostics_relay::DiagnosticsRelayClient,
|
||||
IdeviceError, IdeviceService, afc::AfcClient, diagnostics_relay::DiagnosticsRelayClient,
|
||||
house_arrest::HouseArrestClient, installation_proxy::InstallationProxyClient,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
@@ -10,6 +10,8 @@ use plist_macro::plist;
|
||||
use rusqlite::Connection;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const PUBLIC_STAGING: &str = "PublicStaging";
|
||||
|
||||
pub async fn get_battery_info(diag: &mut DiagnosticsRelayClient) -> Option<PlistDictionary> {
|
||||
match diag.ioregistry(None, None, Some("IOPMPowerSource")).await {
|
||||
Ok(Some(dict)) => Some(dict),
|
||||
@@ -28,7 +30,7 @@ pub async fn get_cable_info(diag: &mut DiagnosticsRelayClient) -> Option<PlistDi
|
||||
}
|
||||
|
||||
pub async fn detect_jailbroken(afc: &mut AfcClient) -> bool {
|
||||
match afc.list_dir(POSSIBLE_ROOT).await {
|
||||
match afc.list_dir(format!("{}/bin", POSSIBLE_ROOT)).await {
|
||||
Ok(vec) => vec.len() > 0,
|
||||
Err(_) => false,
|
||||
}
|
||||
@@ -146,3 +148,12 @@ pub fn get_lockdown_path() -> PathBuf {
|
||||
base.join("Apple").join("Lockdown")
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure `PublicStaging` exists on device via AFC
|
||||
pub async fn ensure_public_staging(afc: &mut AfcClient) -> Result<(), IdeviceError> {
|
||||
// Try to stat and if it fails, create directory
|
||||
match afc.get_file_info(PUBLIC_STAGING).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => afc.mk_dir(PUBLIC_STAGING).await,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user