mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
add DevDiskImagesWidget for managing developer disk images
This commit is contained in:
@@ -89,7 +89,7 @@ static ssize_t mim_upload_cb(void *buf, size_t size, void *userdata)
|
||||
return fread(buf, 1, size, (FILE *)userdata);
|
||||
}
|
||||
// TODO: cleanup
|
||||
bool mount_dev_image(char *udid)
|
||||
bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
{
|
||||
mobile_image_mounter_client_t mim = NULL;
|
||||
int res = -1;
|
||||
@@ -137,16 +137,14 @@ bool mount_dev_image(char *udid)
|
||||
char *image_path = nullptr;
|
||||
char *image_sig_path = nullptr;
|
||||
|
||||
if (asprintf(&image_path,
|
||||
"%s/resources/dev-images/15/DeveloperDiskImage.dmg",
|
||||
SOURCE_DIR) < 0) {
|
||||
if (asprintf(&image_path, "%s/DeveloperDiskImage.dmg", image_dir_path) <
|
||||
0) {
|
||||
qDebug() << "Out of memory constructing image path!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (asprintf(&image_sig_path,
|
||||
"%s/resources/dev-images/15/DeveloperDiskImage.dmg.signature",
|
||||
SOURCE_DIR) < 0) {
|
||||
if (asprintf(&image_sig_path, "%s/DeveloperDiskImage.dmg.signature",
|
||||
image_dir_path) < 0) {
|
||||
qDebug() << "Out of memory constructing signature path!";
|
||||
free(image_path);
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
#include "devdiskimageswidget.h"
|
||||
#include "appcontext.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
// 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);
|
||||
|
||||
DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}, m_networkManager(new QNetworkAccessManager(this)),
|
||||
m_currentDevice(device)
|
||||
{
|
||||
setupUi();
|
||||
fetchImageList();
|
||||
updateDeviceList();
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
|
||||
&DevDiskImagesWidget::updateDeviceList);
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
|
||||
&DevDiskImagesWidget::updateDeviceList);
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::setupUi()
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
|
||||
auto *pathLayout = new QHBoxLayout();
|
||||
pathLayout->addWidget(new QLabel(tr("Download Path:")));
|
||||
m_downloadPathEdit = new QLineEdit();
|
||||
m_downloadPathEdit->setReadOnly(true);
|
||||
pathLayout->addWidget(m_downloadPathEdit);
|
||||
auto *changeDirButton = new QPushButton(tr("Change..."));
|
||||
connect(changeDirButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::changeDownloadDirectory);
|
||||
pathLayout->addWidget(changeDirButton);
|
||||
layout->addLayout(pathLayout);
|
||||
|
||||
auto *mountLayout = new QHBoxLayout();
|
||||
mountLayout->addWidget(new QLabel(tr("Device:")));
|
||||
m_deviceComboBox = new QComboBox(this);
|
||||
mountLayout->addWidget(m_deviceComboBox);
|
||||
m_mountButton = new QPushButton(tr("Mount"), this);
|
||||
connect(m_mountButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::onMountButtonClicked);
|
||||
mountLayout->addWidget(m_mountButton);
|
||||
layout->addLayout(mountLayout);
|
||||
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
layout->addWidget(m_stackedWidget);
|
||||
|
||||
m_statusLabel = new QLabel(tr("Fetching image list..."));
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_stackedWidget->addWidget(m_statusLabel);
|
||||
|
||||
m_imageListWidget = new QListWidget(this);
|
||||
m_stackedWidget->addWidget(m_imageListWidget);
|
||||
|
||||
m_downloadPath =
|
||||
QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages");
|
||||
m_downloadPathEdit->setText(m_downloadPath);
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::fetchImageList()
|
||||
{
|
||||
m_stackedWidget->setCurrentWidget(m_statusLabel);
|
||||
m_statusLabel->setText(tr("Fetching image list..."));
|
||||
|
||||
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); });
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onImageListFetchFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
m_statusLabel->setText(
|
||||
tr("Error fetching image list: %1").arg(reply->errorString()));
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
m_imageListJsonData = data;
|
||||
parseAndDisplayImages(m_imageListJsonData);
|
||||
m_stackedWidget->setCurrentWidget(m_imageListWidget);
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData)
|
||||
{
|
||||
m_imageListWidget->clear();
|
||||
m_availableImages.clear();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
||||
if (!doc.isObject()) {
|
||||
m_statusLabel->setText(tr("Failed to parse JSON response."));
|
||||
m_stackedWidget->setCurrentWidget(m_statusLabel);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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"]};
|
||||
|
||||
auto *itemWidget = new QWidget();
|
||||
auto *itemLayout = new QHBoxLayout(itemWidget);
|
||||
itemLayout->addWidget(new QLabel(version));
|
||||
|
||||
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();
|
||||
progressBar->setVisible(false);
|
||||
itemLayout->addWidget(progressBar);
|
||||
|
||||
auto *downloadButton =
|
||||
new QPushButton(exists ? tr("Re-download") : tr("Download"));
|
||||
downloadButton->setProperty("version", version);
|
||||
connect(downloadButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::onDownloadButtonClicked);
|
||||
itemLayout->addWidget(downloadButton);
|
||||
|
||||
auto *listItem = new QListWidgetItem(m_imageListWidget);
|
||||
listItem->setSizeHint(itemWidget->sizeHint());
|
||||
m_imageListWidget->addItem(listItem);
|
||||
m_imageListWidget->setItemWidget(listItem, itemWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onDownloadButtonClicked()
|
||||
{
|
||||
auto *button = qobject_cast<QPushButton *>(sender());
|
||||
if (!button)
|
||||
return;
|
||||
|
||||
QString version = button->property("version").toString();
|
||||
|
||||
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?")
|
||||
.arg(version),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startDownload(version);
|
||||
}
|
||||
|
||||
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;
|
||||
for (int i = 0; i < m_imageListWidget->count(); ++i) {
|
||||
auto *item = m_imageListWidget->item(i);
|
||||
auto *widget = m_imageListWidget->itemWidget(item);
|
||||
auto *button = widget->findChild<QPushButton *>();
|
||||
if (button && button->property("version") == version) {
|
||||
downloadButton = button;
|
||||
progressBar = widget->findChild<QProgressBar *>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!downloadButton || !progressBar)
|
||||
return;
|
||||
|
||||
downloadButton->setEnabled(false);
|
||||
progressBar->setVisible(true);
|
||||
progressBar->setValue(0);
|
||||
|
||||
QString targetDir = QDir(m_downloadPath).filePath(version);
|
||||
if (!QDir().mkpath(targetDir)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Could not create directory: %1").arg(targetDir));
|
||||
downloadButton->setEnabled(true);
|
||||
progressBar->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto *downloadItem = new DownloadItem();
|
||||
downloadItem->version = version;
|
||||
downloadItem->progressBar = progressBar;
|
||||
downloadItem->downloadButton = downloadButton;
|
||||
|
||||
QString dmgPath = m_availableImages[version].first;
|
||||
QString sigPath = m_availableImages[version].second;
|
||||
|
||||
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,
|
||||
&DevDiskImagesWidget::onFileDownloadFinished);
|
||||
|
||||
m_activeDownloads[downloadItem->dmgReply] = downloadItem;
|
||||
m_activeDownloads[downloadItem->sigReply] = downloadItem;
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::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;
|
||||
}
|
||||
|
||||
item->totalReceived = item->dmgReceived + item->sigReceived;
|
||||
|
||||
if (item->totalSize > 0) {
|
||||
item->progressBar->setValue((item->totalReceived * 100) /
|
||||
item->totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::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) {
|
||||
QMessageBox::critical(this, tr("Download Error"),
|
||||
tr("Failed to download %1: %2")
|
||||
.arg(reply->url().path())
|
||||
.arg(reply->errorString()));
|
||||
|
||||
if (reply == item->dmgReply && item->sigReply)
|
||||
item->sigReply->abort();
|
||||
if (reply == item->sigReply && item->dmgReply)
|
||||
item->dmgReply->abort();
|
||||
|
||||
item->downloadButton->setEnabled(true);
|
||||
item->downloadButton->setText(tr("Retry"));
|
||||
item->progressBar->setVisible(false);
|
||||
|
||||
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();
|
||||
QString targetPath =
|
||||
QDir(QDir(m_downloadPath).filePath(item->version)).filePath(filename);
|
||||
|
||||
QFile file(targetPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::critical(this, tr("File Error"),
|
||||
tr("Could not save file: %1").arg(targetPath));
|
||||
} else {
|
||||
file.write(reply->readAll());
|
||||
file.close();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (m_activeDownloads.key(item) == nullptr) { // Both files downloaded
|
||||
item->downloadButton->setText(tr("Downloaded"));
|
||||
item->downloadButton->setEnabled(false);
|
||||
item->progressBar->setValue(100);
|
||||
item->progressBar->setVisible(false);
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::updateDeviceList()
|
||||
{
|
||||
QString currentUdid;
|
||||
if (m_deviceComboBox->count() > 0 &&
|
||||
m_deviceComboBox->currentIndex() >= 0) {
|
||||
currentUdid = m_deviceComboBox->currentData().toString();
|
||||
}
|
||||
|
||||
m_deviceComboBox->clear();
|
||||
|
||||
auto devices = AppContext::sharedInstance()->getAllDevices();
|
||||
int newIndex = -1;
|
||||
for (int i = 0; i < devices.size(); ++i) {
|
||||
auto *device = devices.at(i);
|
||||
m_deviceComboBox->addItem(
|
||||
QString("%1 (%2)")
|
||||
.arg(QString::fromStdString(device->deviceInfo.deviceName))
|
||||
.arg(QString::fromStdString(device->udid)),
|
||||
QString::fromStdString(device->udid));
|
||||
if (QString().fromStdString((device->udid)) == currentUdid) {
|
||||
newIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex != -1) {
|
||||
m_deviceComboBox->setCurrentIndex(newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::onMountButtonClicked()
|
||||
{
|
||||
if (m_deviceComboBox->currentIndex() < 0) {
|
||||
QMessageBox::warning(
|
||||
this, tr("No Device"),
|
||||
tr("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."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto *widget = m_imageListWidget->itemWidget(currentItem);
|
||||
auto *button = widget->findChild<QPushButton *>();
|
||||
if (!button)
|
||||
return;
|
||||
|
||||
QString version = button->property("version").toString();
|
||||
|
||||
mountImage(version);
|
||||
}
|
||||
|
||||
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."));
|
||||
return;
|
||||
}
|
||||
|
||||
QString versionPath = QDir(m_downloadPath).filePath(version);
|
||||
if (!QDir(versionPath).exists()) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Image Not Found"),
|
||||
tr("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..."));
|
||||
|
||||
bool success = mount_dev_image(udid.toUtf8().constData(),
|
||||
versionPath.toUtf8().constData());
|
||||
|
||||
m_mountButton->setEnabled(true);
|
||||
m_mountButton->setText(tr("Mount"));
|
||||
|
||||
if (success) {
|
||||
QMessageBox::information(this, tr("Success"),
|
||||
tr("Image mounted successfully on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Mount Failed"),
|
||||
tr("Failed to mount image on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::changeDownloadDirectory()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef DEVDISKIMAGESWIDGET_H
|
||||
#define DEVDISKIMAGESWIDGET_H
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include <QMap>
|
||||
#include <QWidget>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QListWidget;
|
||||
class QStackedWidget;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QProgressBar;
|
||||
class QComboBox;
|
||||
|
||||
class DevDiskImagesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DevDiskImagesWidget(iDescriptorDevice *device,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void fetchImageList();
|
||||
void onImageListFetchFinished(QNetworkReply *reply);
|
||||
void onDownloadButtonClicked();
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onFileDownloadFinished();
|
||||
void changeDownloadDirectory();
|
||||
void updateDeviceList();
|
||||
void onMountButtonClicked();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void parseAndDisplayImages(const QByteArray &jsonData);
|
||||
void startDownload(const QString &version);
|
||||
void mountImage(const QString &version);
|
||||
|
||||
struct DownloadItem {
|
||||
QNetworkReply *dmgReply = nullptr;
|
||||
QNetworkReply *sigReply = nullptr;
|
||||
QProgressBar *progressBar = nullptr;
|
||||
QPushButton *downloadButton = nullptr;
|
||||
QString version;
|
||||
qint64 totalSize = 0;
|
||||
qint64 totalReceived = 0;
|
||||
qint64 dmgReceived = 0;
|
||||
qint64 sigReceived = 0;
|
||||
};
|
||||
|
||||
QStackedWidget *m_stackedWidget;
|
||||
QListWidget *m_imageListWidget;
|
||||
QLabel *m_statusLabel;
|
||||
QLineEdit *m_downloadPathEdit;
|
||||
QComboBox *m_deviceComboBox;
|
||||
QPushButton *m_mountButton;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_downloadPath;
|
||||
iDescriptorDevice *m_currentDevice;
|
||||
|
||||
QMap<QString, QPair<QString, QString>>
|
||||
m_availableImages; // version -> {dmg_path, sig_path}
|
||||
QMap<QNetworkReply *, DownloadItem *> m_activeDownloads;
|
||||
QByteArray m_imageListJsonData;
|
||||
};
|
||||
|
||||
#endif // DEVDISKIMAGESWIDGET_H
|
||||
+31
-14
@@ -3,6 +3,7 @@
|
||||
#include "./core/services/restart.cpp"
|
||||
#include "airplaywindow.h"
|
||||
#include "appcontext.h"
|
||||
#include "devdiskimageswidget.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "querymobilegestaltwidget.h"
|
||||
#include "realtimescreen.h"
|
||||
@@ -132,6 +133,10 @@ void ToolboxWidget::setupUI()
|
||||
createToolbox("Enter Recovery Mode", "Enter device recovery mode",
|
||||
"SP_DialogOkButton", true);
|
||||
|
||||
QWidget *devDiskImages =
|
||||
createToolbox("Developer Disk Images", "Manage developer disk images",
|
||||
"SP_DialogOkButton", false);
|
||||
|
||||
// Add toolboxes to grid (3 columns)
|
||||
m_gridLayout->addWidget(airplayerBox, 0, 0);
|
||||
m_gridLayout->addWidget(virtualLocationBox, 0, 1);
|
||||
@@ -145,6 +150,7 @@ void ToolboxWidget::setupUI()
|
||||
m_gridLayout->addWidget(enterRecoveryMode, 3, 0);
|
||||
m_gridLayout->addWidget(mountDevImage, 3, 1);
|
||||
m_gridLayout->addWidget(unmountDevImage, 3, 2);
|
||||
m_gridLayout->addWidget(devDiskImages, 4, 0, 1, 3);
|
||||
|
||||
m_gridLayout->setRowStretch(3, 1);
|
||||
|
||||
@@ -161,9 +167,10 @@ QWidget *ToolboxWidget::createToolbox(const QString &title,
|
||||
bool requiresDevice)
|
||||
{
|
||||
QFrame *frame = new QFrame();
|
||||
frame->setObjectName("toolboxFrame");
|
||||
frame->setFrameStyle(QFrame::Box);
|
||||
frame->setStyleSheet(
|
||||
"QFrame { border: 1px solid #ccc; border-radius: 5px; padding: 5px; }");
|
||||
frame->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; "
|
||||
"border-radius: 5px; padding: 5px; }");
|
||||
frame->setFixedSize(200, 120);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(frame);
|
||||
@@ -259,11 +266,12 @@ void ToolboxWidget::updateToolboxStates()
|
||||
toolbox->setEnabled(enabled);
|
||||
|
||||
if (enabled) {
|
||||
toolbox->setStyleSheet("QFrame { border: 1px solid #ccc; "
|
||||
toolbox->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; "
|
||||
"border-radius: 5px; padding: 5px; }");
|
||||
} else {
|
||||
toolbox->setStyleSheet(
|
||||
"QFrame { border: 1px solid #ccc; border-radius: 5px; padding: "
|
||||
"#toolboxFrame { border: 1px solid #ccc; border-radius: 5px; "
|
||||
"padding: "
|
||||
"5px; background-color: #f0f0f0; color: #999; }");
|
||||
}
|
||||
}
|
||||
@@ -323,16 +331,17 @@ void ToolboxWidget::onToolboxClicked(const QString &toolName)
|
||||
msgBox.exec();
|
||||
} else if (toolName == "Mount Dev Image") {
|
||||
// Handle mounting device image
|
||||
bool success =
|
||||
mount_dev_image(const_cast<char *>(m_currentDevice->udid.c_str()));
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Mount Dev Image");
|
||||
if (success) {
|
||||
msgBox.setText("Successfully mounted device image.");
|
||||
} else {
|
||||
msgBox.setText("Failed to mount device image.");
|
||||
}
|
||||
msgBox.exec();
|
||||
// bool success =
|
||||
// mount_dev_image(const_cast<char
|
||||
// *>(m_currentDevice->udid.c_str()));
|
||||
// QMessageBox msgBox;
|
||||
// msgBox.setWindowTitle("Mount Dev Image");
|
||||
// if (success) {
|
||||
// msgBox.setText("Successfully mounted device image.");
|
||||
// } else {
|
||||
// msgBox.setText("Failed to mount device image.");
|
||||
// }
|
||||
// msgBox.exec();
|
||||
} else if (toolName == "Virtual Location") {
|
||||
// Handle virtual location functionality
|
||||
VirtualLocation *virtualLocation = new VirtualLocation(m_currentDevice);
|
||||
@@ -369,6 +378,14 @@ void ToolboxWidget::onToolboxClicked(const QString &toolName)
|
||||
QueryMobileGestaltWidget *queryMobileGestaltWidget =
|
||||
new QueryMobileGestaltWidget();
|
||||
queryMobileGestaltWidget->show();
|
||||
} else if (toolName == "Developer Disk Images") {
|
||||
// Handle developer disk images
|
||||
DevDiskImagesWidget *devDiskImagesWidget =
|
||||
new DevDiskImagesWidget(m_currentDevice);
|
||||
devDiskImagesWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
devDiskImagesWidget->setWindowFlag(Qt::Window);
|
||||
devDiskImagesWidget->resize(800, 600);
|
||||
devDiskImagesWidget->show();
|
||||
}
|
||||
// Implement specific tool functionality here
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user