mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
move more functionality to singleton devdiskmanager
This commit is contained in:
+195
-279
@@ -1,5 +1,6 @@
|
||||
#include "devdiskimageswidget.h"
|
||||
#include "appcontext.h"
|
||||
#include "devdiskmanager.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QCloseEvent>
|
||||
#include <QComboBox>
|
||||
@@ -23,33 +24,21 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
#include <string>
|
||||
|
||||
// Handle errors , event though it failed, ui thinks it is mounted
|
||||
/*DetailedError: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-2
|
||||
* "Failed to mount
|
||||
* /private/var/run/mobile_image_mounter/42B093B66120045164A6781BD419867320D74D62DA078BB73979CD87C9AC14ECF4331C385F72EAF1F68C81C922D2EF3DE647BC0949DEE6557FBC06DAF7C13FEC/E9E8F8B5021B74DF4C10E6595BB4C16BB45CC55503B8B32085CE31C20A62812D4DB8264F758D5256DBA697139E56F2E8BEE3690EE33F252D17C044BB6C0446A2/tonVFp.dmg."
|
||||
* UserInfo={NSLocalizedDescription=Failed to mount
|
||||
* /private/var/run/mobile_image_mounter/42B093B66120045164A6781BD419867320D74D62DA078BB73979CD87C9AC14ECF4331C385F72EAF1F68C81C922D2EF3DE647BC0949DEE6557FBC06DAF7C13FEC/E9E8F8B5021B74DF4C10E6595BB4C16BB45CC55503B8B32085CE31C20A62812D4DB8264F758D5256DBA697139E56F2E8BEE3690EE33F252D17C044BB6C0446A2/tonVFp.dmg.,
|
||||
* NSUnderlyingError=0x12fe05ce0 {Error
|
||||
* Domain=com.apple.MobileStorage.ErrorDomain Code=-2 "Invalid value for
|
||||
* MountPath: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-3 "A disk
|
||||
* image of type Developer/(null) is already mounted at /Developer."
|
||||
* UserInfo={NSLocalizedDescription=A disk image of type Developer/(null) is
|
||||
* already mounted at /Developer.}" UserInfo={NSLocalizedDescription=Invalid
|
||||
* value for MountPath: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-3
|
||||
* "A disk image of type Developer/(null) is already mounted at /Developer."
|
||||
* UserInfo={NSLocalizedDescription=A disk image of type Developer/(null) is
|
||||
* already mounted at /Developer.}}}}*/
|
||||
|
||||
extern bool mount_dev_image(const char *udid, const char *image_dir_path);
|
||||
// TODO:sometimes non authentic cables do not work with img mounting
|
||||
|
||||
DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}, m_networkManager(new QNetworkAccessManager(this)),
|
||||
m_currentDevice(device)
|
||||
: QWidget{parent}, m_currentDevice(device)
|
||||
{
|
||||
setupUi();
|
||||
fetchImageList();
|
||||
|
||||
// Connect to manager signals
|
||||
// TODO: can prevent race condition ?
|
||||
connect(DevDiskManager::sharedInstance(), &DevDiskManager::imageListFetched,
|
||||
this, &DevDiskImagesWidget::onImageListFetched);
|
||||
|
||||
updateDeviceList();
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
|
||||
&DevDiskImagesWidget::updateDeviceList);
|
||||
@@ -65,44 +54,36 @@ void DevDiskImagesWidget::setupUi()
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
|
||||
auto *pathLayout = new QHBoxLayout();
|
||||
pathLayout->addWidget(new QLabel(tr("Download Path:")));
|
||||
pathLayout->addWidget(new QLabel("Download Path:"));
|
||||
m_downloadPathEdit = new QLineEdit();
|
||||
m_downloadPathEdit->setReadOnly(true);
|
||||
pathLayout->addWidget(m_downloadPathEdit);
|
||||
auto *changeDirButton = new QPushButton(tr("Change..."));
|
||||
auto *changeDirButton = new QPushButton("Change...");
|
||||
connect(changeDirButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::changeDownloadDirectory);
|
||||
pathLayout->addWidget(changeDirButton);
|
||||
layout->addLayout(pathLayout);
|
||||
|
||||
auto *mountLayout = new QHBoxLayout();
|
||||
mountLayout->addWidget(new QLabel(tr("Device:")));
|
||||
mountLayout->addWidget(new QLabel("Device:"));
|
||||
m_deviceComboBox = new QComboBox(this);
|
||||
mountLayout->addWidget(m_deviceComboBox);
|
||||
m_mountButton = new QPushButton(tr("Mount"), this);
|
||||
m_mountButton = new QPushButton("Mount", this);
|
||||
m_check_mountedButton = new QPushButton("Check Mounted", this);
|
||||
connect(m_mountButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::onMountButtonClicked);
|
||||
connect(m_check_mountedButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::checkMountedImage);
|
||||
mountLayout->addWidget(m_mountButton);
|
||||
mountLayout->addWidget(m_check_mountedButton);
|
||||
layout->addLayout(mountLayout);
|
||||
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
layout->addWidget(m_stackedWidget);
|
||||
|
||||
m_initialStatusLabel = new QLabel("Fetching image list...");
|
||||
m_initialStatusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_stackedWidget->addWidget(m_initialStatusLabel);
|
||||
|
||||
m_errorWidget = new QWidget(this);
|
||||
QPushButton *retryButton = new QPushButton(tr("Retry"), m_errorWidget);
|
||||
connect(retryButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::fetchImageList);
|
||||
|
||||
auto *errorLayout = new QVBoxLayout(m_errorWidget);
|
||||
m_statusLabel = new QLabel("");
|
||||
errorLayout->addWidget(m_statusLabel);
|
||||
errorLayout->addWidget(retryButton);
|
||||
errorLayout->addStretch();
|
||||
m_stackedWidget->addWidget(m_errorWidget);
|
||||
m_statusLabel = new QLabel("Fetching image list...");
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_stackedWidget->addWidget(m_statusLabel);
|
||||
|
||||
m_imageListWidget = new QListWidget(this);
|
||||
m_stackedWidget->addWidget(m_imageListWidget);
|
||||
@@ -110,191 +91,96 @@ void DevDiskImagesWidget::setupUi()
|
||||
m_downloadPath =
|
||||
QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages");
|
||||
m_downloadPathEdit->setText(m_downloadPath);
|
||||
|
||||
displayImages();
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::fetchImageList()
|
||||
void DevDiskImagesWidget::fetchImages()
|
||||
{
|
||||
m_stackedWidget->setCurrentWidget(m_initialStatusLabel);
|
||||
|
||||
QUrl url("https://api.github.com/repos/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/git/trees/master?recursive=true");
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this,
|
||||
[this, reply]() { onImageListFetchFinished(reply); });
|
||||
m_stackedWidget->setCurrentWidget(m_statusLabel);
|
||||
m_statusLabel->setText("Fetching image list...");
|
||||
// DevDiskManager::sharedInstance()->fetchImageList();
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onImageListFetchFinished(QNetworkReply *reply)
|
||||
void DevDiskImagesWidget::onImageListFetched(bool success,
|
||||
const QString &errorMessage)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
m_errorWidget->setVisible(true);
|
||||
m_statusLabel->setText(reply->errorString());
|
||||
m_stackedWidget->setCurrentWidget(m_errorWidget);
|
||||
reply->deleteLater();
|
||||
if (!success) {
|
||||
m_statusLabel->setText(
|
||||
QString("Error fetching image list: %1").arg(errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
m_imageListJsonData = data;
|
||||
parseAndDisplayImages(m_imageListJsonData);
|
||||
displayImages();
|
||||
m_stackedWidget->setCurrentWidget(m_imageListWidget);
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onDeviceSelectionChanged(int index)
|
||||
{
|
||||
if (index < 0 ||
|
||||
index >= AppContext::sharedInstance()->getAllDevices().size()) {
|
||||
m_currentDevice = nullptr;
|
||||
} else {
|
||||
m_currentDevice = AppContext::sharedInstance()->getAllDevices()[index];
|
||||
}
|
||||
parseAndDisplayImages(m_imageListJsonData);
|
||||
index >= AppContext::sharedInstance()->getAllDevices().size())
|
||||
return;
|
||||
|
||||
m_currentDevice = AppContext::sharedInstance()->getAllDevices()[index];
|
||||
displayImages();
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData)
|
||||
void DevDiskImagesWidget::displayImages()
|
||||
{
|
||||
m_imageListWidget->clear();
|
||||
m_availableImages.clear();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
||||
if (!doc.isObject()) {
|
||||
m_statusLabel->setText(tr("Invalid image list format."));
|
||||
m_stackedWidget->setCurrentWidget(m_errorWidget);
|
||||
return;
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>>
|
||||
imageFiles; // dir -> {filename -> path}
|
||||
|
||||
QJsonArray tree = doc.object()["tree"].toArray();
|
||||
for (const QJsonValue &value : tree) {
|
||||
QJsonObject obj = value.toObject();
|
||||
QString path = obj["path"].toString();
|
||||
if (path.endsWith(".dmg") || path.endsWith(".dmg.signature")) {
|
||||
QFileInfo fileInfo(path);
|
||||
QString dir = fileInfo.path();
|
||||
QString filename = fileInfo.fileName();
|
||||
if (!dir.isEmpty() && dir != ".")
|
||||
imageFiles[dir][filename] = path;
|
||||
}
|
||||
}
|
||||
|
||||
// Get device iOS version for compatibility checking
|
||||
QString deviceVersion;
|
||||
// Get device version for compatibility checking
|
||||
int deviceMajorVersion = 0;
|
||||
int deviceMinorVersion = 0;
|
||||
bool hasConnectedDevice = false;
|
||||
|
||||
if (m_currentDevice && m_currentDevice->device) {
|
||||
// TODO : use the macro IDEVICE_DEVICE_VERSION
|
||||
unsigned int device_version =
|
||||
idevice_get_device_version(m_currentDevice->device);
|
||||
deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
deviceVersion =
|
||||
QString("%1.%2").arg(deviceMajorVersion).arg(deviceMinorVersion);
|
||||
hasConnectedDevice = true;
|
||||
}
|
||||
|
||||
qDebug() << "Has connected device:" << hasConnectedDevice;
|
||||
// Parse images using manager
|
||||
GetImagesSortedFinalResult sortedResult =
|
||||
DevDiskManager::sharedInstance()->parseImageList(
|
||||
QByteArray(), m_downloadPath, deviceMajorVersion,
|
||||
deviceMinorVersion, m_mounted_sig, m_mounted_sig_len);
|
||||
|
||||
// Separate compatible and other versions
|
||||
QStringList compatibleVersions;
|
||||
QStringList otherVersions;
|
||||
|
||||
for (auto it = imageFiles.constBegin(); it != imageFiles.constEnd(); ++it) {
|
||||
if (it.value().contains("DeveloperDiskImage.dmg") &&
|
||||
it.value().contains("DeveloperDiskImage.dmg.signature")) {
|
||||
QFileInfo dirInfo(it.key());
|
||||
QString version = dirInfo.fileName();
|
||||
m_availableImages[version] = {
|
||||
it.value()["DeveloperDiskImage.dmg"],
|
||||
it.value()["DeveloperDiskImage.dmg.signature"]};
|
||||
|
||||
// Determine compatibility
|
||||
bool isCompatible = false;
|
||||
if (hasConnectedDevice) {
|
||||
// Parse version string (e.g., "15.0", "16.1")
|
||||
QStringList versionParts = version.split('.');
|
||||
if (versionParts.size() >= 1) {
|
||||
bool ok;
|
||||
int imageMajorVersion = versionParts[0].toInt(&ok);
|
||||
if (ok) {
|
||||
// iOS 16+ uses iOS 16 images, earlier versions use
|
||||
// exact or lower version
|
||||
if (deviceMajorVersion >= 16) {
|
||||
isCompatible = (imageMajorVersion == 16);
|
||||
} else {
|
||||
isCompatible =
|
||||
(imageMajorVersion == deviceMajorVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCompatible) {
|
||||
compatibleVersions.append(version);
|
||||
} else {
|
||||
otherVersions.append(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort versions (compatible ones first, then others)
|
||||
auto versionSort = [](const QString &a, const QString &b) {
|
||||
QStringList aParts = a.split('.');
|
||||
QStringList bParts = b.split('.');
|
||||
|
||||
for (int i = 0; i < qMax(aParts.size(), bParts.size()); ++i) {
|
||||
int aNum = (i < aParts.size()) ? aParts[i].toInt() : 0;
|
||||
int bNum = (i < bParts.size()) ? bParts[i].toInt() : 0;
|
||||
|
||||
if (aNum != bNum) {
|
||||
return aNum > bNum; // Descending order (newest first)
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::sort(compatibleVersions.begin(), compatibleVersions.end(),
|
||||
versionSort);
|
||||
std::sort(otherVersions.begin(), otherVersions.end(), versionSort);
|
||||
auto compatibleImages = sortedResult.compatibleImages;
|
||||
auto otherImages = sortedResult.otherImages;
|
||||
|
||||
// Create UI items - compatible versions first
|
||||
auto createVersionItem = [&](const QString &version, bool isCompatible) {
|
||||
auto createVersionItem = [&](const ImageInfo &info, bool isCompatible) {
|
||||
auto *itemWidget = new QWidget();
|
||||
auto *itemLayout = new QHBoxLayout(itemWidget);
|
||||
|
||||
auto *versionLabel = new QLabel(version);
|
||||
auto *versionLabel = new QLabel(info.version);
|
||||
if (isCompatible) {
|
||||
versionLabel->setStyleSheet(
|
||||
"QLabel { font-weight: bold; color: #2E7D32; }");
|
||||
}
|
||||
itemLayout->addWidget(versionLabel);
|
||||
|
||||
// Add compatibility label
|
||||
// Add status labels
|
||||
if (hasConnectedDevice) {
|
||||
if (isCompatible) {
|
||||
auto *compatLabel = new QLabel(tr("✓ Compatible"));
|
||||
compatLabel->setStyleSheet(
|
||||
"QLabel { color: #2E7D32; font-weight: bold; }");
|
||||
itemLayout->addWidget(compatLabel);
|
||||
if (info.isMounted) {
|
||||
auto *mountedLabel = new QLabel("✓ Mounted");
|
||||
mountedLabel->setStyleSheet(
|
||||
"QLabel { color: #1565C0; font-weight: bold; }");
|
||||
itemLayout->addWidget(mountedLabel);
|
||||
}
|
||||
} else {
|
||||
auto *incompatLabel = new QLabel(tr("⚠ Not recommended"));
|
||||
incompatLabel->setStyleSheet("QLabel { color: #F57C00; }");
|
||||
auto *incompatLabel = new QLabel("⚠ Not compatible");
|
||||
incompatLabel->setStyleSheet(
|
||||
"QLabel { color: #F57C00; margin-left: 10px; font-weight: "
|
||||
"bold; }");
|
||||
itemLayout->addWidget(incompatLabel);
|
||||
}
|
||||
}
|
||||
|
||||
QString versionPath = QDir(m_downloadPath).filePath(version);
|
||||
bool exists = QDir(versionPath).exists();
|
||||
|
||||
if (exists) {
|
||||
itemLayout->addWidget(new QLabel(tr("(already exists)")));
|
||||
}
|
||||
|
||||
itemLayout->addStretch();
|
||||
|
||||
auto *progressBar = new QProgressBar();
|
||||
@@ -302,8 +188,8 @@ void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData)
|
||||
itemLayout->addWidget(progressBar);
|
||||
|
||||
auto *downloadButton =
|
||||
new QPushButton(exists ? tr("Re-download") : tr("Download"));
|
||||
downloadButton->setProperty("version", version);
|
||||
new QPushButton(info.isDownloaded ? "Re-download" : "Download");
|
||||
downloadButton->setProperty("version", info.version);
|
||||
connect(downloadButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::onDownloadButtonClicked);
|
||||
itemLayout->addWidget(downloadButton);
|
||||
@@ -315,16 +201,16 @@ void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData)
|
||||
};
|
||||
|
||||
// Add compatible versions first
|
||||
for (const QString &version : compatibleVersions) {
|
||||
createVersionItem(version, true);
|
||||
for (const auto &info : compatibleImages) {
|
||||
createVersionItem(info, true);
|
||||
}
|
||||
|
||||
// Add separator if we have both compatible and other versions
|
||||
if (!compatibleVersions.isEmpty() && !otherVersions.isEmpty()) {
|
||||
if (!compatibleImages.isEmpty() && !otherImages.isEmpty()) {
|
||||
auto *separatorItem = new QListWidgetItem(m_imageListWidget);
|
||||
auto *separatorWidget = new QWidget();
|
||||
auto *separatorLayout = new QHBoxLayout(separatorWidget);
|
||||
auto *separatorLabel = new QLabel(tr("Other versions"));
|
||||
auto *separatorLabel = new QLabel("Other versions");
|
||||
separatorLabel->setStyleSheet(
|
||||
"QLabel { font-weight: bold; color: #757575; margin: 10px 0; }");
|
||||
separatorLayout->addWidget(separatorLabel);
|
||||
@@ -334,8 +220,17 @@ void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData)
|
||||
}
|
||||
|
||||
// Add other versions
|
||||
for (const QString &version : otherVersions) {
|
||||
createVersionItem(version, false);
|
||||
for (const auto &info : otherImages) {
|
||||
createVersionItem(info, false);
|
||||
}
|
||||
|
||||
// Show device info if available
|
||||
if (hasConnectedDevice) {
|
||||
QString deviceVersion =
|
||||
QString("%1.%2").arg(deviceMajorVersion).arg(deviceMinorVersion);
|
||||
m_statusLabel->setText(
|
||||
QString("Connected device: iOS %1 - Compatible images shown at top")
|
||||
.arg(deviceVersion));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,8 +245,9 @@ void DevDiskImagesWidget::onDownloadButtonClicked()
|
||||
QString versionPath = QDir(m_downloadPath).filePath(version);
|
||||
if (QDir(versionPath).exists()) {
|
||||
auto reply = QMessageBox::question(
|
||||
this, tr("Confirm Overwrite"),
|
||||
tr("Directory '%1' already exists. Do you want to overwrite it?")
|
||||
this, "Confirm Overwrite",
|
||||
QString(
|
||||
"Directory '%1' already exists. Do you want to overwrite it?")
|
||||
.arg(version),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) {
|
||||
@@ -364,9 +260,6 @@ void DevDiskImagesWidget::onDownloadButtonClicked()
|
||||
|
||||
void DevDiskImagesWidget::startDownload(const QString &version)
|
||||
{
|
||||
if (!m_availableImages.contains(version))
|
||||
return;
|
||||
|
||||
// Find the button and progress bar for this version
|
||||
QPushButton *downloadButton = nullptr;
|
||||
QProgressBar *progressBar = nullptr;
|
||||
@@ -391,8 +284,8 @@ void DevDiskImagesWidget::startDownload(const QString &version)
|
||||
QString targetDir = QDir(m_downloadPath).filePath(version);
|
||||
if (!QDir().mkpath(targetDir)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Could not create directory: %1").arg(targetDir));
|
||||
this, "Error",
|
||||
QString("Could not create directory: %1").arg(targetDir));
|
||||
downloadButton->setEnabled(true);
|
||||
progressBar->setVisible(false);
|
||||
return;
|
||||
@@ -403,24 +296,21 @@ void DevDiskImagesWidget::startDownload(const QString &version)
|
||||
downloadItem->progressBar = progressBar;
|
||||
downloadItem->downloadButton = downloadButton;
|
||||
|
||||
QString dmgPath = m_availableImages[version].first;
|
||||
QString sigPath = m_availableImages[version].second;
|
||||
auto replies = DevDiskManager::sharedInstance()->downloadImage(version);
|
||||
downloadItem->dmgReply = replies.first;
|
||||
downloadItem->sigReply = replies.second;
|
||||
|
||||
if (!downloadItem->dmgReply || !downloadItem->sigReply) {
|
||||
delete downloadItem;
|
||||
downloadButton->setEnabled(true);
|
||||
progressBar->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl dmgUrl("https://raw.githubusercontent.com/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/master/" +
|
||||
dmgPath);
|
||||
QNetworkRequest dmgRequest(dmgUrl);
|
||||
downloadItem->dmgReply = m_networkManager->get(dmgRequest);
|
||||
connect(downloadItem->dmgReply, &QNetworkReply::downloadProgress, this,
|
||||
&DevDiskImagesWidget::onDownloadProgress);
|
||||
connect(downloadItem->dmgReply, &QNetworkReply::finished, this,
|
||||
&DevDiskImagesWidget::onFileDownloadFinished);
|
||||
|
||||
QUrl sigUrl("https://raw.githubusercontent.com/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/master/" +
|
||||
sigPath);
|
||||
QNetworkRequest sigRequest(sigUrl);
|
||||
downloadItem->sigReply = m_networkManager->get(sigRequest);
|
||||
connect(downloadItem->sigReply, &QNetworkReply::downloadProgress, this,
|
||||
&DevDiskImagesWidget::onDownloadProgress);
|
||||
connect(downloadItem->sigReply, &QNetworkReply::finished, this,
|
||||
@@ -458,6 +348,7 @@ void DevDiskImagesWidget::onDownloadProgress(qint64 bytesReceived,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: file saving should be in manager
|
||||
void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
{
|
||||
auto *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
@@ -468,8 +359,8 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
m_activeDownloads.remove(reply);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(this, tr("Download Error"),
|
||||
tr("Failed to download %1: %2")
|
||||
QMessageBox::critical(this, "Download Error",
|
||||
QString("Failed to download %1: %2")
|
||||
.arg(reply->url().path())
|
||||
.arg(reply->errorString()));
|
||||
|
||||
@@ -479,7 +370,7 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
item->dmgReply->abort();
|
||||
|
||||
item->downloadButton->setEnabled(true);
|
||||
item->downloadButton->setText(tr("Retry"));
|
||||
item->downloadButton->setText("Retry");
|
||||
item->progressBar->setVisible(false);
|
||||
|
||||
if (m_activeDownloads.key(item) == nullptr) {
|
||||
@@ -497,8 +388,9 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
|
||||
QFile file(targetPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::critical(this, tr("File Error"),
|
||||
tr("Could not save file: %1").arg(targetPath));
|
||||
QMessageBox::critical(
|
||||
this, "File Error",
|
||||
QString("Could not save file: %1").arg(targetPath));
|
||||
} else {
|
||||
file.write(reply->readAll());
|
||||
file.close();
|
||||
@@ -507,7 +399,7 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
reply->deleteLater();
|
||||
|
||||
if (m_activeDownloads.key(item) == nullptr) { // Both files downloaded
|
||||
item->downloadButton->setText(tr("Downloaded"));
|
||||
item->downloadButton->setText("Downloaded");
|
||||
item->downloadButton->setEnabled(false);
|
||||
item->progressBar->setValue(100);
|
||||
item->progressBar->setVisible(false);
|
||||
@@ -518,19 +410,12 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
void DevDiskImagesWidget::updateDeviceList()
|
||||
{
|
||||
auto devices = AppContext::sharedInstance()->getAllDevices();
|
||||
qDebug() << "devdiskwidget devices:" << devices.size();
|
||||
QString currentUdid = "";
|
||||
if (m_deviceComboBox->count() > 0 &&
|
||||
m_deviceComboBox->currentIndex() >= 0) {
|
||||
currentUdid = m_deviceComboBox->currentData().toString();
|
||||
}
|
||||
|
||||
// Temporarily disconnect to avoid triggering onDeviceSelectionChanged
|
||||
// multiple times
|
||||
disconnect(m_deviceComboBox,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DevDiskImagesWidget::onDeviceSelectionChanged);
|
||||
|
||||
m_deviceComboBox->clear();
|
||||
|
||||
int newIndex = -1;
|
||||
@@ -548,46 +433,22 @@ void DevDiskImagesWidget::updateDeviceList()
|
||||
|
||||
if (newIndex != -1) {
|
||||
m_deviceComboBox->setCurrentIndex(newIndex);
|
||||
m_currentDevice = devices.at(newIndex);
|
||||
} else if (!devices.isEmpty()) {
|
||||
// If no previous device was selected but devices are available, select
|
||||
// the first one
|
||||
m_deviceComboBox->setCurrentIndex(0);
|
||||
m_currentDevice = devices.at(0);
|
||||
} else {
|
||||
m_currentDevice = nullptr;
|
||||
}
|
||||
|
||||
// Reconnect the signal
|
||||
connect(m_deviceComboBox,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DevDiskImagesWidget::onDeviceSelectionChanged);
|
||||
|
||||
qDebug() << "devdiskwidget device:" << m_deviceComboBox->currentText();
|
||||
qDebug() << "devdiskwidget Current device:"
|
||||
<< (m_currentDevice
|
||||
? m_currentDevice->deviceInfo.deviceName.c_str()
|
||||
: "None");
|
||||
|
||||
// Refresh the UI with the updated device information
|
||||
if (!m_imageListJsonData.isEmpty()) {
|
||||
parseAndDisplayImages(m_imageListJsonData);
|
||||
}
|
||||
displayImages();
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onMountButtonClicked()
|
||||
{
|
||||
if (m_deviceComboBox->currentIndex() < 0) {
|
||||
QMessageBox::warning(
|
||||
this, tr("No Device"),
|
||||
tr("Please select a device to mount the image on."));
|
||||
QMessageBox::warning(this, "No Device",
|
||||
"Please select a device to mount the image on.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *currentItem = m_imageListWidget->currentItem();
|
||||
if (!currentItem) {
|
||||
QMessageBox::warning(this, tr("No Image Selected"),
|
||||
tr("Please select a disk image to mount."));
|
||||
QMessageBox::warning(this, "No Image Selected",
|
||||
"Please select a disk image to mount.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -605,50 +466,37 @@ void DevDiskImagesWidget::mountImage(const QString &version)
|
||||
{
|
||||
QString udid = m_deviceComboBox->currentData().toString();
|
||||
if (udid.isEmpty()) {
|
||||
QMessageBox::warning(this, tr("No Device"),
|
||||
tr("Please select a device."));
|
||||
QMessageBox::warning(this, "No Device", "Please select a device.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: add a refresh button
|
||||
QString versionPath = QDir(m_downloadPath).filePath(version);
|
||||
if (!QDir(versionPath).exists()) {
|
||||
if (!DevDiskManager::sharedInstance()->isImageDownloaded(version,
|
||||
m_downloadPath)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Image Not Found"),
|
||||
tr("The selected disk image for version %1 is not downloaded. "
|
||||
"Please download it first.")
|
||||
this, "Image Not Found",
|
||||
QString("The selected disk image for version %1 is not downloaded. "
|
||||
"Please download it first.")
|
||||
.arg(version));
|
||||
return;
|
||||
}
|
||||
|
||||
QString dmgPath = QDir(versionPath).filePath("DeveloperDiskImage.dmg");
|
||||
QString sigPath =
|
||||
QDir(versionPath).filePath("DeveloperDiskImage.dmg.signature");
|
||||
|
||||
if (!QFile::exists(dmgPath) || !QFile::exists(sigPath)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Image Files Missing"),
|
||||
tr("Image files are missing in %1. Please re-download.")
|
||||
.arg(versionPath));
|
||||
return;
|
||||
}
|
||||
|
||||
m_mountButton->setEnabled(false);
|
||||
m_mountButton->setText(tr("Mounting..."));
|
||||
m_mountButton->setText("Mounting...");
|
||||
|
||||
bool success = mount_dev_image(udid.toUtf8().constData(),
|
||||
versionPath.toUtf8().constData());
|
||||
bool success = DevDiskManager::sharedInstance()->mountImage(version, udid,
|
||||
m_downloadPath);
|
||||
|
||||
m_mountButton->setEnabled(true);
|
||||
m_mountButton->setText(tr("Mount"));
|
||||
m_mountButton->setText("Mount");
|
||||
|
||||
if (success) {
|
||||
QMessageBox::information(this, tr("Success"),
|
||||
tr("Image mounted successfully on %1.")
|
||||
QMessageBox::information(this, "Success",
|
||||
QString("Image mounted successfully on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
displayImages(); // Refresh to show mounted status
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Mount Failed"),
|
||||
tr("Failed to mount image on %1.")
|
||||
QMessageBox::critical(this, "Mount Failed",
|
||||
QString("Failed to mount image on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
}
|
||||
}
|
||||
@@ -656,14 +504,12 @@ void DevDiskImagesWidget::mountImage(const QString &version)
|
||||
void DevDiskImagesWidget::changeDownloadDirectory()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Select Download Directory"), m_downloadPath,
|
||||
this, "Select Download Directory", m_downloadPath,
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
if (!dir.isEmpty() && dir != m_downloadPath) {
|
||||
m_downloadPath = dir;
|
||||
m_downloadPathEdit->setText(m_downloadPath);
|
||||
if (!m_imageListJsonData.isEmpty()) {
|
||||
parseAndDisplayImages(m_imageListJsonData);
|
||||
}
|
||||
displayImages();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,9 +517,10 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (!m_activeDownloads.isEmpty()) {
|
||||
auto reply = QMessageBox::question(
|
||||
this, tr("Downloads in Progress"),
|
||||
tr("There are %1 download(s) in progress. Do you really want to "
|
||||
"close and cancel all downloads?")
|
||||
this, "Downloads in Progress",
|
||||
QString(
|
||||
"There are %1 download(s) in progress. Do you really want to "
|
||||
"close and cancel all downloads?")
|
||||
.arg(m_activeDownloads.size()),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
|
||||
@@ -693,4 +540,73 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event)
|
||||
}
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
// Toolbox clicked: "Developer Disk Images"
|
||||
// terminate called after throwing an instance of 'std::logic_error'
|
||||
// what(): basic_string: construction from null is not valid
|
||||
|
||||
void DevDiskImagesWidget::checkMountedImage()
|
||||
{
|
||||
if (m_deviceComboBox->currentIndex() < 0) {
|
||||
QMessageBox::warning(
|
||||
this, "No Device",
|
||||
"Please select a device to check the mounted image.");
|
||||
return;
|
||||
}
|
||||
|
||||
GetMountedImageResult result =
|
||||
DevDiskManager::sharedInstance()->getMountedImage(
|
||||
m_currentDevice->udid.c_str());
|
||||
|
||||
qDebug() << "checkMountedImage result:" << result.success
|
||||
<< result.message.c_str() << QString::fromStdString(result.output);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
m_mounted_sig = strdup(result.output.c_str());
|
||||
m_mounted_sig_len = result.output.size();
|
||||
displayImages(); // Refresh to show mounted status
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::information(this, "Something went wrong",
|
||||
result.message.c_str());
|
||||
// get_mounted_image(m_currentDevice->udid.c_str());
|
||||
|
||||
// plist_t sig_array_node =
|
||||
// plist_dict_get_item(result.output, "ImageSignature");
|
||||
|
||||
// if (result.success == false || sig_array_node == NULL) {
|
||||
// QMessageBox::information(
|
||||
// this, "Locked",
|
||||
// "The device is locked. Please unlock it and try again.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// char *mounted_sig = nullptr;
|
||||
// uint64_t mounted_sig_len = 0;
|
||||
|
||||
// if (sig_array_node && plist_get_node_type(sig_array_node) == PLIST_ARRAY
|
||||
// &&
|
||||
// plist_array_get_size(sig_array_node) > 0) {
|
||||
// plist_t sig_data_node = plist_array_get_item(sig_array_node, 0);
|
||||
// if (sig_data_node && plist_get_node_type(sig_data_node) ==
|
||||
// PLIST_DATA) {
|
||||
// plist_get_data_val(sig_data_node, &mounted_sig,
|
||||
// &mounted_sig_len);
|
||||
// }
|
||||
// }
|
||||
|
||||
// auto compatibleImages =
|
||||
// DevDiskManager::sharedInstance()->getCompatibleImages();
|
||||
// for (const auto &info : compatibleImages) {
|
||||
// if (info.isMounted) {
|
||||
// displayImages(); // Refresh to show mounted status
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// QMessageBox::information(this, "Not Mounted",
|
||||
// "The device has no mounted images.");
|
||||
}
|
||||
@@ -23,22 +23,25 @@ public:
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void fetchImageList();
|
||||
void onImageListFetchFinished(QNetworkReply *reply);
|
||||
void fetchImages();
|
||||
void onDownloadButtonClicked();
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onFileDownloadFinished();
|
||||
void changeDownloadDirectory();
|
||||
void updateDeviceList();
|
||||
void onMountButtonClicked();
|
||||
void onImageListFetched(bool success,
|
||||
const QString &errorMessage = QString());
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void parseAndDisplayImages(const QByteArray &jsonData);
|
||||
void displayImages();
|
||||
void startDownload(const QString &version);
|
||||
void mountImage(const QString &version);
|
||||
void onDeviceSelectionChanged(int index);
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void checkMountedImage();
|
||||
|
||||
struct DownloadItem {
|
||||
QNetworkReply *dmgReply = nullptr;
|
||||
QNetworkReply *sigReply = nullptr;
|
||||
@@ -51,6 +54,9 @@ private:
|
||||
qint64 sigReceived = 0;
|
||||
};
|
||||
|
||||
char *m_mounted_sig = NULL;
|
||||
uint64_t m_mounted_sig_len = 0;
|
||||
|
||||
QStackedWidget *m_stackedWidget;
|
||||
QListWidget *m_imageListWidget;
|
||||
QLabel *m_statusLabel;
|
||||
@@ -59,15 +65,16 @@ private:
|
||||
QLineEdit *m_downloadPathEdit;
|
||||
QComboBox *m_deviceComboBox;
|
||||
QPushButton *m_mountButton;
|
||||
QPushButton *m_check_mountedButton;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_downloadPath;
|
||||
iDescriptorDevice *m_currentDevice;
|
||||
QStringList m_compatibleVersions;
|
||||
QStringList m_otherVersions;
|
||||
|
||||
QMap<QString, QPair<QString, QString>>
|
||||
m_availableImages; // version -> {dmg_path, sig_path}
|
||||
QMap<QNetworkReply *, DownloadItem *> m_activeDownloads;
|
||||
QByteArray m_imageListJsonData;
|
||||
};
|
||||
|
||||
#endif // DEVDISKIMAGESWIDGET_H
|
||||
|
||||
@@ -0,0 +1,538 @@
|
||||
#include "devdiskmanager.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
DevDiskManager *DevDiskManager::sharedInstance()
|
||||
{
|
||||
static DevDiskManager instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
DevDiskManager::DevDiskManager(QObject *parent) : QObject{parent}
|
||||
{
|
||||
m_networkManager = new QNetworkAccessManager(this);
|
||||
m_isImageListReady = false; // Explicitly set initial state
|
||||
fetchImageList();
|
||||
}
|
||||
|
||||
QNetworkReply *DevDiskManager::fetchImageList()
|
||||
{
|
||||
QUrl url("https://api.github.com/repos/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/git/trees/master?recursive=true");
|
||||
QNetworkRequest request(url);
|
||||
auto *reply = m_networkManager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit imageListFetched(false, reply->errorString());
|
||||
} else {
|
||||
m_imageListJsonData = reply->readAll();
|
||||
m_isImageListReady = true; // Set the flag on success
|
||||
emit imageListFetched(true);
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_imageListJsonData);
|
||||
// if (!doc.isObject()) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
QMap<QString, QMap<QString, QString>>
|
||||
imageFiles; // dir -> {filename -> path}
|
||||
|
||||
QJsonArray tree = doc.object()["tree"].toArray();
|
||||
for (const QJsonValue &value : tree) {
|
||||
QJsonObject obj = value.toObject();
|
||||
QString path = obj["path"].toString();
|
||||
if (path.endsWith(".dmg") || path.endsWith(".dmg.signature")) {
|
||||
QFileInfo fileInfo(path);
|
||||
QString dir = fileInfo.path();
|
||||
QString filename = fileInfo.fileName();
|
||||
if (!dir.isEmpty() && dir != ".")
|
||||
imageFiles[dir][filename] = path;
|
||||
}
|
||||
}
|
||||
return imageFiles;
|
||||
}
|
||||
|
||||
GetImagesSortedFinalResult DevDiskManager::parseImageList(
|
||||
const QByteArray &jsonData, const QString &downloadPath,
|
||||
int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig,
|
||||
uint64_t mounted_sig_len)
|
||||
{
|
||||
m_availableImages.clear();
|
||||
QStringList compatibleVersions = QStringList();
|
||||
QStringList otherVersions = QStringList();
|
||||
|
||||
// TODO : wtf is this ?
|
||||
if (!jsonData.isEmpty()) {
|
||||
m_imageListJsonData = jsonData;
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>> imageFiles = parseDiskDir();
|
||||
GetImagesSortedResult sortedResult =
|
||||
getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion,
|
||||
downloadPath, mounted_sig, mounted_sig_len);
|
||||
sortVersions(sortedResult);
|
||||
|
||||
QList<ImageInfo> compatibleResult;
|
||||
for (const QString &version : sortedResult.compatibleImages) {
|
||||
compatibleResult.append(m_availableImages[version]);
|
||||
}
|
||||
QList<ImageInfo> otherResult;
|
||||
for (const QString &version : sortedResult.otherImages) {
|
||||
otherResult.append(m_availableImages[version]);
|
||||
}
|
||||
|
||||
return GetImagesSortedFinalResult{compatibleResult, otherResult};
|
||||
}
|
||||
|
||||
void DevDiskManager::sortVersions(GetImagesSortedResult &sortedResult)
|
||||
{
|
||||
QStringList &compatibleVersions = sortedResult.compatibleImages;
|
||||
QStringList &otherVersions = sortedResult.otherImages;
|
||||
auto versionSort = [](const QString &a, const QString &b) {
|
||||
QStringList aParts = a.split('.');
|
||||
QStringList bParts = b.split('.');
|
||||
|
||||
for (int i = 0; i < qMax(aParts.size(), bParts.size()); ++i) {
|
||||
int aNum = (i < aParts.size()) ? aParts[i].toInt() : 0;
|
||||
int bNum = (i < bParts.size()) ? bParts[i].toInt() : 0;
|
||||
|
||||
if (aNum != bNum) {
|
||||
return aNum > bNum; // Descending order (newest first)
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::sort(compatibleVersions.begin(), compatibleVersions.end(),
|
||||
versionSort);
|
||||
std::sort(otherVersions.begin(), otherVersions.end(), versionSort);
|
||||
}
|
||||
|
||||
GetImagesSortedResult DevDiskManager::getImagesSorted(
|
||||
QMap<QString, QMap<QString, QString>> imageFiles, int deviceMajorVersion,
|
||||
int deviceMinorVersion, const QString &downloadPath,
|
||||
const char *mounted_sig, uint64_t mounted_sig_len)
|
||||
{
|
||||
|
||||
QStringList compatibleVersions = QStringList();
|
||||
QStringList otherVersions = QStringList();
|
||||
// TODO: what is this ?
|
||||
bool hasConnectedDevice = (deviceMajorVersion > 0);
|
||||
|
||||
for (auto it = imageFiles.constBegin(); it != imageFiles.constEnd(); ++it) {
|
||||
if (it.value().contains("DeveloperDiskImage.dmg") &&
|
||||
it.value().contains("DeveloperDiskImage.dmg.signature")) {
|
||||
QFileInfo dirInfo(it.key());
|
||||
QString version = dirInfo.fileName();
|
||||
|
||||
ImageInfo info;
|
||||
info.version = version;
|
||||
info.dmgPath = it.value()["DeveloperDiskImage.dmg"];
|
||||
info.sigPath = it.value()["DeveloperDiskImage.dmg.signature"];
|
||||
info.isDownloaded = isImageDownloaded(version, downloadPath);
|
||||
|
||||
// Determine compatibility
|
||||
if (hasConnectedDevice) {
|
||||
QStringList versionParts = version.split('.');
|
||||
if (versionParts.size() >= 1) {
|
||||
bool ok;
|
||||
int imageMajorVersion = versionParts[0].toInt(&ok);
|
||||
if (ok) {
|
||||
if (deviceMajorVersion >= 16) {
|
||||
info.isCompatible = (imageMajorVersion == 16);
|
||||
} else {
|
||||
info.isCompatible =
|
||||
(imageMajorVersion == deviceMajorVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if mounted
|
||||
if (info.isCompatible && info.isDownloaded && mounted_sig) {
|
||||
QString sigLocalPath =
|
||||
QDir(QDir(downloadPath).filePath(version))
|
||||
.filePath("DeveloperDiskImage.dmg.signature");
|
||||
info.isMounted =
|
||||
compareSignatures(sigLocalPath.toUtf8().constData(),
|
||||
mounted_sig, mounted_sig_len);
|
||||
}
|
||||
|
||||
m_availableImages[version] = info;
|
||||
|
||||
if (info.isCompatible) {
|
||||
compatibleVersions.append(version);
|
||||
} else {
|
||||
otherVersions.append(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
return GetImagesSortedResult{compatibleVersions, otherVersions};
|
||||
}
|
||||
|
||||
QList<ImageInfo> DevDiskManager::getAllImages() const
|
||||
{
|
||||
return m_availableImages.values();
|
||||
}
|
||||
|
||||
QPair<QNetworkReply *, QNetworkReply *>
|
||||
DevDiskManager::downloadImage(const QString &version)
|
||||
{
|
||||
qDebug() << "Request to download image version:" << version;
|
||||
if (!m_availableImages.contains(version)) {
|
||||
qDebug() << "Image not found:" << version;
|
||||
emit imageDownloadFinished(version, false, "Image version not found.");
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
QString targetDir = QDir("devdiskimages").filePath(version);
|
||||
if (!QDir().mkpath(targetDir)) {
|
||||
qDebug() << "Could not create directory:" << targetDir;
|
||||
emit imageDownloadFinished(
|
||||
version, false,
|
||||
QString("Could not create directory: %1").arg(targetDir));
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
const ImageInfo &info = m_availableImages[version];
|
||||
|
||||
QUrl dmgUrl("https://raw.githubusercontent.com/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/master/" +
|
||||
info.dmgPath);
|
||||
QNetworkRequest dmgRequest(dmgUrl);
|
||||
QNetworkReply *dmgReply = m_networkManager->get(dmgRequest);
|
||||
|
||||
QUrl sigUrl("https://raw.githubusercontent.com/mspvirajpatel/"
|
||||
"Xcode_Developer_Disk_Images/master/" +
|
||||
info.sigPath);
|
||||
QNetworkRequest sigRequest(sigUrl);
|
||||
QNetworkReply *sigReply = m_networkManager->get(sigRequest);
|
||||
|
||||
return {dmgReply, sigReply};
|
||||
}
|
||||
|
||||
bool DevDiskManager::isImageDownloaded(const QString &version,
|
||||
const QString &downloadPath) const
|
||||
{
|
||||
QString versionPath = QDir(downloadPath).filePath(version);
|
||||
QString dmgPath = QDir(versionPath).filePath("DeveloperDiskImage.dmg");
|
||||
QString sigPath =
|
||||
QDir(versionPath).filePath("DeveloperDiskImage.dmg.signature");
|
||||
|
||||
return QFile::exists(dmgPath) && QFile::exists(sigPath);
|
||||
}
|
||||
|
||||
bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device,
|
||||
const QString &downloadPath)
|
||||
{
|
||||
GetMountedImageResult res = getMountedImage(device->udid.c_str());
|
||||
if (res.success) {
|
||||
qDebug() << "An image is already mounted on device:"
|
||||
<< device->udid.c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
GetImagesSortedFinalResult images =
|
||||
parseImageList(m_imageListJsonData, downloadPath, 15, 0,
|
||||
res.output.c_str(), res.output.length());
|
||||
|
||||
// // 1. Try to mount an already downloaded compatible image
|
||||
// for (const ImageInfo &info : images.compatibleImages) {
|
||||
// if (info.isDownloaded) {
|
||||
// if (mountImage(info.version, device->udid.c_str(), downloadPath))
|
||||
// {
|
||||
// qDebug() << "Mounted existing image version" << info.version
|
||||
// << "on device:" << device->udid.c_str();
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 2. If none are downloaded, download the newest compatible one
|
||||
if (!images.compatibleImages.isEmpty()) {
|
||||
const QString versionToDownload =
|
||||
images.compatibleImages.first().version;
|
||||
qDebug() << "No compatible image found locally. Downloading version:"
|
||||
<< versionToDownload;
|
||||
|
||||
// Connect a one-time slot to mount the image after download finishes
|
||||
// connect(
|
||||
// this, &DevDiskManager::imageDownloadFinished, this,
|
||||
// [this, device, downloadPath](const QString &finishedVersion,
|
||||
// bool success,
|
||||
// const QString &errorMessage) {
|
||||
// if (success && finishedVersion == versionToDownload) {
|
||||
// qDebug() << "Download finished for" << finishedVersion
|
||||
// << ". Now attempting to mount.";
|
||||
// mountImage(finishedVersion, device->udid.c_str(),
|
||||
// downloadPath);
|
||||
// // TODO: You might want to emit another signal here to
|
||||
// // notify the UI of the final mount result.
|
||||
// } else if (!success) {
|
||||
// qDebug() << "Failed to download" << finishedVersion <<
|
||||
// ":"
|
||||
// << errorMessage;
|
||||
// }
|
||||
// },
|
||||
// Qt::SingleShotConnection);
|
||||
|
||||
// Start the download
|
||||
QPair<QNetworkReply *, QNetworkReply *> replies =
|
||||
downloadImage(versionToDownload);
|
||||
auto *downloadItem = new DownloadItem();
|
||||
downloadItem->version = versionToDownload;
|
||||
downloadItem->downloadPath = downloadPath;
|
||||
downloadItem->dmgReply = replies.first;
|
||||
downloadItem->sigReply = replies.second;
|
||||
|
||||
connect(downloadItem->dmgReply, &QNetworkReply::downloadProgress, this,
|
||||
&DevDiskManager::onDownloadProgress);
|
||||
connect(downloadItem->dmgReply, &QNetworkReply::finished, this,
|
||||
&DevDiskManager::onFileDownloadFinished);
|
||||
connect(downloadItem->sigReply, &QNetworkReply::downloadProgress, this,
|
||||
&DevDiskManager::onDownloadProgress);
|
||||
connect(downloadItem->sigReply, &QNetworkReply::finished, this,
|
||||
&DevDiskManager::onFileDownloadFinished);
|
||||
|
||||
m_activeDownloads[downloadItem->dmgReply] = downloadItem;
|
||||
m_activeDownloads[downloadItem->sigReply] = downloadItem;
|
||||
return true; // Indicate that the async operation has started
|
||||
}
|
||||
|
||||
qDebug() << "No compatible image found to mount on device:"
|
||||
<< device->udid.c_str();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device,
|
||||
const QString &downloadPath)
|
||||
{
|
||||
if (m_isImageListReady) {
|
||||
// If the list is already fetched, run the logic immediately.
|
||||
return mountCompatibleImageInternal(device, downloadPath);
|
||||
} else {
|
||||
// Otherwise, connect to the signal and wait.
|
||||
qDebug() << "Image list not ready, waiting for it to be fetched...";
|
||||
connect(
|
||||
this, &DevDiskManager::imageListFetched, this,
|
||||
[this, device, downloadPath](bool success) {
|
||||
if (success) {
|
||||
qDebug() << "Image list is now ready. Retrying mount...";
|
||||
mountCompatibleImageInternal(device, downloadPath);
|
||||
} else {
|
||||
qDebug() << "Failed to fetch image list. Cannot mount.";
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
// The operation is now asynchronous, the immediate return value
|
||||
// indicates that the process has started.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DevDiskManager::mountImage(const QString &version, const QString &udid,
|
||||
const QString &downloadPath)
|
||||
{
|
||||
if (!isImageDownloaded(version, downloadPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString versionPath = QDir(downloadPath).filePath(version);
|
||||
return mount_dev_image(udid.toUtf8().constData(),
|
||||
versionPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
void DevDiskManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
auto *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (!reply || !m_activeDownloads.contains(reply))
|
||||
return;
|
||||
|
||||
auto *item = m_activeDownloads[reply];
|
||||
|
||||
if (reply->property("totalSizeAdded").isNull() && bytesTotal > 0) {
|
||||
item->totalSize += bytesTotal;
|
||||
reply->setProperty("totalSizeAdded", true);
|
||||
}
|
||||
|
||||
if (reply == item->dmgReply) {
|
||||
item->dmgReceived = bytesReceived;
|
||||
} else if (reply == item->sigReply) {
|
||||
item->sigReceived = bytesReceived;
|
||||
}
|
||||
|
||||
qint64 totalReceived = item->dmgReceived + item->sigReceived;
|
||||
|
||||
if (item->totalSize > 0) {
|
||||
emit imageDownloadProgress(item->version,
|
||||
(totalReceived * 100) / item->totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskManager::onFileDownloadFinished()
|
||||
{
|
||||
auto *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (!reply || !m_activeDownloads.contains(reply))
|
||||
return;
|
||||
|
||||
auto *item = m_activeDownloads[reply];
|
||||
m_activeDownloads.remove(reply);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit imageDownloadFinished(item->version, false, reply->errorString());
|
||||
|
||||
if (reply == item->dmgReply && item->sigReply)
|
||||
item->sigReply->abort();
|
||||
if (reply == item->sigReply && item->dmgReply)
|
||||
item->dmgReply->abort();
|
||||
|
||||
if (m_activeDownloads.key(item) == nullptr) {
|
||||
delete item;
|
||||
}
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = QUrl::fromPercentEncoding(reply->url().path().toUtf8());
|
||||
QFileInfo fileInfo(path);
|
||||
QString filename = fileInfo.fileName();
|
||||
// TODO
|
||||
// QString targetPath =
|
||||
// QDir(QDir(item->downloadPath).filePath(item->version))
|
||||
|
||||
QString targetPath = QDir(QDir("./devdiskimages").filePath(item->version))
|
||||
.filePath(filename);
|
||||
// Saving downloaded file to: "/tmp/15.7/DeveloperDiskImage.dmg.signature"
|
||||
// Saving downloaded file to: "/tmp/15.7/DeveloperDiskImage.dmg"
|
||||
|
||||
QFile file(targetPath);
|
||||
qDebug() << "Saving downloaded file to:" << targetPath;
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
emit imageDownloadFinished(
|
||||
item->version, false,
|
||||
QString("Could not save file: %1").arg(targetPath));
|
||||
} else {
|
||||
file.write(reply->readAll());
|
||||
file.close();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (m_activeDownloads.key(item) == nullptr) { // Both files finished
|
||||
emit imageDownloadFinished(item->version, true);
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
bool DevDiskManager::unmountImage()
|
||||
{
|
||||
// Implementation for unmounting the currently mounted disk image
|
||||
return false; // TODO: Implement when unmount functionality is available
|
||||
}
|
||||
|
||||
bool DevDiskManager::compareSignatures(const char *signature_file_path,
|
||||
const char *mounted_sig,
|
||||
uint64_t mounted_sig_len)
|
||||
{
|
||||
FILE *f_sig = fopen(signature_file_path, "rb");
|
||||
if (!f_sig) {
|
||||
qDebug() << "ERROR: Could not open signature file:"
|
||||
<< signature_file_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f_sig, 0, SEEK_END);
|
||||
long local_sig_len = ftell(f_sig);
|
||||
fseek(f_sig, 0, SEEK_SET);
|
||||
|
||||
char *local_sig = (char *)malloc(local_sig_len);
|
||||
if (!local_sig) {
|
||||
fclose(f_sig);
|
||||
return false;
|
||||
}
|
||||
|
||||
fread(local_sig, 1, local_sig_len, f_sig);
|
||||
fclose(f_sig);
|
||||
|
||||
bool matches = false;
|
||||
if ((mounted_sig_len == (uint64_t)local_sig_len) &&
|
||||
(memcmp(mounted_sig, local_sig, mounted_sig_len) == 0)) {
|
||||
qDebug() << "Signatures match!";
|
||||
matches = true;
|
||||
} else {
|
||||
qDebug() << "Signatures DO NOT match!";
|
||||
}
|
||||
|
||||
free(local_sig);
|
||||
return matches;
|
||||
}
|
||||
|
||||
GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
|
||||
{
|
||||
QPair<bool, plist_t> result = _get_mounted_image(udid);
|
||||
|
||||
if (result.first == false) {
|
||||
plist_t sig_err = plist_dict_get_item(result.second, "Error");
|
||||
// TODO: should print ?
|
||||
plist_print(result.second);
|
||||
if (sig_err) {
|
||||
char *error = NULL;
|
||||
plist_get_string_val(sig_err, &error);
|
||||
if (error == "DeviceLocked") {
|
||||
qDebug() << "Error:" << error;
|
||||
free(error);
|
||||
plist_free(result.second);
|
||||
return GetMountedImageResult{false, "", "Device is locked"};
|
||||
}
|
||||
} else {
|
||||
return GetMountedImageResult{false, "", "Unknown error"};
|
||||
}
|
||||
}
|
||||
|
||||
plist_t sig_array_node =
|
||||
plist_dict_get_item(result.second, "ImageSignature");
|
||||
if (sig_array_node == NULL) {
|
||||
plist_free(result.second);
|
||||
return GetMountedImageResult{false, "", "No disk image mounted"};
|
||||
}
|
||||
|
||||
char *mounted_sig = nullptr;
|
||||
uint64_t mounted_sig_len = 0;
|
||||
|
||||
if (sig_array_node && plist_get_node_type(sig_array_node) == PLIST_ARRAY &&
|
||||
plist_array_get_size(sig_array_node) > 0) {
|
||||
plist_t sig_data_node = plist_array_get_item(sig_array_node, 0);
|
||||
if (sig_data_node && plist_get_node_type(sig_data_node) == PLIST_DATA) {
|
||||
plist_get_data_val(sig_data_node, &mounted_sig, &mounted_sig_len);
|
||||
}
|
||||
}
|
||||
std::string mounted_sig_str(mounted_sig ? mounted_sig : "");
|
||||
free(mounted_sig);
|
||||
plist_free(result.second);
|
||||
if (mounted_sig_str.empty()) {
|
||||
return GetMountedImageResult{
|
||||
false, "", "No disk image mounted (No signature found)"};
|
||||
}
|
||||
return GetMountedImageResult{true, mounted_sig_str, "Success"};
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#ifndef DEVDISKMANAGER_H
|
||||
#define DEVDISKMANAGER_H
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include <QMap>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
class DevDiskManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DevDiskManager(QObject *parent = nullptr);
|
||||
static DevDiskManager *sharedInstance();
|
||||
|
||||
// TODO:public or private?
|
||||
// Image list management
|
||||
QNetworkReply *fetchImageList();
|
||||
GetImagesSortedFinalResult parseImageList(const QByteArray &jsonData,
|
||||
const QString &downloadPath,
|
||||
int deviceMajorVersion = 0,
|
||||
int deviceMinorVersion = 0,
|
||||
const char *mounted_sig = nullptr,
|
||||
uint64_t mounted_sig_len = 0);
|
||||
QList<ImageInfo> getAllImages() const;
|
||||
|
||||
// Download management
|
||||
QPair<QNetworkReply *, QNetworkReply *>
|
||||
downloadImage(const QString &version);
|
||||
bool isImageDownloaded(const QString &version,
|
||||
const QString &downloadPath) const;
|
||||
|
||||
// Mount operations
|
||||
|
||||
bool mountImage(const QString &version, const QString &udid,
|
||||
const QString &downloadPath);
|
||||
bool unmountImage();
|
||||
|
||||
// Signature comparison
|
||||
bool compareSignatures(const char *signature_file_path,
|
||||
const char *mounted_sig, uint64_t mounted_sig_len);
|
||||
|
||||
QByteArray getImageListData() const { return m_imageListJsonData; }
|
||||
GetMountedImageResult getMountedImage(const char *udid);
|
||||
bool mountCompatibleImage(iDescriptorDevice *device,
|
||||
const QString &downloadPath);
|
||||
|
||||
signals:
|
||||
void imageListFetched(bool success,
|
||||
const QString &errorMessage = QString());
|
||||
void imageDownloadProgress(const QString &version, int percentage);
|
||||
void imageDownloadFinished(const QString &version, bool success,
|
||||
const QString &errorMessage = QString());
|
||||
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onFileDownloadFinished();
|
||||
|
||||
private:
|
||||
struct DownloadItem {
|
||||
QString version;
|
||||
QString downloadPath;
|
||||
QNetworkReply *dmgReply = nullptr;
|
||||
QNetworkReply *sigReply = nullptr;
|
||||
qint64 dmgReceived = 0;
|
||||
qint64 sigReceived = 0;
|
||||
qint64 totalSize = 0;
|
||||
};
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QByteArray m_imageListJsonData;
|
||||
QMap<QString, ImageInfo> m_availableImages;
|
||||
QMap<QNetworkReply *, DownloadItem *> m_activeDownloads;
|
||||
|
||||
void sortVersions(GetImagesSortedResult &sortedResult);
|
||||
|
||||
QMap<QString, QMap<QString, QString>> parseDiskDir();
|
||||
// TODO:move this to header
|
||||
bool m_isImageListReady = false;
|
||||
GetImagesSortedResult
|
||||
getImagesSorted(QMap<QString, QMap<QString, QString>> imageFiles,
|
||||
int deviceMajorVersion, int deviceMinorVersion,
|
||||
const QString &downloadPath, const char *mounted_sig,
|
||||
uint64_t mounted_sig_len);
|
||||
bool mountCompatibleImageInternal(iDescriptorDevice *device,
|
||||
const QString &downloadPath);
|
||||
};
|
||||
|
||||
#endif // DEVDISKMANAGER_H
|
||||
Reference in New Issue
Block a user