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:
uncor3
2025-10-11 05:36:26 +00:00
parent 8d4f4b11f9
commit bb6b577526
28 changed files with 1234 additions and 343 deletions
+2
View File
@@ -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
)
+7 -5
View File
@@ -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
View File
@@ -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()
+8
View File
@@ -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,
+48 -20
View File
@@ -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();
}
}
+11 -1
View File
@@ -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
+3 -5
View File
@@ -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
View File
@@ -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();
}
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+5 -22
View File
@@ -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
View File
@@ -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;
}
+526
View File
@@ -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;
}
+16 -7
View File
@@ -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);
+2 -1
View File
@@ -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
View File
@@ -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);
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+2 -1
View File
@@ -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
View File
@@ -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;
+85
View File
@@ -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());
});
}
+136
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -31,6 +31,7 @@ private:
void setupUI();
void updateDeviceList();
void updateToolboxStates();
void updateUI();
ClickableWidget *createToolbox(iDescriptorTool tool,
const QString &description,
bool requiresDevice);