mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
implement ServiceManager to handle services safely & cleanup UI
- Added `install_ipa.cpp` to handle IPA installation on iOS devices using the installation proxy. - Introduced `ServiceManager` class to centralize and thread-safely manage device service operations, including AFC file operations. - Updated `DeviceInfoWidget` layout for improved UI responsiveness. - Refactored `GalleryWidget`, `MediaStreamer`, `PhotoExportManager`, and `PhotoModel` to utilize `ServiceManager` for safer AFC operations. - Enhanced error handling and logging across various components. - Adjusted `ToolboxWidget` to streamline device change handling and UI updates.
This commit is contained in:
@@ -82,6 +82,7 @@ find_library(TATSU_LIBRARY
|
||||
# Add QR code generation library
|
||||
pkg_check_modules(QRENCODE REQUIRED IMPORTED_TARGET libqrencode)
|
||||
pkg_check_modules(HEIF REQUIRED IMPORTED_TARGET libheif)
|
||||
pkg_check_modules(ZIP REQUIRED IMPORTED_TARGET libzip)
|
||||
find_library(IRECOVERY_LIBRARY
|
||||
NAMES irecovery-1.0
|
||||
PATHS ${CUSTOM_LIB_PATH}
|
||||
@@ -238,6 +239,7 @@ target_link_libraries(iDescriptor PRIVATE
|
||||
PkgConfig::QRENCODE
|
||||
PkgConfig::QTERMWIDGET
|
||||
PkgConfig::HEIF
|
||||
PkgConfig::ZIP
|
||||
airplay
|
||||
ipatool-go
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "mediapreviewdialog.h"
|
||||
#include "servicemanager.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
@@ -184,7 +185,7 @@ void AfcExplorerWidget::loadPath(const QString &path)
|
||||
updateAddressBar(path);
|
||||
|
||||
AFCFileTree tree =
|
||||
get_file_tree(m_currentAfcClient, m_device->device, path.toStdString());
|
||||
ServiceManager::safeGetFileTree(m_device, path.toStdString());
|
||||
if (!tree.success) {
|
||||
m_fileList->addItem("Failed to load directory");
|
||||
return;
|
||||
@@ -325,8 +326,8 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
|
||||
const char *local_path)
|
||||
{
|
||||
uint64_t handle = 0;
|
||||
if (afc_file_open(afc, device_path, AFC_FOPEN_RDONLY, &handle) !=
|
||||
AFC_E_SUCCESS) {
|
||||
if (ServiceManager::safeAfcFileOpen(m_device, device_path, AFC_FOPEN_RDONLY,
|
||||
&handle) != AFC_E_SUCCESS) {
|
||||
qDebug() << "Failed to open file on device:" << device_path;
|
||||
return -1;
|
||||
}
|
||||
@@ -339,8 +340,9 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
|
||||
|
||||
char buffer[4096];
|
||||
uint32_t bytes_read = 0;
|
||||
while (afc_file_read(afc, handle, buffer, sizeof(buffer), &bytes_read) ==
|
||||
AFC_E_SUCCESS &&
|
||||
while (ServiceManager::safeAfcFileRead(m_device, handle, buffer,
|
||||
sizeof(buffer),
|
||||
&bytes_read) == AFC_E_SUCCESS &&
|
||||
bytes_read > 0) {
|
||||
fwrite(buffer, 1, bytes_read, out);
|
||||
}
|
||||
|
||||
+28
-6
@@ -65,7 +65,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
AddType addType)
|
||||
{
|
||||
try {
|
||||
IDescriptorInitDeviceResult initResult =
|
||||
iDescriptorInitDeviceResult initResult =
|
||||
init_idescriptor_device(udid.toStdString().c_str());
|
||||
|
||||
qDebug() << "init_idescriptor_device success ?: " << initResult.success;
|
||||
@@ -78,6 +78,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
if (addType == AddType::Regular) {
|
||||
m_pendingDevices.append(udid);
|
||||
emit devicePasswordProtected(udid);
|
||||
emit deviceChange();
|
||||
QTimer::singleShot(30000, this, [this, udid]() {
|
||||
// After 30 seconds, if the device is still pending,
|
||||
// consider the pairing expired
|
||||
@@ -88,6 +89,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
<< "Pairing expired for device UDID: " << udid;
|
||||
m_pendingDevices.removeAll(udid);
|
||||
emit devicePairingExpired(udid);
|
||||
emit deviceChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -95,6 +97,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING) {
|
||||
m_pendingDevices.append(udid);
|
||||
emit devicePairPending(udid);
|
||||
emit deviceChange();
|
||||
QTimer::singleShot(30000, this, [this, udid]() {
|
||||
// After 30 seconds, if the device is still pending,
|
||||
// consider the pairing expired
|
||||
@@ -103,6 +106,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
qDebug() << "Pairing expired for device UDID: " << udid;
|
||||
m_pendingDevices.removeAll(udid);
|
||||
emit devicePairingExpired(udid);
|
||||
emit deviceChange();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -120,11 +124,16 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
.deviceInfo = initResult.deviceInfo,
|
||||
.afcClient = initResult.afcClient,
|
||||
.afc2Client = initResult.afc2Client,
|
||||
.mutex = new std::recursive_mutex(),
|
||||
};
|
||||
m_devices[device->udid] = device;
|
||||
if (addType == AddType::Regular)
|
||||
return emit deviceAdded(device);
|
||||
if (addType == AddType::Regular) {
|
||||
emit deviceAdded(device);
|
||||
emit deviceChange();
|
||||
return;
|
||||
}
|
||||
emit devicePaired(device);
|
||||
emit deviceChange();
|
||||
m_pendingDevices.removeAll(udid);
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
@@ -137,16 +146,22 @@ int AppContext::getConnectedDeviceCount() const
|
||||
return m_devices.size() + m_recoveryDevices.size();
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME:
|
||||
on macOS, sometimes you get wireless disconnects even though we are not
|
||||
listening for wireless devices it does not have any to do with us, but it
|
||||
still happens so be aware of that
|
||||
*/
|
||||
void AppContext::removeDevice(QString _udid)
|
||||
|
||||
{
|
||||
const std::string uuid = _udid.toStdString();
|
||||
qDebug() << "AppContext::removeDevice device with UUID:"
|
||||
<< QString::fromStdString(uuid);
|
||||
|
||||
if (m_pendingDevices.contains(_udid)) {
|
||||
m_pendingDevices.removeAll(_udid);
|
||||
emit devicePairingExpired(_udid);
|
||||
emit deviceChange();
|
||||
m_pendingDevices.removeAll(_udid);
|
||||
return;
|
||||
} else {
|
||||
qDebug() << "Device with UUID " + _udid +
|
||||
@@ -163,9 +178,14 @@ void AppContext::removeDevice(QString _udid)
|
||||
m_devices.remove(uuid);
|
||||
|
||||
emit deviceRemoved(uuid);
|
||||
emit deviceChange();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
|
||||
if (device->afcClient)
|
||||
afc_client_free(device->afcClient);
|
||||
idevice_free(device->device);
|
||||
delete device->mutex;
|
||||
delete device;
|
||||
}
|
||||
|
||||
@@ -181,6 +201,7 @@ void AppContext::removeRecoveryDevice(uint64_t ecid)
|
||||
|
||||
m_recoveryDevices.remove(ecid);
|
||||
emit recoveryDeviceRemoved(ecid);
|
||||
emit deviceChange();
|
||||
iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices[ecid];
|
||||
|
||||
delete deviceInfo;
|
||||
@@ -210,7 +231,7 @@ bool AppContext::noDevicesConnected() const
|
||||
|
||||
void AppContext::addRecoveryDevice(uint64_t ecid)
|
||||
{
|
||||
IDescriptorInitDeviceResultRecovery res =
|
||||
iDescriptorInitDeviceResultRecovery res =
|
||||
init_idescriptor_recovery_device(ecid);
|
||||
|
||||
if (!res.success) {
|
||||
@@ -229,6 +250,7 @@ void AppContext::addRecoveryDevice(uint64_t ecid)
|
||||
|
||||
m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice;
|
||||
emit recoveryDeviceAdded(recoveryDevice);
|
||||
emit deviceChange();
|
||||
}
|
||||
|
||||
AppContext::~AppContext()
|
||||
|
||||
@@ -37,6 +37,14 @@ signals:
|
||||
void devicePairingExpired(const QString &udid);
|
||||
void systemSleepStarting();
|
||||
void systemWakeup();
|
||||
/*
|
||||
Generic change event for any device state change we
|
||||
need this because many UI elements need to update by
|
||||
listening for this only you can watch for any event
|
||||
and using the public members of this class you can
|
||||
do anything you want
|
||||
*/
|
||||
void deviceChange();
|
||||
public slots:
|
||||
void removeDevice(QString udid);
|
||||
void addDevice(QString udid, idevice_connection_type connType,
|
||||
|
||||
@@ -32,6 +32,7 @@ void AppDownloadBaseDialog::addProgressBar(int index)
|
||||
}
|
||||
|
||||
AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
|
||||
const QString &bundleId,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), m_appName(appName), m_downloadProcess(nullptr),
|
||||
m_progressTimer(nullptr)
|
||||
@@ -42,8 +43,7 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
|
||||
m_layout->setContentsMargins(30, 30, 30, 30);
|
||||
|
||||
QLabel *nameLabel = new QLabel(appName);
|
||||
nameLabel->setStyleSheet(
|
||||
"font-size: 20px; font-weight: bold; color: #333;");
|
||||
nameLabel->setStyleSheet("font-size: 20px; font-weight: bold;");
|
||||
m_layout->addWidget(nameLabel);
|
||||
|
||||
m_actionButton = nullptr; // Derived classes set this
|
||||
@@ -51,7 +51,8 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
|
||||
|
||||
void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
|
||||
const QString &outputDir,
|
||||
int index)
|
||||
int index,
|
||||
bool promptToOpenDir)
|
||||
{
|
||||
bool acquireLicense = true;
|
||||
|
||||
@@ -65,6 +66,8 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
|
||||
if (m_actionButton)
|
||||
m_actionButton->setEnabled(false);
|
||||
|
||||
m_operationInProgress = true;
|
||||
|
||||
AppStoreManager *manager = AppStoreManager::sharedInstance();
|
||||
if (!manager) {
|
||||
QMessageBox::critical(this, "Error",
|
||||
@@ -83,28 +86,34 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
|
||||
|
||||
manager->downloadApp(
|
||||
bundleId, outputDir, "", acquireLicense,
|
||||
[this, outputDir](int result) {
|
||||
[this, outputDir, promptToOpenDir](int result) {
|
||||
if (result == 0) { // Success
|
||||
emit downloadFinished(true, "Success");
|
||||
m_progressBar->setValue(100);
|
||||
if (QMessageBox::Yes ==
|
||||
QMessageBox::question(
|
||||
this, "Download Successful",
|
||||
QString("Successfully downloaded. Would you like "
|
||||
"to open the output directory: %1?")
|
||||
.arg(outputDir))) {
|
||||
QDir dir(outputDir);
|
||||
if (!dir.exists()) {
|
||||
QMessageBox::warning(
|
||||
this, "Directory Not Found",
|
||||
QString("The directory %1 does not exist.")
|
||||
.arg(outputDir));
|
||||
} else {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl::fromLocalFile(outputDir));
|
||||
if (promptToOpenDir) {
|
||||
|
||||
if (QMessageBox::Yes ==
|
||||
QMessageBox::question(
|
||||
this, "Download Successful",
|
||||
QString("Successfully downloaded. Would you like "
|
||||
"to open the output directory: %1?")
|
||||
.arg(outputDir))) {
|
||||
QDir dir(outputDir);
|
||||
if (!dir.exists()) {
|
||||
QMessageBox::warning(
|
||||
this, "Directory Not Found",
|
||||
QString("The directory %1 does not exist.")
|
||||
.arg(outputDir));
|
||||
} else {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl::fromLocalFile(outputDir));
|
||||
}
|
||||
}
|
||||
accept();
|
||||
}
|
||||
accept();
|
||||
} else { // Failure
|
||||
emit downloadFinished(false, "Failed");
|
||||
// if (promptToOpenDir)
|
||||
QMessageBox::critical(
|
||||
this, "Download Failed",
|
||||
QString("Failed to download %1. Error code: %2")
|
||||
@@ -114,4 +123,23 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
|
||||
}
|
||||
},
|
||||
progressCallback);
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::reject()
|
||||
{
|
||||
// FIXME: we need to cancel download if it gets closed
|
||||
// if (m_operationInProgress) {
|
||||
// AppStoreManager *manager = AppStoreManager::sharedInstance();
|
||||
// m_operationInProgress = false;
|
||||
// }
|
||||
|
||||
cleanup();
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::cleanup()
|
||||
{
|
||||
if (m_progressTimer && m_progressTimer->isActive()) {
|
||||
m_progressTimer->stop();
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,20 @@ class AppDownloadBaseDialog : public QDialog
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppDownloadBaseDialog(const QString &appName,
|
||||
const QString &bundleId,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateProgressBar(int percentage);
|
||||
|
||||
signals:
|
||||
void downloadFinished(bool success, const QString &message);
|
||||
|
||||
protected:
|
||||
void reject() override;
|
||||
void startDownloadProcess(const QString &bundleId,
|
||||
const QString &workingDir, int index);
|
||||
const QString &workingDir, int index,
|
||||
bool promptToOpenDir = true);
|
||||
void checkDownloadProgress(const QString &logFilePath,
|
||||
const QString &appName,
|
||||
const QString &outputDir);
|
||||
@@ -30,6 +36,10 @@ protected:
|
||||
QString m_appName;
|
||||
QPushButton *m_actionButton;
|
||||
QVBoxLayout *m_layout;
|
||||
bool m_operationInProgress = false;
|
||||
|
||||
private slots:
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
#endif // APPDOWNLOADBASEDIALOG_H
|
||||
|
||||
@@ -12,7 +12,7 @@ AppDownloadDialog::AppDownloadDialog(const QString &appName,
|
||||
const QString &bundleId,
|
||||
const QString &description,
|
||||
QWidget *parent)
|
||||
: AppDownloadBaseDialog(appName, parent),
|
||||
: AppDownloadBaseDialog(appName, bundleId, parent),
|
||||
m_outputDir(
|
||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)),
|
||||
m_bundleId(bundleId)
|
||||
@@ -32,7 +32,7 @@ AppDownloadDialog::AppDownloadDialog(const QString &appName,
|
||||
// Directory selection UI
|
||||
QHBoxLayout *dirLayout = new QHBoxLayout();
|
||||
QLabel *dirTextLabel = new QLabel("Save to:");
|
||||
dirTextLabel->setStyleSheet("font-size: 14px; color: #333;");
|
||||
dirTextLabel->setStyleSheet("font-size: 14px;");
|
||||
dirLayout->addWidget(dirTextLabel);
|
||||
|
||||
m_dirLabel = new ClickableLabel(this);
|
||||
@@ -60,9 +60,7 @@ AppDownloadDialog::AppDownloadDialog(const QString &appName,
|
||||
|
||||
m_actionButton = new QPushButton("Download IPA");
|
||||
m_actionButton->setFixedHeight(40);
|
||||
m_actionButton->setStyleSheet(
|
||||
"background-color: #34C759; color: white; border: none; border-radius: "
|
||||
"6px; font-size: 16px; font-weight: bold;");
|
||||
m_actionButton->setDefault(true);
|
||||
connect(m_actionButton, &QPushButton::clicked, this,
|
||||
&AppDownloadDialog::onDownloadClicked);
|
||||
layout->addWidget(m_actionButton);
|
||||
|
||||
+134
-39
@@ -1,21 +1,25 @@
|
||||
#include "appinstalldialog.h"
|
||||
#include "appcontext.h"
|
||||
#include "appdownloadbasedialog.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDir>
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
AppInstallDialog::AppInstallDialog(const QString &appName,
|
||||
const QString &description, QWidget *parent)
|
||||
: AppDownloadBaseDialog(appName, parent)
|
||||
const QString &description,
|
||||
const QString &bundleId, QWidget *parent)
|
||||
: AppDownloadBaseDialog(appName, bundleId, parent), m_bundleId(bundleId),
|
||||
m_statusLabel(nullptr), m_installWatcher(nullptr)
|
||||
{
|
||||
setWindowTitle("Install " + appName);
|
||||
setWindowTitle("Install " + appName + " - iDescriptor");
|
||||
setModal(true);
|
||||
// setFixedSize(500, 350);
|
||||
setFixedWidth(500);
|
||||
|
||||
QVBoxLayout *layout = qobject_cast<QVBoxLayout *>(this->layout());
|
||||
@@ -31,13 +35,12 @@ AppInstallDialog::AppInstallDialog(const QString &appName,
|
||||
|
||||
QVBoxLayout *detailsLayout = new QVBoxLayout();
|
||||
QLabel *nameLabel = new QLabel(appName);
|
||||
nameLabel->setStyleSheet(
|
||||
"font-size: 20px; font-weight: bold; color: #333;");
|
||||
nameLabel->setStyleSheet("font-size: 20px; font-weight: bold;");
|
||||
detailsLayout->addWidget(nameLabel);
|
||||
|
||||
QLabel *descLabel = new QLabel(description);
|
||||
descLabel->setWordWrap(true);
|
||||
descLabel->setStyleSheet("font-size: 14px; color: #666;");
|
||||
descLabel->setStyleSheet("font-size: 14px;");
|
||||
detailsLayout->addWidget(descLabel);
|
||||
|
||||
appInfoLayout->addLayout(detailsLayout);
|
||||
@@ -45,32 +48,22 @@ AppInstallDialog::AppInstallDialog(const QString &appName,
|
||||
layout->insertLayout(0, appInfoLayout);
|
||||
|
||||
QLabel *deviceLabel = new QLabel("Choose Device:");
|
||||
deviceLabel->setStyleSheet(
|
||||
"font-size: 16px; font-weight: bold; color: #333;");
|
||||
deviceLabel->setStyleSheet("font-size: 16px; font-weight: bold;");
|
||||
layout->insertWidget(1, deviceLabel);
|
||||
|
||||
m_deviceCombo = new QComboBox();
|
||||
m_deviceCombo->setStyleSheet("padding: 8px; border: 1px solid #ddd; "
|
||||
"border-radius: 4px; font-size: 14px;");
|
||||
updateDeviceList();
|
||||
layout->insertWidget(2, m_deviceCombo);
|
||||
|
||||
m_statusLabel = new QLabel("Ready to install");
|
||||
m_statusLabel->setStyleSheet("font-size: 14px; padding: 5px;");
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
layout->insertWidget(3, m_statusLabel);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
m_actionButton = new QPushButton("Install");
|
||||
m_actionButton->setFixedHeight(40);
|
||||
bool hasDevices = m_deviceCombo->count() > 0;
|
||||
if (hasDevices) {
|
||||
m_actionButton->setStyleSheet(
|
||||
"background-color: #34C759; color: white; border: none; "
|
||||
"border-radius: 6px; font-size: 16px; font-weight: bold;");
|
||||
m_actionButton->setEnabled(true);
|
||||
} else {
|
||||
m_actionButton->setStyleSheet(
|
||||
"background-color: #cccccc; color: #666; border: none; "
|
||||
"border-radius: 6px; font-size: 16px; font-weight: bold;");
|
||||
m_actionButton->setEnabled(false);
|
||||
}
|
||||
|
||||
connect(m_actionButton, &QPushButton::clicked, this,
|
||||
&AppInstallDialog::onInstallClicked);
|
||||
layout->addWidget(m_actionButton);
|
||||
@@ -82,6 +75,11 @@ AppInstallDialog::AppInstallDialog(const QString &appName,
|
||||
"border-radius: 6px; font-size: 16px;");
|
||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
layout->addWidget(cancelButton);
|
||||
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceChange, this,
|
||||
&AppInstallDialog::updateDeviceList);
|
||||
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
void AppInstallDialog::updateDeviceList()
|
||||
@@ -91,6 +89,9 @@ void AppInstallDialog::updateDeviceList()
|
||||
if (devices.empty()) {
|
||||
m_deviceCombo->addItem("No devices connected");
|
||||
m_deviceCombo->setEnabled(false);
|
||||
m_actionButton->setDefault(false);
|
||||
m_actionButton->setEnabled(false);
|
||||
m_statusLabel->setText("No devices connected");
|
||||
} else {
|
||||
m_deviceCombo->setEnabled(true);
|
||||
for (const auto &device : devices) {
|
||||
@@ -98,11 +99,58 @@ void AppInstallDialog::updateDeviceList()
|
||||
QString::fromStdString(device->deviceInfo.productType);
|
||||
QString deviceId = QString::fromStdString(device->udid);
|
||||
m_deviceCombo->addItem(
|
||||
deviceName + " (" + deviceId.left(8) + "...)", deviceId);
|
||||
deviceName + " / " + deviceId.left(8) + "...", deviceId);
|
||||
}
|
||||
m_actionButton->setDefault(true);
|
||||
m_actionButton->setEnabled(true);
|
||||
m_statusLabel->setText("Ready to install");
|
||||
}
|
||||
}
|
||||
|
||||
void AppInstallDialog::performInstallation(const QString &ipaPath,
|
||||
const QString &deviceUdid)
|
||||
{
|
||||
m_statusLabel->setText("Installing app...");
|
||||
|
||||
// Setup install watcher
|
||||
m_installWatcher = new QFutureWatcher<int>(this);
|
||||
connect(m_installWatcher, &QFutureWatcher<int>::finished, this, [this]() {
|
||||
int result = m_installWatcher->result();
|
||||
m_installWatcher->deleteLater();
|
||||
m_installWatcher = nullptr;
|
||||
|
||||
if (result == 0) {
|
||||
m_statusLabel->setText("Installation completed successfully!");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #34C759; padding: 5px;");
|
||||
QMessageBox::information(this, "Success",
|
||||
"App installed successfully!");
|
||||
accept();
|
||||
} else {
|
||||
m_statusLabel->setText("Installation failed");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #FF3B30; padding: 5px;");
|
||||
QMessageBox::critical(
|
||||
this, "Error",
|
||||
QString("Installation failed with error code: %1").arg(result));
|
||||
}
|
||||
});
|
||||
|
||||
// Run installation in background thread
|
||||
QFuture<int> future = QtConcurrent::run([ipaPath, deviceUdid]() -> int {
|
||||
iDescriptorDevice *device =
|
||||
AppContext::sharedInstance()->getDevice(deviceUdid.toStdString());
|
||||
if (!device) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
instproxy_error_t ret = install_IPA(device->device, device->afcClient,
|
||||
ipaPath.toStdString().c_str());
|
||||
return static_cast<int>(ret);
|
||||
});
|
||||
|
||||
m_installWatcher->setFuture(future);
|
||||
}
|
||||
void AppInstallDialog::onInstallClicked()
|
||||
{
|
||||
if (m_deviceCombo->count() == 0) {
|
||||
@@ -110,24 +158,71 @@ void AppInstallDialog::onInstallClicked()
|
||||
"Please connect a device first.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_deviceCombo->setEnabled(false);
|
||||
QString selectedDevice = m_deviceCombo->currentData().toString();
|
||||
// QStringList args = {"download",
|
||||
// "-b",
|
||||
// m_bundleId,
|
||||
// "-o",
|
||||
// "./",
|
||||
// "--purchase",
|
||||
// "--keychain-passphrase",
|
||||
// "iDescriptor",
|
||||
// "--format",
|
||||
// "json"};
|
||||
m_actionButton->setEnabled(false);
|
||||
m_statusLabel->setText("Downloading app...");
|
||||
|
||||
QString selectedDevice = m_deviceCombo->currentData().toString();
|
||||
|
||||
int buttonIndex = m_layout->indexOf(m_actionButton);
|
||||
layout()->removeWidget(m_actionButton);
|
||||
m_actionButton->deleteLater();
|
||||
m_actionButton = nullptr; // Reset to avoid double deletion
|
||||
// TODO: need to implement the actual installation logic
|
||||
// startDownloadProcess(args, QDir::currentPath(), buttonIndex);
|
||||
m_actionButton = nullptr;
|
||||
|
||||
m_tempDir = QDir::tempPath();
|
||||
startDownloadProcess(m_bundleId, m_tempDir, buttonIndex, false);
|
||||
connect(this, &AppDownloadBaseDialog::downloadFinished, this,
|
||||
[this, selectedDevice](bool success) {
|
||||
if (success) {
|
||||
qDebug() << "Download finished, starting installation...";
|
||||
/*
|
||||
FIXME: libipatool generates random id and appends that
|
||||
to the downloaded IPA filename, so we need to search for
|
||||
it.
|
||||
*/
|
||||
// Find the actual downloaded IPA file
|
||||
QDir outDir = m_tempDir;
|
||||
QStringList filters;
|
||||
filters << m_bundleId + "*.ipa";
|
||||
QStringList matches =
|
||||
outDir.entryList(filters, QDir::Files, QDir::Time);
|
||||
if (matches.isEmpty()) {
|
||||
m_statusLabel->setText(
|
||||
"Download failed - IPA not found");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #FF3B30; padding: 5px;");
|
||||
QMessageBox::critical(
|
||||
this, "Error",
|
||||
QString("Downloaded IPA not found in %1")
|
||||
.arg(outDir.absolutePath()));
|
||||
return;
|
||||
}
|
||||
|
||||
QString ipaFile = outDir.filePath(matches.first());
|
||||
performInstallation(ipaFile, selectedDevice);
|
||||
|
||||
} else {
|
||||
m_statusLabel->setText("Download failed");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #FF3B30; padding: 5px;");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AppInstallDialog::reject()
|
||||
{
|
||||
// Cancel installation if it's running
|
||||
if (m_installWatcher && !m_installWatcher->isFinished()) {
|
||||
m_installWatcher->cancel();
|
||||
m_installWatcher->deleteLater();
|
||||
m_installWatcher = nullptr;
|
||||
if (m_statusLabel) {
|
||||
m_statusLabel->setText("Installation cancelled");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #FF3B30; padding: 5px;");
|
||||
}
|
||||
}
|
||||
|
||||
AppDownloadBaseDialog::reject();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "appdownloadbasedialog.h"
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
|
||||
class AppInstallDialog : public AppDownloadBaseDialog
|
||||
{
|
||||
@@ -11,15 +13,23 @@ class AppInstallDialog : public AppDownloadBaseDialog
|
||||
public:
|
||||
explicit AppInstallDialog(const QString &appName,
|
||||
const QString &description,
|
||||
const QString &bundleId,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void reject() override;
|
||||
|
||||
private slots:
|
||||
void onInstallClicked();
|
||||
|
||||
private:
|
||||
QComboBox *m_deviceCombo;
|
||||
QString m_bundleId;
|
||||
QLabel *m_statusLabel;
|
||||
QFutureWatcher<int> *m_installWatcher;
|
||||
QString m_tempDir;
|
||||
void updateDeviceList();
|
||||
void performInstallation(const QString &ipaPath, const QString &deviceUdid);
|
||||
};
|
||||
|
||||
#endif // APPINSTALLDIALOG_H
|
||||
|
||||
+9
-14
@@ -279,19 +279,12 @@ void AppsWidget::clearAppGrid()
|
||||
}
|
||||
}
|
||||
|
||||
// void AppsWidget::showStatusMessage(const QString &message)
|
||||
// {
|
||||
// showError(message);
|
||||
// }
|
||||
|
||||
void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
|
||||
const QString &description,
|
||||
const QString &iconPath, QGridLayout *gridLayout,
|
||||
int row, int col)
|
||||
{
|
||||
QWidget *cardWidget = new QWidget();
|
||||
// cardWidget->setFixedSize(200, 250);
|
||||
cardWidget->setCursor(Qt::PointingHandCursor);
|
||||
|
||||
QHBoxLayout *cardLayout = new QHBoxLayout(cardWidget);
|
||||
cardLayout->setContentsMargins(15, 15, 15, 15);
|
||||
@@ -355,14 +348,14 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
|
||||
QPushButton *installLabel = new QPushButton("Install");
|
||||
QPushButton *downloadIpaLabel = new QPushButton("Download IPA");
|
||||
|
||||
installLabel->setStyleSheet(
|
||||
"font-size: 12px; color: #007AFF; font-weight: bold;");
|
||||
installLabel->setStyleSheet("font-size: 12px; color: #007AFF; font-weight: "
|
||||
"bold; background-color: transparent;");
|
||||
installLabel->setCursor(Qt::PointingHandCursor);
|
||||
installLabel->setFixedHeight(30);
|
||||
// installLabel->setAlignment(Qt::AlignCenter);
|
||||
connect(
|
||||
installLabel, &QPushButton::clicked, this,
|
||||
[this, name, description]() { onAppCardClicked(name, description); });
|
||||
connect(installLabel, &QPushButton::clicked, this,
|
||||
[this, name, bundleId, description]() {
|
||||
onAppCardClicked(name, bundleId, description);
|
||||
});
|
||||
|
||||
connect(downloadIpaLabel, &QPushButton::clicked, this,
|
||||
[this, name, bundleId]() { onDownloadIpaClicked(name, bundleId); });
|
||||
@@ -416,6 +409,7 @@ void AppsWidget::onLoginClicked()
|
||||
}
|
||||
|
||||
void AppsWidget::onAppCardClicked(const QString &appName,
|
||||
const QString &bundleId,
|
||||
const QString &description)
|
||||
{
|
||||
if (!m_isLoggedIn) {
|
||||
@@ -424,7 +418,7 @@ void AppsWidget::onAppCardClicked(const QString &appName,
|
||||
return;
|
||||
}
|
||||
|
||||
AppInstallDialog dialog(appName, description, this);
|
||||
AppInstallDialog dialog(appName, description, bundleId, this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
@@ -477,6 +471,7 @@ void AppsWidget::onSearchFinished(bool success, const QString &results)
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Search results:" << doc;
|
||||
QJsonObject rootObj = doc.object();
|
||||
if (!rootObj.value("success").toBool()) {
|
||||
QString errorMessage =
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ public:
|
||||
|
||||
private slots:
|
||||
void onLoginClicked();
|
||||
void onAppCardClicked(const QString &appName, const QString &description);
|
||||
void onAppCardClicked(const QString &appName, const QString &bundleId,
|
||||
const QString &description);
|
||||
void onDownloadIpaClicked(const QString &name, const QString &bundleId);
|
||||
void onSearchTextChanged();
|
||||
void performSearch();
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#include <QDebug>
|
||||
#include <libimobiledevice/afc.h>
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
|
||||
afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device,
|
||||
const char *path, char ***dirs)
|
||||
{
|
||||
try {
|
||||
if (!afcClient || !device) {
|
||||
qDebug() << "AFC client is null in safe_afc_read_directory";
|
||||
return AFC_E_INVALID_ARG;
|
||||
}
|
||||
|
||||
afc_error_t result = afc_read_directory(afcClient, path, dirs);
|
||||
|
||||
return result;
|
||||
} catch (const std::exception &e) {
|
||||
qDebug() << "Exception in safe_afc_read_directory:" << e.what();
|
||||
return AFC_E_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,8 @@ static const char *domains[] = {
|
||||
"com.apple.mobile.iTunes", "com.apple.fmip", "com.apple.Accessibility",
|
||||
NULL};
|
||||
|
||||
plist_t get_device_info(const char *udid, int use_network, int simple,
|
||||
lockdownd_client_t client, idevice_t device)
|
||||
plist_t get_device_info(const char *udid, lockdownd_client_t client,
|
||||
idevice_t device)
|
||||
{
|
||||
lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
|
||||
idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
|
||||
@@ -78,21 +78,6 @@ plist_t get_device_info(const char *udid, int use_network, int simple,
|
||||
plist_t disk_info = nullptr;
|
||||
uint64_t total_space = 0;
|
||||
uint64_t free_space = 0;
|
||||
/* {
|
||||
"AmountDataAvailable": 6663077888,
|
||||
"AmountDataReserved": 209715200,
|
||||
"AmountRestoreAvailable": 11524079616,
|
||||
"CalculateDiskUsage": "OkilyDokily",
|
||||
"NANDInfo": <01000000 01000000 01000000 00000080 ... 00 00000000 000000>,
|
||||
"TotalDataAvailable": 6872793088,
|
||||
"TotalDataCapacity": 11306721280,
|
||||
"TotalDiskCapacity": 16000000000,
|
||||
"TotalSystemAvailable": 0,
|
||||
"TotalSystemCapacity": 4693204992
|
||||
}*/
|
||||
/* trying to set DiskInfo as key results in
|
||||
xplist.c:365: node_to_xml: Assertion `(node->children->count % 2) == 0'
|
||||
failed. so lets do merge it*/
|
||||
if (lockdownd_get_value(client, "com.apple.disk_usage", nullptr,
|
||||
&disk_info) == LOCKDOWN_E_SUCCESS) {
|
||||
// merge dict
|
||||
@@ -103,14 +88,12 @@ plist_t get_device_info(const char *udid, int use_network, int simple,
|
||||
return node;
|
||||
}
|
||||
|
||||
void get_device_info_xml(const char *udid, int use_network, int simple,
|
||||
pugi::xml_document &infoXml, lockdownd_client_t client,
|
||||
idevice_t device)
|
||||
void get_device_info_xml(const char *udid, lockdownd_client_t client,
|
||||
idevice_t device, pugi::xml_document &infoXml)
|
||||
{
|
||||
plist_t node = get_device_info(udid, use_network, simple, client, device);
|
||||
plist_t node = get_device_info(udid, client, device);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
char *xml_string = nullptr;
|
||||
uint32_t xml_length = 0;
|
||||
plist_to_xml(node, &xml_string, &xml_length);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <string.h>
|
||||
|
||||
AFCFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
|
||||
AFCFileTree get_file_tree(afc_client_t afcClient,
|
||||
const std::string &path)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ AFCFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
|
||||
}
|
||||
|
||||
char **dirs = NULL;
|
||||
if (safe_afc_read_directory(afcClient, device, path.c_str(), &dirs) !=
|
||||
if (afc_read_directory(afcClient, path.c_str(), &dirs) !=
|
||||
AFC_E_SUCCESS) {
|
||||
result.success = false;
|
||||
return result;
|
||||
+120
-133
@@ -1,5 +1,6 @@
|
||||
#include "../../devicedatabase.h"
|
||||
#include "../../iDescriptor.h"
|
||||
#include "../../servicemanager.h"
|
||||
#include "libirecovery.h"
|
||||
#include <QDebug>
|
||||
#include <libimobiledevice/diagnostics_relay.h>
|
||||
@@ -120,7 +121,7 @@ void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d)
|
||||
// TODO: return tyype
|
||||
DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
|
||||
afc_client_t &afcClient,
|
||||
IDescriptorInitDeviceResult &result)
|
||||
iDescriptorInitDeviceResult &result)
|
||||
{
|
||||
pugi::xml_node dict = doc.child("plist").child("dict");
|
||||
auto safeGet = [&](const char *key) -> std::string {
|
||||
@@ -206,7 +207,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
|
||||
const DeviceDatabaseInfo *info =
|
||||
DeviceDatabase::findByIdentifier(rawProductType);
|
||||
d.productType =
|
||||
info ? info->displayName ? info->marketingName : "Unknown Device"
|
||||
info ? info->displayName ? info->displayName : info->marketingName
|
||||
: "Unknown Device";
|
||||
d.rawProductType = rawProductType;
|
||||
d.jailbroken = detect_jailbroken(afcClient);
|
||||
@@ -271,152 +272,134 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: IDescriptorInitDeviceResult
|
||||
IDescriptorInitDeviceResult init_idescriptor_device(const char *udid)
|
||||
iDescriptorInitDeviceResult init_idescriptor_device(const char *udid)
|
||||
{
|
||||
// TODO:on a broken usb cable this can hang for a long time
|
||||
// causing the UI to freeze
|
||||
qDebug() << "Initializing iDescriptor device with UDID: "
|
||||
<< QString::fromUtf8(udid);
|
||||
IDescriptorInitDeviceResult result = {};
|
||||
iDescriptorInitDeviceResult result = {};
|
||||
|
||||
lockdownd_client_t client;
|
||||
// TODO: LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING
|
||||
// LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING = -19,
|
||||
lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
|
||||
// 1. Initialize all resource handles to nullptr
|
||||
idevice_t device = nullptr;
|
||||
lockdownd_client_t client = nullptr;
|
||||
lockdownd_service_descriptor_t lockdownService = nullptr;
|
||||
afc_client_t afcClient = nullptr;
|
||||
afc_client_t afc2Client = nullptr;
|
||||
try {
|
||||
idevice_error_t ret = idevice_new_with_options(&result.device, udid,
|
||||
IDEVICE_LOOKUP_USBMUX);
|
||||
pugi::xml_document infoXml;
|
||||
|
||||
if (ret != IDEVICE_E_SUCCESS) {
|
||||
qDebug() << "Failed to connect to device: " << ret;
|
||||
return result;
|
||||
}
|
||||
if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(
|
||||
result.device, &client, APP_LABEL))) {
|
||||
result.error = ldret;
|
||||
qDebug() << "Failed to create lockdown client: " << ldret;
|
||||
idevice_free(result.device);
|
||||
return result;
|
||||
}
|
||||
idevice_error_t ret =
|
||||
idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX);
|
||||
|
||||
if (LOCKDOWN_E_SUCCESS !=
|
||||
(ldret = lockdownd_start_service(client, "com.apple.afc",
|
||||
&lockdownService))) {
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(result.device);
|
||||
qDebug() << "Failed to start AFC service: " << ldret;
|
||||
return result;
|
||||
}
|
||||
if (lockdownService) {
|
||||
qDebug() << "AFC service started successfully.";
|
||||
} else {
|
||||
qDebug() << "AFC service descriptor is null.";
|
||||
// lockdownd_client_free(result.client);
|
||||
// idevice_free(result.device);
|
||||
// return result;
|
||||
}
|
||||
|
||||
if (afc_client_new(result.device, lockdownService, &afcClient) !=
|
||||
AFC_E_SUCCESS) {
|
||||
lockdownd_service_descriptor_free(lockdownService);
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(result.device);
|
||||
qDebug() << "Failed to create AFC client: " << ldret;
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
afc_error_t err = AFC_E_UNKNOWN_ERROR;
|
||||
if ((err = afc2_client_new(result.device, &afc2Client)) !=
|
||||
AFC_E_SUCCESS) {
|
||||
qDebug() << "AFC2 client not available." << "Error:" << err;
|
||||
} else {
|
||||
result.afc2Client = afc2Client;
|
||||
qDebug() << "AFC2 client created successfully.";
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
/* Fine! This only works on Jailbroken and AFC2 tweak installed
|
||||
* devices */
|
||||
}
|
||||
|
||||
pugi::xml_document infoXml;
|
||||
get_device_info_xml(udid, 0, 0, infoXml, client, result.device);
|
||||
|
||||
if (infoXml.empty()) {
|
||||
qDebug() << "Failed to retrieve device info XML for UDID: "
|
||||
<< QString::fromUtf8(udid);
|
||||
// Clean up resources before returning
|
||||
// afc_client_free(result.afcClient);
|
||||
// lockdownd_service_descriptor_free(result.lockdownService);
|
||||
// lockdownd_client_free(result.client);
|
||||
idevice_free(result.device);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string productType =
|
||||
safeGetXML("ProductType", infoXml.child("plist").child("dict"));
|
||||
|
||||
// if (result.device) idevice_free(result.device);
|
||||
|
||||
fullDeviceInfo(infoXml, afcClient, result);
|
||||
result.afcClient = afcClient;
|
||||
result.success = true;
|
||||
|
||||
if (lockdownService)
|
||||
lockdownd_service_descriptor_free(lockdownService);
|
||||
if (client)
|
||||
lockdownd_client_free(client);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
qDebug() << "Exception in init_idescriptor_device: " << e.what();
|
||||
// Clean up any allocated resources
|
||||
// if (result.afcClient) afc_client_free(result.afcClient);
|
||||
// if (result.lockdownService)
|
||||
// lockdownd_service_descriptor_free(result.lockdownService);
|
||||
if (client)
|
||||
lockdownd_client_free(client);
|
||||
if (result.device)
|
||||
idevice_free(result.device);
|
||||
return result;
|
||||
if (ret != IDEVICE_E_SUCCESS) {
|
||||
qDebug() << "Failed to connect to device: " << ret;
|
||||
// result.error is not set here as idevice_error_t is different
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
lockdownd_error_t ldret;
|
||||
if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(
|
||||
device, &client, APP_LABEL))) {
|
||||
result.error = ldret;
|
||||
qDebug() << "Failed to create lockdown client: " << ldret;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (LOCKDOWN_E_SUCCESS !=
|
||||
(ldret = lockdownd_start_service(client, "com.apple.afc",
|
||||
&lockdownService))) {
|
||||
result.error = ldret;
|
||||
qDebug() << "Failed to start AFC service: " << ldret;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (afc_client_new(device, lockdownService, &afcClient) != AFC_E_SUCCESS) {
|
||||
qDebug() << "Failed to create AFC client.";
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// AFC2 is optional, so we don't goto cleanup on failure
|
||||
afc_error_t afc2_err;
|
||||
if ((afc2_err = afc2_client_new(device, &afc2Client)) != AFC_E_SUCCESS) {
|
||||
qDebug() << "AFC2 client not available. Error:" << afc2_err;
|
||||
afc2Client = nullptr;
|
||||
} else {
|
||||
qDebug() << "AFC2 client created successfully.";
|
||||
}
|
||||
|
||||
get_device_info_xml(udid, client, device, infoXml);
|
||||
|
||||
if (infoXml.empty()) {
|
||||
qDebug() << "Failed to retrieve device info XML for UDID: "
|
||||
<< QString::fromUtf8(udid);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// If we got this far, the core initialization is successful
|
||||
result.success = true;
|
||||
result.device = device;
|
||||
result.afcClient = afcClient;
|
||||
result.afc2Client = afc2Client;
|
||||
fullDeviceInfo(infoXml, afcClient, result);
|
||||
|
||||
cleanup:
|
||||
if (lockdownService) {
|
||||
lockdownd_service_descriptor_free(lockdownService);
|
||||
}
|
||||
if (client) {
|
||||
lockdownd_client_free(client);
|
||||
}
|
||||
|
||||
// free on error
|
||||
if (!result.success) {
|
||||
if (afc2Client) {
|
||||
afc_client_free(afc2Client);
|
||||
}
|
||||
if (afcClient) {
|
||||
afc_client_free(afcClient);
|
||||
}
|
||||
if (device) {
|
||||
idevice_free(device);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IDescriptorInitDeviceResultRecovery
|
||||
iDescriptorInitDeviceResultRecovery
|
||||
init_idescriptor_recovery_device(uint64_t ecid)
|
||||
{
|
||||
IDescriptorInitDeviceResultRecovery result;
|
||||
qDebug() << "Initializing iDescriptor recovery device with ECID: " << ecid;
|
||||
iDescriptorInitDeviceResultRecovery result = {};
|
||||
|
||||
irecv_client_t client = nullptr;
|
||||
irecv_error_t ret = IRECV_E_UNKNOWN_ERROR;
|
||||
ret = irecv_open_with_ecid_and_attempts(&client, ecid,
|
||||
RECOVERY_CLIENT_CONNECTION_TRIES);
|
||||
|
||||
if (ret != IRECV_E_SUCCESS) {
|
||||
result.error = ret;
|
||||
return result;
|
||||
}
|
||||
ret = irecv_get_mode(client, (int *)&result.mode);
|
||||
|
||||
if (ret != IRECV_E_SUCCESS) {
|
||||
result.error = ret;
|
||||
irecv_close(client);
|
||||
return result;
|
||||
}
|
||||
|
||||
const irecv_device_info *deviceInfo = irecv_get_device_info(client);
|
||||
if (!deviceInfo) {
|
||||
result.error = IRECV_E_UNKNOWN_ERROR;
|
||||
irecv_close(client);
|
||||
return result;
|
||||
}
|
||||
|
||||
const irecv_device_info *deviceInfo = nullptr;
|
||||
irecv_device_t device = nullptr;
|
||||
const DeviceDatabaseInfo *info = nullptr;
|
||||
|
||||
irecv_error_t ret = irecv_open_with_ecid_and_attempts(
|
||||
&client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
|
||||
|
||||
if (ret != IRECV_E_SUCCESS) {
|
||||
qDebug() << "Failed to open recovery client with ECID:" << ecid
|
||||
<< "Error:" << ret;
|
||||
result.error = ret;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = irecv_get_mode(client, (int *)&result.mode);
|
||||
if (ret != IRECV_E_SUCCESS) {
|
||||
qDebug() << "Failed to get recovery mode. Error:" << ret;
|
||||
result.error = ret;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
deviceInfo = irecv_get_device_info(client);
|
||||
if (!deviceInfo) {
|
||||
qDebug() << "Failed to get device info from recovery client";
|
||||
result.error = IRECV_E_UNKNOWN_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (irecv_devices_get_device_by_client(client, &device) ==
|
||||
IRECV_E_SUCCESS &&
|
||||
device && device->hardware_model) {
|
||||
@@ -431,9 +414,13 @@ init_idescriptor_recovery_device(uint64_t ecid)
|
||||
result.displayName =
|
||||
info ? (info->displayName ? info->displayName : info->marketingName)
|
||||
: "Unknown Device";
|
||||
|
||||
result.deviceInfo = *deviceInfo;
|
||||
result.success = true;
|
||||
irecv_close(client);
|
||||
|
||||
cleanup:
|
||||
if (client) {
|
||||
irecv_close(client);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <libimobiledevice/afc.h>
|
||||
#include <libimobiledevice/installation_proxy.h>
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
|
||||
#include <plist/plist.h>
|
||||
|
||||
#include <zip.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#define wait_ms(x) Sleep(x)
|
||||
#else
|
||||
#define wait_ms(x) \
|
||||
{ \
|
||||
struct timespec ts; \
|
||||
ts.tv_sec = 0; \
|
||||
ts.tv_nsec = x * 1000000; \
|
||||
nanosleep(&ts, NULL); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist"
|
||||
|
||||
const char PKG_PATH[] = "PublicStaging";
|
||||
|
||||
struct install_status_data {
|
||||
int command_completed;
|
||||
int err_occurred;
|
||||
char *last_status;
|
||||
};
|
||||
|
||||
static void status_cb(plist_t command, plist_t status, void *user_data)
|
||||
{
|
||||
struct install_status_data *isd = (struct install_status_data *)user_data;
|
||||
if (command && status) {
|
||||
char *command_name = NULL;
|
||||
instproxy_command_get_name(command, &command_name);
|
||||
|
||||
/* get status */
|
||||
char *status_name = NULL;
|
||||
instproxy_status_get_name(status, &status_name);
|
||||
|
||||
if (status_name) {
|
||||
if (!strcmp(status_name, "Complete")) {
|
||||
isd->command_completed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* get error if any */
|
||||
char *error_name = NULL;
|
||||
char *error_description = NULL;
|
||||
uint64_t error_code = 0;
|
||||
instproxy_status_get_error(status, &error_name, &error_description,
|
||||
&error_code);
|
||||
|
||||
/* output/handling */
|
||||
if (!error_name) {
|
||||
if (status_name) {
|
||||
/* get progress if any */
|
||||
int percent = -1;
|
||||
instproxy_status_get_percent_complete(status, &percent);
|
||||
|
||||
if (isd->last_status &&
|
||||
(strcmp(isd->last_status, status_name))) {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (percent >= 0) {
|
||||
printf("\r%s: %s (%d%%)", command_name, status_name,
|
||||
percent);
|
||||
} else {
|
||||
printf("\r%s: %s", command_name, status_name);
|
||||
}
|
||||
if (isd->command_completed) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* report error to the user */
|
||||
if (error_description)
|
||||
fprintf(stderr,
|
||||
"ERROR: %s failed. Got error \"%s\" with code "
|
||||
"0x%08" PRIx64 ": %s\n",
|
||||
command_name, error_name, error_code,
|
||||
error_description ? error_description : "N/A");
|
||||
else
|
||||
fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n",
|
||||
command_name, error_name);
|
||||
isd->err_occurred = 1;
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
free(error_name);
|
||||
free(error_description);
|
||||
|
||||
free(isd->last_status);
|
||||
isd->last_status = status_name;
|
||||
|
||||
free(command_name);
|
||||
command_name = NULL;
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: %s was called with invalid arguments!\n",
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
|
||||
static int zip_get_contents(struct zip *zf, const char *filename,
|
||||
int locate_flags, char **buffer, uint32_t *len)
|
||||
{
|
||||
struct zip_stat zs;
|
||||
struct zip_file *zfile;
|
||||
int zindex = zip_name_locate(zf, filename, locate_flags);
|
||||
|
||||
*buffer = NULL;
|
||||
*len = 0;
|
||||
|
||||
if (zindex < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
zip_stat_init(&zs);
|
||||
|
||||
if (zip_stat_index(zf, zindex, 0, &zs) != 0) {
|
||||
fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (zs.size > 10485760) {
|
||||
fprintf(stderr, "ERROR: file '%s' is too large!\n", filename);
|
||||
return -3;
|
||||
}
|
||||
|
||||
zfile = zip_fopen_index(zf, zindex, 0);
|
||||
if (!zfile) {
|
||||
fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename);
|
||||
return -4;
|
||||
}
|
||||
|
||||
*buffer = (char *)malloc(zs.size);
|
||||
if (zs.size > LLONG_MAX ||
|
||||
zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) {
|
||||
fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n",
|
||||
(uint64_t)zs.size, filename);
|
||||
free(*buffer);
|
||||
*buffer = NULL;
|
||||
zip_fclose(zfile);
|
||||
return -5;
|
||||
}
|
||||
*len = zs.size;
|
||||
zip_fclose(zfile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zip_get_app_directory(struct zip *zf, char **path)
|
||||
{
|
||||
zip_int64_t i = 0;
|
||||
zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0);
|
||||
int len = 0;
|
||||
const char *name = NULL;
|
||||
|
||||
/* look through all filenames in the archive */
|
||||
do {
|
||||
/* get filename at current index */
|
||||
name = zip_get_name(zf, i++, 0);
|
||||
if (name != NULL) {
|
||||
/* check if we have a "Payload/.../" name */
|
||||
len = strlen(name);
|
||||
if (!strncmp(name, "Payload/", 8) && (len > 8)) {
|
||||
/* skip hidden files */
|
||||
if (name[8] == '.')
|
||||
continue;
|
||||
|
||||
/* locate the second directory delimiter */
|
||||
const char *p = name + 8;
|
||||
do {
|
||||
if (*p == '/') {
|
||||
break;
|
||||
}
|
||||
} while (p++ != NULL);
|
||||
|
||||
/* try next entry if not found */
|
||||
if (p == NULL)
|
||||
continue;
|
||||
|
||||
len = p - name + 1;
|
||||
|
||||
/* make sure app directory endwith .app */
|
||||
if (len < 12 || strncmp(p - 4, ".app", 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path != NULL) {
|
||||
free(*path);
|
||||
*path = NULL;
|
||||
}
|
||||
|
||||
/* allocate and copy filename */
|
||||
*path = (char *)malloc(len + 1);
|
||||
strncpy(*path, name, len);
|
||||
|
||||
/* add terminating null character */
|
||||
char *t = *path + len;
|
||||
*t = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (i < c);
|
||||
|
||||
if (*path == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int afc_upload_file(afc_client_t afc, const char *filename,
|
||||
const char *dstfn)
|
||||
{
|
||||
FILE *f = NULL;
|
||||
uint64_t af = 0;
|
||||
char buf[1048576];
|
||||
|
||||
f = fopen(filename, "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) ||
|
||||
!af) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t amount = 0;
|
||||
do {
|
||||
amount = fread(buf, 1, sizeof(buf), f);
|
||||
if (amount > 0) {
|
||||
uint32_t written, total = 0;
|
||||
while (total < amount) {
|
||||
written = 0;
|
||||
afc_error_t aerr =
|
||||
afc_file_write(afc, af, buf, amount, &written);
|
||||
if (aerr != AFC_E_SUCCESS) {
|
||||
fprintf(stderr, "AFC Write error: %d\n", aerr);
|
||||
break;
|
||||
}
|
||||
total += written;
|
||||
}
|
||||
if (total != amount) {
|
||||
fprintf(stderr, "Error: wrote only %u of %u\n", total,
|
||||
(uint32_t)amount);
|
||||
afc_file_close(afc, af);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} while (amount > 0);
|
||||
|
||||
afc_file_close(afc, af);
|
||||
fclose(f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
instproxy_error_t install_IPA(idevice_t device, afc_client_t afc,
|
||||
const char *filePath)
|
||||
{
|
||||
lockdownd_client_t client = NULL;
|
||||
instproxy_client_t ipc = NULL;
|
||||
lockdownd_service_descriptor_t service = NULL;
|
||||
instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR;
|
||||
char *bundleidentifier = NULL;
|
||||
struct install_status_data status_data = {0, 0, NULL};
|
||||
plist_t sinf = NULL;
|
||||
plist_t meta = NULL;
|
||||
char *pkgname = NULL;
|
||||
struct stat fst;
|
||||
char **strs = NULL;
|
||||
plist_t client_opts = instproxy_client_options_new();
|
||||
char *zbuf = NULL;
|
||||
uint32_t len = 0;
|
||||
plist_t meta_dict = NULL;
|
||||
int errp = 0;
|
||||
struct zip *zf = zip_open(filePath, 0, &errp);
|
||||
plist_t info = NULL;
|
||||
char *filename = NULL;
|
||||
char *app_directory_name = NULL;
|
||||
char *bundleexecutable = NULL;
|
||||
plist_t bname = NULL;
|
||||
char *sinfname = NULL;
|
||||
|
||||
if (!device || !filePath || !afc) {
|
||||
fprintf(stderr, "ERROR: Invalid arguments passed to install_IPA.\n");
|
||||
return INSTPROXY_E_INVALID_ARG;
|
||||
}
|
||||
|
||||
lockdownd_error_t lerr = lockdownd_client_new_with_handshake(
|
||||
device, &client, "ideviceinstaller");
|
||||
if (lerr != LOCKDOWN_E_SUCCESS) {
|
||||
fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n",
|
||||
lockdownd_strerror(lerr));
|
||||
return INSTPROXY_E_OP_FAILED;
|
||||
}
|
||||
|
||||
lerr = lockdownd_start_service(
|
||||
client, "com.apple.mobile.installation_proxy", &service);
|
||||
if (lerr != LOCKDOWN_E_SUCCESS) {
|
||||
fprintf(stderr,
|
||||
"Could not start com.apple.mobile.installation_proxy: %s\n",
|
||||
lockdownd_strerror(lerr));
|
||||
lockdownd_client_free(client);
|
||||
return INSTPROXY_E_OP_FAILED;
|
||||
}
|
||||
|
||||
err = instproxy_client_new(device, service, &ipc);
|
||||
if (service) {
|
||||
lockdownd_service_descriptor_free(service);
|
||||
service = NULL;
|
||||
}
|
||||
|
||||
if (err != INSTPROXY_E_SUCCESS) {
|
||||
fprintf(stderr, "Could not connect to installation_proxy!\n");
|
||||
lockdownd_client_free(client);
|
||||
return err;
|
||||
}
|
||||
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
if (stat(filePath, &fst) != 0) {
|
||||
fprintf(stderr, "ERROR: stat: %s: %s\n", filePath, strerror(errno));
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto leave_cleanup;
|
||||
}
|
||||
|
||||
if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
|
||||
if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
|
||||
fprintf(stderr,
|
||||
"WARNING: Could not create directory '%s' on device!\n",
|
||||
PKG_PATH);
|
||||
}
|
||||
}
|
||||
if (strs) {
|
||||
int i = 0;
|
||||
while (strs[i]) {
|
||||
free(strs[i]);
|
||||
i++;
|
||||
}
|
||||
free(strs);
|
||||
}
|
||||
|
||||
if (!zf) {
|
||||
fprintf(stderr, "ERROR: zip_open: %s: %d\n", filePath, errp);
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto leave_cleanup;
|
||||
}
|
||||
|
||||
/* extract iTunesMetadata.plist from package */
|
||||
if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) ==
|
||||
0) {
|
||||
meta = plist_new_data(zbuf, len);
|
||||
plist_from_memory(zbuf, len, &meta_dict, NULL);
|
||||
}
|
||||
if (!meta_dict) {
|
||||
plist_free(meta);
|
||||
meta = NULL;
|
||||
fprintf(stderr, "WARNING: could not locate %s in archive!\n",
|
||||
ITUNES_METADATA_PLIST_FILENAME);
|
||||
}
|
||||
free(zbuf);
|
||||
|
||||
/* determine .app directory in archive */
|
||||
zbuf = NULL;
|
||||
len = 0;
|
||||
|
||||
if (zip_get_app_directory(zf, &app_directory_name)) {
|
||||
fprintf(stderr, "ERROR: Unable to locate .app directory in archive. "
|
||||
"Make sure it is inside a 'Payload' directory.\n");
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
|
||||
/* construct full filename to Info.plist */
|
||||
filename = (char *)malloc(strlen(app_directory_name) + 10 + 1);
|
||||
strcpy(filename, app_directory_name);
|
||||
free(app_directory_name);
|
||||
app_directory_name = NULL;
|
||||
strcat(filename, "Info.plist");
|
||||
|
||||
if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) {
|
||||
fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename);
|
||||
free(filename);
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
free(filename);
|
||||
plist_from_memory(zbuf, len, &info, NULL);
|
||||
free(zbuf);
|
||||
|
||||
if (!info) {
|
||||
fprintf(stderr, "Could not parse Info.plist!\n");
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
|
||||
bname = plist_dict_get_item(info, "CFBundleExecutable");
|
||||
if (bname) {
|
||||
plist_get_string_val(bname, &bundleexecutable);
|
||||
}
|
||||
|
||||
bname = plist_dict_get_item(info, "CFBundleIdentifier");
|
||||
if (bname) {
|
||||
plist_get_string_val(bname, &bundleidentifier);
|
||||
}
|
||||
plist_free(info);
|
||||
info = NULL;
|
||||
|
||||
if (!bundleexecutable) {
|
||||
fprintf(stderr, "Could not determine value for CFBundleExecutable!\n");
|
||||
err = INSTPROXY_E_INVALID_ARG;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
|
||||
if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable,
|
||||
bundleexecutable) < 0) {
|
||||
fprintf(stderr, "Out of memory!?\n");
|
||||
err = INSTPROXY_E_UNKNOWN_ERROR;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
free(bundleexecutable);
|
||||
|
||||
/* extract .sinf from package */
|
||||
zbuf = NULL;
|
||||
len = 0;
|
||||
if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
|
||||
sinf = plist_new_data(zbuf, len);
|
||||
} else {
|
||||
fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname);
|
||||
}
|
||||
free(sinfname);
|
||||
free(zbuf);
|
||||
|
||||
/* copy archive to device */
|
||||
pkgname = NULL;
|
||||
if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) {
|
||||
fprintf(stderr, "Out of memory!?\n");
|
||||
err = INSTPROXY_E_UNKNOWN_ERROR;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
|
||||
printf("Copying '%s' to device... ", filePath);
|
||||
|
||||
if (afc_upload_file(afc, filePath, pkgname) < 0) {
|
||||
printf("FAILED\n");
|
||||
free(pkgname);
|
||||
err = INSTPROXY_E_OP_FAILED;
|
||||
goto zip_cleanup;
|
||||
}
|
||||
|
||||
printf("DONE.\n");
|
||||
|
||||
if (bundleidentifier) {
|
||||
instproxy_client_options_add(client_opts, "CFBundleIdentifier",
|
||||
bundleidentifier, NULL);
|
||||
}
|
||||
if (sinf) {
|
||||
instproxy_client_options_add(client_opts, "ApplicationSINF", sinf,
|
||||
NULL);
|
||||
}
|
||||
if (meta) {
|
||||
instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
|
||||
}
|
||||
|
||||
zip_cleanup:
|
||||
if (zf) {
|
||||
zip_unchange_all(zf);
|
||||
zip_close(zf);
|
||||
}
|
||||
if (err != INSTPROXY_E_SUCCESS) {
|
||||
goto leave_cleanup;
|
||||
}
|
||||
|
||||
/* perform installation */
|
||||
printf("Installing '%s'\n", bundleidentifier);
|
||||
instproxy_install(ipc, pkgname, client_opts, status_cb, &status_data);
|
||||
|
||||
instproxy_client_options_free(client_opts);
|
||||
free(pkgname);
|
||||
|
||||
while (!status_data.command_completed && !status_data.err_occurred) {
|
||||
wait_ms(50);
|
||||
}
|
||||
|
||||
if (status_data.err_occurred) {
|
||||
err = INSTPROXY_E_OP_FAILED;
|
||||
} else {
|
||||
err = INSTPROXY_E_SUCCESS;
|
||||
}
|
||||
|
||||
leave_cleanup:
|
||||
instproxy_client_free(ipc);
|
||||
lockdownd_client_free(client);
|
||||
free(bundleidentifier);
|
||||
free(status_data.last_status);
|
||||
|
||||
return err;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 10, 0);
|
||||
mainLayout->setSpacing(1);
|
||||
mainLayout->addStretch();
|
||||
|
||||
// Left side container for image and actions
|
||||
QWidget *leftContainer = new QWidget();
|
||||
@@ -78,15 +79,17 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
actionsLayout->addWidget(restartBtn);
|
||||
actionsLayout->addWidget(recoveryBtn);
|
||||
|
||||
leftLayout->addStretch();
|
||||
leftLayout->addWidget(m_deviceImageLabel);
|
||||
leftLayout->addWidget(actionsWidget, 0, Qt::AlignCenter);
|
||||
leftLayout->addStretch(); // stretch to push everything to the top
|
||||
leftLayout->addStretch();
|
||||
|
||||
mainLayout->addWidget(leftContainer); // Stretch factor 1
|
||||
mainLayout->addWidget(leftContainer);
|
||||
|
||||
// Right side: Info Table
|
||||
QWidget *infoContainer = new QWidget();
|
||||
infoContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
|
||||
// 2. Change the horizontal size policy from Expanding to Preferred
|
||||
infoContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
|
||||
|
||||
QVBoxLayout *infoLayout = new QVBoxLayout(infoContainer);
|
||||
|
||||
@@ -111,6 +114,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
// background-color: rgba(0, 255, 30, 0.5);
|
||||
diskCapacityLabel->setStyleSheet(QString("background-color: %1;"
|
||||
"padding: 2px 4px;"
|
||||
"color : white;"
|
||||
"border-radius: 13px;")
|
||||
.arg(COLOR_ACCENT_BLUE.name()));
|
||||
|
||||
@@ -285,20 +289,25 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
// Footer
|
||||
QLabel *footerLabel =
|
||||
new QLabel("UDID: " + QString::fromStdString(device->udid));
|
||||
footerLabel->setToolTip("Unique Device Identifier");
|
||||
footerLabel->setStyleSheet(
|
||||
"font-size: 10px; color: #666; padding-top: 5px; "
|
||||
"border-top: 1px solid #eee;");
|
||||
"font-size: 10px; color: #666; margin-top: 5px; ");
|
||||
footerLabel->setWordWrap(true);
|
||||
infoLayout->addWidget(footerLabel);
|
||||
|
||||
// Create a vertical layout for the right side to stack info and disk usage
|
||||
QVBoxLayout *rightSideLayout = new QVBoxLayout();
|
||||
rightSideLayout->setSpacing(10);
|
||||
rightSideLayout->addStretch();
|
||||
|
||||
rightSideLayout->addWidget(infoContainer);
|
||||
rightSideLayout->addWidget(new DiskUsageWidget(device, this));
|
||||
|
||||
rightSideLayout->addStretch();
|
||||
// TODO: layout shift cause ?
|
||||
// rightSideLayout->setAlignment(Qt::AlignCenter);
|
||||
mainLayout->addLayout(rightSideLayout, 2); // Stretch factor 2
|
||||
|
||||
mainLayout->addLayout(rightSideLayout);
|
||||
|
||||
m_updateTimer = new QTimer(this);
|
||||
connect(m_updateTimer, &QTimer::timeout, this,
|
||||
&DeviceInfoWidget::updateBatteryInfo);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include "servicemanager.h"
|
||||
|
||||
void GalleryWidget::load()
|
||||
{
|
||||
@@ -399,7 +400,7 @@ void GalleryWidget::loadAlbumList()
|
||||
// Get DCIM directory contents
|
||||
qDebug() << "Loading album list from /DCIM";
|
||||
AFCFileTree dcimTree =
|
||||
get_file_tree(m_device->afcClient, m_device->device, "/DCIM");
|
||||
ServiceManager::safeGetFileTree(m_device, "/DCIM");
|
||||
|
||||
if (!dcimTree.success) {
|
||||
qDebug() << "Failed to read DCIM directory";
|
||||
|
||||
+16
-11
@@ -2,11 +2,13 @@
|
||||
#include <QImage>
|
||||
#include <QtCore/QObject>
|
||||
#include <libimobiledevice/afc.h>
|
||||
#include <libimobiledevice/installation_proxy.h>
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libimobiledevice/mobile_image_mounter.h>
|
||||
#include <libimobiledevice/screenshotr.h>
|
||||
#include <libirecovery.h>
|
||||
#include <mutex>
|
||||
#include <pugixml.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -143,10 +145,11 @@ struct iDescriptorDevice {
|
||||
afc_client_t afcClient;
|
||||
afc_client_t afc2Client;
|
||||
bool is_iPhone;
|
||||
std::recursive_mutex *mutex;
|
||||
};
|
||||
|
||||
struct IDescriptorInitDeviceResult {
|
||||
bool success;
|
||||
struct iDescriptorInitDeviceResult {
|
||||
bool success = false;
|
||||
lockdownd_error_t error;
|
||||
idevice_t device;
|
||||
DeviceInfo deviceInfo;
|
||||
@@ -163,11 +166,11 @@ struct iDescriptorRecoveryDevice {
|
||||
};
|
||||
|
||||
struct TakeScreenshotResult {
|
||||
bool success;
|
||||
bool success = false;
|
||||
QImage img;
|
||||
};
|
||||
|
||||
struct IDescriptorInitDeviceResultRecovery {
|
||||
struct iDescriptorInitDeviceResultRecovery {
|
||||
irecv_client_t client = nullptr;
|
||||
irecv_device_info deviceInfo;
|
||||
irecv_error_t error;
|
||||
@@ -266,18 +269,17 @@ struct AFCFileTree {
|
||||
std::string currentPath;
|
||||
};
|
||||
|
||||
AFCFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
|
||||
AFCFileTree get_file_tree(afc_client_t afcClient,
|
||||
const std::string &path = "/");
|
||||
|
||||
bool detect_jailbroken(afc_client_t afc);
|
||||
|
||||
void get_device_info_xml(const char *udid, int use_network, int simple,
|
||||
pugi::xml_document &infoXml, lockdownd_client_t client,
|
||||
idevice_t device);
|
||||
void get_device_info_xml(const char *udid, lockdownd_client_t client,
|
||||
idevice_t device, pugi::xml_document &infoXml);
|
||||
|
||||
IDescriptorInitDeviceResult init_idescriptor_device(const char *udid);
|
||||
iDescriptorInitDeviceResult init_idescriptor_device(const char *udid);
|
||||
|
||||
IDescriptorInitDeviceResultRecovery
|
||||
iDescriptorInitDeviceResultRecovery
|
||||
init_idescriptor_recovery_device(uint64_t ecid);
|
||||
|
||||
bool set_location(idevice_t device, char *lat, char *lon);
|
||||
@@ -398,4 +400,7 @@ QPixmap load_heic(const QByteArray &data);
|
||||
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient,
|
||||
const char *path);
|
||||
|
||||
bool isDarkMode();
|
||||
bool isDarkMode();
|
||||
|
||||
instproxy_error_t install_IPA(idevice_t device, afc_client_t afc,
|
||||
const char *filePath);
|
||||
@@ -605,6 +605,10 @@ void InstalledAppsWidget::filterApps(const QString &searchText)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME: maybe we better have this in servicemanager,
|
||||
for now it's ok as it's only used here
|
||||
*/
|
||||
void InstalledAppsWidget::loadAppContainer(const QString &bundleId)
|
||||
{
|
||||
if (!m_device || !m_device->device) {
|
||||
|
||||
+14
-13
@@ -2,6 +2,7 @@
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include "servicemanager.h"
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QHostAddress>
|
||||
@@ -44,6 +45,7 @@ QUrl MediaStreamer::getUrl() const
|
||||
if (!isListening()) {
|
||||
return QUrl();
|
||||
}
|
||||
// todo pass folder/filename
|
||||
return QUrl(QString("http://127.0.0.1:%1/%2")
|
||||
.arg(serverPort())
|
||||
.arg(QFileInfo(m_filePath).fileName()));
|
||||
@@ -257,11 +259,10 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
|
||||
context->afcHandle = 0;
|
||||
|
||||
qDebug() << "m_filepath" << m_filePath;
|
||||
// Open file on device
|
||||
// Open file on device using ServiceManager
|
||||
const QByteArray pathBytes = m_filePath.toUtf8();
|
||||
afc_error_t openResult =
|
||||
afc_file_open(m_afcClient, pathBytes.constData(), AFC_FOPEN_RDONLY,
|
||||
&context->afcHandle);
|
||||
afc_error_t openResult = ServiceManager::safeAfcFileOpen(
|
||||
m_device, pathBytes.constData(), AFC_FOPEN_RDONLY, &context->afcHandle);
|
||||
|
||||
if (openResult != AFC_E_SUCCESS || context->afcHandle == 0) {
|
||||
qWarning() << "Failed to open file on device:" << m_filePath;
|
||||
@@ -272,11 +273,11 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
|
||||
|
||||
// Seek to start position if needed
|
||||
if (startByte > 0) {
|
||||
afc_error_t seekResult =
|
||||
afc_file_seek(m_afcClient, context->afcHandle, startByte, SEEK_SET);
|
||||
afc_error_t seekResult = ServiceManager::safeAfcFileSeek(
|
||||
m_device, context->afcHandle, startByte, SEEK_SET);
|
||||
if (seekResult != AFC_E_SUCCESS) {
|
||||
qWarning() << "Failed to seek in file:" << m_filePath;
|
||||
afc_file_close(m_afcClient, context->afcHandle);
|
||||
ServiceManager::safeAfcFileClose(m_device, context->afcHandle);
|
||||
delete context;
|
||||
socket->disconnectFromHost();
|
||||
return;
|
||||
@@ -329,11 +330,11 @@ qint64 MediaStreamer::getFileSize()
|
||||
return m_cachedFileSize;
|
||||
}
|
||||
|
||||
// Get file info from device
|
||||
// Get file info from device using ServiceManager
|
||||
char **info = nullptr;
|
||||
const QByteArray pathBytes = m_filePath.toUtf8();
|
||||
afc_error_t result =
|
||||
afc_get_file_info(m_afcClient, pathBytes.constData(), &info);
|
||||
afc_error_t result = ServiceManager::safeAfcGetFileInfo(
|
||||
m_device, pathBytes.constData(), &info);
|
||||
|
||||
if (result != AFC_E_SUCCESS || !info) {
|
||||
qWarning() << "Failed to get file info for:" << m_filePath;
|
||||
@@ -409,8 +410,8 @@ void MediaStreamer::streamNextChunk(StreamingContext *context)
|
||||
auto buffer = std::make_unique<char[]>(bytesToRead);
|
||||
uint32_t bytesRead = 0;
|
||||
|
||||
afc_error_t readResult = afc_file_read(
|
||||
m_afcClient, context->afcHandle, buffer.get(), bytesToRead, &bytesRead);
|
||||
afc_error_t readResult = ServiceManager::safeAfcFileRead(
|
||||
m_device, context->afcHandle, buffer.get(), bytesToRead, &bytesRead);
|
||||
|
||||
if (readResult != AFC_E_SUCCESS || bytesRead == 0) {
|
||||
qWarning() << "AFC read error or EOF during streaming";
|
||||
@@ -467,7 +468,7 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context)
|
||||
}
|
||||
|
||||
if (context->afcHandle != 0) {
|
||||
afc_file_close(context->device->afcClient, context->afcHandle);
|
||||
ServiceManager::safeAfcFileClose(context->device, context->afcHandle);
|
||||
context->afcHandle = 0;
|
||||
}
|
||||
|
||||
|
||||
+25
-17
@@ -1,4 +1,5 @@
|
||||
#include "photoexportmanager.h"
|
||||
#include "servicemanager.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -104,7 +105,7 @@ void PhotoExportManager::performExport()
|
||||
outputPath = generateUniqueOutputPath(outputPath);
|
||||
|
||||
ExportResult result =
|
||||
exportSingleFile(m_device->afcClient, devicePath, outputPath);
|
||||
exportSingleFile(m_device, devicePath, outputPath);
|
||||
|
||||
if (result.success) {
|
||||
successful++;
|
||||
@@ -126,24 +127,26 @@ void PhotoExportManager::performExport()
|
||||
emit exportFinished(successful, failed);
|
||||
}
|
||||
|
||||
PhotoExportManager::ExportResult PhotoExportManager::exportSingleFile(
|
||||
afc_client_t afc, const QString &devicePath, const QString &outputPath)
|
||||
PhotoExportManager::ExportResult
|
||||
PhotoExportManager::exportSingleFile(iDescriptorDevice *device,
|
||||
const QString &devicePath,
|
||||
const QString &outputPath)
|
||||
{
|
||||
ExportResult result;
|
||||
result.filePath = devicePath;
|
||||
result.outputPath = outputPath;
|
||||
result.success = false;
|
||||
|
||||
// Open file on device
|
||||
// Use ServiceManager for thread-safe AFC operations
|
||||
uint64_t handle = 0;
|
||||
afc_error_t afc_err = afc_file_open(afc, devicePath.toUtf8().constData(),
|
||||
AFC_FOPEN_RDONLY, &handle);
|
||||
afc_error_t openResult = ServiceManager::safeAfcFileOpen(
|
||||
device, devicePath.toUtf8().constData(), AFC_FOPEN_RDONLY, &handle);
|
||||
|
||||
if (afc_err != AFC_E_SUCCESS) {
|
||||
if (openResult != AFC_E_SUCCESS) {
|
||||
result.errorMessage =
|
||||
QString("Failed to open file on device: %1 (AFC error: %2)")
|
||||
.arg(devicePath)
|
||||
.arg(static_cast<int>(afc_err));
|
||||
.arg(static_cast<int>(openResult));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -153,30 +156,35 @@ PhotoExportManager::ExportResult PhotoExportManager::exportSingleFile(
|
||||
result.errorMessage = QString("Failed to create local file: %1 (%2)")
|
||||
.arg(outputPath)
|
||||
.arg(outputFile.errorString());
|
||||
afc_file_close(afc, handle);
|
||||
ServiceManager::safeAfcFileClose(device, handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Copy data from device to local file
|
||||
// Copy data from device to local file using ServiceManager
|
||||
char buffer[4096];
|
||||
uint32_t bytesRead = 0;
|
||||
qint64 totalBytes = 0;
|
||||
|
||||
while (afc_file_read(afc, handle, buffer, sizeof(buffer), &bytesRead) ==
|
||||
AFC_E_SUCCESS &&
|
||||
bytesRead > 0) {
|
||||
while (true) {
|
||||
// Check for cancellation during file copy
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_cancelRequested) {
|
||||
outputFile.close();
|
||||
outputFile.remove(); // Clean up partial file
|
||||
afc_file_close(afc, handle);
|
||||
ServiceManager::safeAfcFileClose(device, handle);
|
||||
result.errorMessage = "Export cancelled";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
afc_error_t readResult = ServiceManager::safeAfcFileRead(
|
||||
device, handle, buffer, sizeof(buffer), &bytesRead);
|
||||
|
||||
if (readResult != AFC_E_SUCCESS || bytesRead == 0) {
|
||||
break; // End of file or error
|
||||
}
|
||||
|
||||
qint64 bytesWritten = outputFile.write(buffer, bytesRead);
|
||||
if (bytesWritten != bytesRead) {
|
||||
result.errorMessage =
|
||||
@@ -185,7 +193,7 @@ PhotoExportManager::ExportResult PhotoExportManager::exportSingleFile(
|
||||
.arg(bytesRead);
|
||||
outputFile.close();
|
||||
outputFile.remove(); // Clean up partial file
|
||||
afc_file_close(afc, handle);
|
||||
ServiceManager::safeAfcFileClose(device, handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -194,7 +202,7 @@ PhotoExportManager::ExportResult PhotoExportManager::exportSingleFile(
|
||||
|
||||
// Clean up
|
||||
outputFile.close();
|
||||
afc_file_close(afc, handle);
|
||||
ServiceManager::safeAfcFileClose(device, handle);
|
||||
|
||||
if (totalBytes == 0) {
|
||||
result.errorMessage = "No data read from device file";
|
||||
@@ -244,4 +252,4 @@ PhotoExportManager::generateUniqueOutputPath(const QString &basePath) const
|
||||
counter < 10000); // Prevent infinite loop
|
||||
|
||||
return uniquePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ private slots:
|
||||
|
||||
private:
|
||||
// Export single file using AFC
|
||||
ExportResult exportSingleFile(afc_client_t afc, const QString &devicePath,
|
||||
ExportResult exportSingleFile(iDescriptorDevice *device,
|
||||
const QString &devicePath,
|
||||
const QString &outputPath);
|
||||
|
||||
// Extract filename from device path
|
||||
|
||||
+14
-11
@@ -1,6 +1,7 @@
|
||||
#include "photomodel.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "mediastreamermanager.h"
|
||||
#include "servicemanager.h"
|
||||
#include <QDebug>
|
||||
#include <QEventLoop>
|
||||
#include <QIcon>
|
||||
@@ -330,9 +331,9 @@ QPixmap PhotoModel::loadThumbnailFromDevice(iDescriptorDevice *device,
|
||||
}
|
||||
}
|
||||
|
||||
// Load from device using your AFC function
|
||||
QByteArray imageData = read_afc_file_to_byte_array(
|
||||
device->afcClient, filePath.toUtf8().constData());
|
||||
// Load from device using ServiceManager
|
||||
QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray(
|
||||
device, filePath.toUtf8().constData());
|
||||
|
||||
if (imageData.isEmpty()) {
|
||||
qDebug() << "Could not read from device:" << filePath;
|
||||
@@ -377,9 +378,9 @@ QPixmap PhotoModel::loadImage(iDescriptorDevice *device,
|
||||
}
|
||||
}
|
||||
|
||||
// Load from device using your AFC function
|
||||
QByteArray imageData = read_afc_file_to_byte_array(
|
||||
device->afcClient, filePath.toUtf8().constData());
|
||||
// Load from device using ServiceManager
|
||||
QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray(
|
||||
device, filePath.toUtf8().constData());
|
||||
|
||||
if (imageData.isEmpty()) {
|
||||
qDebug() << "Could not read from device:" << filePath;
|
||||
@@ -422,9 +423,9 @@ void PhotoModel::populatePhotoPaths()
|
||||
m_allPhotos.clear();
|
||||
m_photos.clear();
|
||||
|
||||
// Your existing logic to populate photo paths
|
||||
char **files = nullptr;
|
||||
qDebug() << "Populating photos from album path:" << m_albumPath;
|
||||
// // Your existing logic to populate photo paths
|
||||
// char **files = nullptr;
|
||||
// qDebug() << "Populating photos from album path:" << m_albumPath;
|
||||
|
||||
// First verify the album path exists
|
||||
QByteArray albumPathBytes = m_albumPath.toUtf8();
|
||||
@@ -449,8 +450,10 @@ void PhotoModel::populatePhotoPaths()
|
||||
qDebug() << "Photo directory:" << m_albumPath;
|
||||
qDebug() << "Photo directory C string:" << photoDir;
|
||||
|
||||
afc_error_t readResult = safe_afc_read_directory(
|
||||
m_device->afcClient, m_device->device, photoDir, &files);
|
||||
// Use ServiceManager for thread-safe AFC operations
|
||||
char **files = nullptr;
|
||||
afc_error_t readResult =
|
||||
ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files);
|
||||
if (readResult != AFC_E_SUCCESS) {
|
||||
qDebug() << "Failed to read photo directory:" << photoDir
|
||||
<< "Error:" << readResult;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "servicemanager.h"
|
||||
|
||||
afc_error_t ServiceManager::safeAfcReadDirectory(iDescriptorDevice *device,
|
||||
const char *path, char ***dirs)
|
||||
{
|
||||
return executeAfcOperation(device, [device, path, dirs]() {
|
||||
return afc_read_directory(device->afcClient, path, dirs);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcGetFileInfo(iDescriptorDevice *device,
|
||||
const char *path, char ***info)
|
||||
{
|
||||
return executeAfcOperation(device, [device, path, info]() {
|
||||
return afc_get_file_info(device->afcClient, path, info);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcFileOpen(iDescriptorDevice *device,
|
||||
const char *path,
|
||||
afc_file_mode_t mode,
|
||||
uint64_t *handle)
|
||||
{
|
||||
return executeAfcOperation(device, [device, path, mode, handle]() {
|
||||
return afc_file_open(device->afcClient, path, mode, handle);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcFileRead(iDescriptorDevice *device,
|
||||
uint64_t handle, char *data,
|
||||
uint32_t length,
|
||||
uint32_t *bytes_read)
|
||||
{
|
||||
return executeAfcOperation(
|
||||
device, [device, handle, data, length, bytes_read]() {
|
||||
return afc_file_read(device->afcClient, handle, data, length,
|
||||
bytes_read);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcFileWrite(iDescriptorDevice *device,
|
||||
uint64_t handle, const char *data,
|
||||
uint32_t length,
|
||||
uint32_t *bytes_written)
|
||||
{
|
||||
return executeAfcOperation(
|
||||
device, [device, handle, data, length, bytes_written]() {
|
||||
return afc_file_write(device->afcClient, handle, data, length,
|
||||
bytes_written);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcFileClose(iDescriptorDevice *device,
|
||||
uint64_t handle)
|
||||
{
|
||||
return executeAfcOperation(device, [device, handle]() {
|
||||
return afc_file_close(device->afcClient, handle);
|
||||
});
|
||||
}
|
||||
|
||||
afc_error_t ServiceManager::safeAfcFileSeek(iDescriptorDevice *device,
|
||||
uint64_t handle, int64_t offset,
|
||||
int whence)
|
||||
{
|
||||
return executeAfcOperation(device, [device, handle, offset, whence]() {
|
||||
return afc_file_seek(device->afcClient, handle, offset, whence);
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray ServiceManager::safeReadAfcFileToByteArray(iDescriptorDevice *device,
|
||||
const char *path)
|
||||
{
|
||||
return executeOperation<QByteArray>(device, [device, path]() -> QByteArray {
|
||||
return read_afc_file_to_byte_array(device->afcClient, path);
|
||||
});
|
||||
}
|
||||
|
||||
AFCFileTree ServiceManager::safeGetFileTree(iDescriptorDevice *device,
|
||||
const std::string &path)
|
||||
{
|
||||
return executeOperation<AFCFileTree>(
|
||||
device, [device, path]() -> AFCFileTree {
|
||||
return get_file_tree(device->afcClient, path.c_str());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#ifndef SERVICEMANAGER_H
|
||||
#define SERVICEMANAGER_H
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include <QDebug>
|
||||
#include <functional>
|
||||
#include <libimobiledevice/afc.h>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* @brief Centralized manager for device service operations with thread safety
|
||||
*
|
||||
* This class provides thread-safe wrappers for all device operations to prevent
|
||||
* crashes when devices are unplugged during active operations. It uses a
|
||||
* per-device recursive mutex to ensure that device cleanup waits for all
|
||||
* operations to complete.
|
||||
*/
|
||||
class ServiceManager
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Execute an AFC operation safely with device locking
|
||||
* @param device The device to operate on
|
||||
* @param operation Function that performs the AFC operation
|
||||
* @return Result of the operation
|
||||
*/
|
||||
template <typename T>
|
||||
static T executeOperation(iDescriptorDevice *device,
|
||||
std::function<T()> operation)
|
||||
{
|
||||
if (!device || !device->mutex) {
|
||||
return T{}; // Return default-constructed value for the type
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
|
||||
// Double-check device is still valid after acquiring lock
|
||||
if (!device->afcClient) {
|
||||
return T{};
|
||||
}
|
||||
|
||||
return operation();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T executeOperation(iDescriptorDevice *device,
|
||||
std::function<T()> operation, T failureValue)
|
||||
{
|
||||
if (!device || !device->mutex) {
|
||||
return failureValue;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
|
||||
// Double-check device is still valid after acquiring lock
|
||||
if (!device->afcClient) {
|
||||
return failureValue;
|
||||
}
|
||||
|
||||
return operation();
|
||||
}
|
||||
|
||||
static afc_error_t
|
||||
executeAfcOperation(iDescriptorDevice *device,
|
||||
std::function<afc_error_t()> operation)
|
||||
{
|
||||
try {
|
||||
if (!device || !device->mutex) {
|
||||
return AFC_E_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
|
||||
// Double-check device is still valid after acquiring lock
|
||||
if (!device->afcClient) {
|
||||
return AFC_E_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return operation();
|
||||
} catch (const std::exception &e) {
|
||||
qDebug() << "Exception in executeAfcOperation:" << e.what();
|
||||
return AFC_E_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Execute an AFC operation safely (void return version)
|
||||
* @param device The device to operate on
|
||||
* @param operation Function that performs the AFC operation
|
||||
*/
|
||||
static void executeOperation(iDescriptorDevice *device,
|
||||
std::function<void()> operation)
|
||||
{
|
||||
if (!device || !device->mutex) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
|
||||
// Double-check device is still valid after acquiring lock
|
||||
if (!device->afcClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
operation();
|
||||
}
|
||||
|
||||
// Specific AFC operation wrappers
|
||||
static afc_error_t safeAfcReadDirectory(iDescriptorDevice *device,
|
||||
const char *path, char ***dirs);
|
||||
static afc_error_t safeAfcGetFileInfo(iDescriptorDevice *device,
|
||||
const char *path, char ***info);
|
||||
static afc_error_t safeAfcFileOpen(iDescriptorDevice *device,
|
||||
const char *path, afc_file_mode_t mode,
|
||||
uint64_t *handle);
|
||||
static afc_error_t safeAfcFileRead(iDescriptorDevice *device,
|
||||
uint64_t handle, char *data,
|
||||
uint32_t length, uint32_t *bytes_read);
|
||||
static afc_error_t safeAfcFileWrite(iDescriptorDevice *device,
|
||||
uint64_t handle, const char *data,
|
||||
uint32_t length,
|
||||
uint32_t *bytes_written);
|
||||
static afc_error_t safeAfcFileClose(iDescriptorDevice *device,
|
||||
uint64_t handle);
|
||||
static afc_error_t safeAfcFileSeek(iDescriptorDevice *device,
|
||||
uint64_t handle, int64_t offset,
|
||||
int whence);
|
||||
|
||||
// Utility functions
|
||||
static QByteArray safeReadAfcFileToByteArray(iDescriptorDevice *device,
|
||||
const char *path);
|
||||
static AFCFileTree safeGetFileTree(iDescriptorDevice *device,
|
||||
const std::string &path = "/");
|
||||
};
|
||||
|
||||
#endif // SERVICEMANAGER_H
|
||||
+4
-13
@@ -72,11 +72,8 @@ ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent}
|
||||
updateDeviceList();
|
||||
updateToolboxStates();
|
||||
|
||||
// Connect to AppContext signals
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
|
||||
&ToolboxWidget::onDeviceAdded);
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
|
||||
&ToolboxWidget::onDeviceRemoved);
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceChange, this,
|
||||
&ToolboxWidget::updateUI);
|
||||
}
|
||||
|
||||
void ToolboxWidget::setupUI()
|
||||
@@ -304,13 +301,7 @@ void ToolboxWidget::updateToolboxStates()
|
||||
}
|
||||
}
|
||||
|
||||
void ToolboxWidget::onDeviceAdded()
|
||||
{
|
||||
updateDeviceList();
|
||||
updateToolboxStates();
|
||||
}
|
||||
|
||||
void ToolboxWidget::onDeviceRemoved()
|
||||
void ToolboxWidget::updateUI()
|
||||
{
|
||||
updateDeviceList();
|
||||
updateToolboxStates();
|
||||
@@ -331,7 +322,7 @@ void ToolboxWidget::onDeviceSelectionChanged()
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_uuid.clear(); // No valid device selected
|
||||
m_uuid.clear();
|
||||
}
|
||||
|
||||
void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
|
||||
|
||||
@@ -31,6 +31,7 @@ private:
|
||||
void setupUI();
|
||||
void updateDeviceList();
|
||||
void updateToolboxStates();
|
||||
void updateUI();
|
||||
ClickableWidget *createToolbox(iDescriptorTool tool,
|
||||
const QString &description,
|
||||
bool requiresDevice);
|
||||
|
||||
Reference in New Issue
Block a user