mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
integrate ipatool fork and enhance download functionality
- Added support for downloading apps using the ipatool-go library. - Updated AppDownloadBaseDialog to handle download progress and success notifications. - Simplified startDownloadProcess method to accept bundleId directly. - Introduced updateProgressBar slot for updating UI during downloads. - Modified AppsWidget to utilize asynchronous search and download processes. - Updated CMakeLists.txt to include new library and dependencies. - Add gallerywidget.cpp and gallerywidget.h to be implemented later
This commit is contained in:
Vendored
+1
@@ -7,6 +7,7 @@
|
||||
"/usr/include/qt/**",
|
||||
"/usr/local/opt/qt/**",
|
||||
"${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include",
|
||||
"${workspaceFolder}/build/Desktop-Debug/src/lib/**",
|
||||
"/usr/local/include/**",
|
||||
"/usr/include/python3.13"
|
||||
],
|
||||
|
||||
Vendored
+159
-155
@@ -1,156 +1,160 @@
|
||||
{
|
||||
"files.associations": {
|
||||
".env*": "dotenv",
|
||||
"stdio.h": "c",
|
||||
"string.h": "c",
|
||||
"stdlib.h": "c",
|
||||
"_malloc.h": "c",
|
||||
"*.mdx": "markdown",
|
||||
"*.scss": "tailwindcss",
|
||||
"*.x": "objective-c",
|
||||
"libimobiledevice.h": "c",
|
||||
"clay.h": "c",
|
||||
"sdl_main.h": "c",
|
||||
"sdl.h": "c",
|
||||
"sdl_ttf.h": "c",
|
||||
"sdl_image.h": "c",
|
||||
"iostream": "cpp",
|
||||
"fstream": "cpp",
|
||||
"__bit_reference": "cpp",
|
||||
"__config": "cpp",
|
||||
"__locale": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__threading_support": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"array": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"complex": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"execution": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"ios": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"locale": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"tuple": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp",
|
||||
"vector": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"__hash_table": "cpp",
|
||||
"__node_handle": "cpp",
|
||||
"__tree": "cpp",
|
||||
"any": "cpp",
|
||||
"charconv": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"map": "cpp",
|
||||
"optional": "cpp",
|
||||
"span": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"valarray": "cpp",
|
||||
"*.moc": "cpp",
|
||||
"unistd.h": "c",
|
||||
"shared_mutex": "c",
|
||||
"mobile_image_mounter.h": "c",
|
||||
"service.h": "c",
|
||||
"property_list_service.h": "c",
|
||||
"memory": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"deque": "cpp",
|
||||
"future": "cpp",
|
||||
"latch": "cpp",
|
||||
"list": "cpp",
|
||||
"numbers": "cpp",
|
||||
"print": "cpp",
|
||||
"queue": "cpp",
|
||||
"regex": "cpp",
|
||||
"set": "cpp",
|
||||
"source_location": "cpp",
|
||||
"stack": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"chrono": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"csignal": "cpp",
|
||||
"exception": "cpp",
|
||||
"expected": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"random": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"ranges": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"stdfloat": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"thread": "cpp",
|
||||
"hash_map": "cpp",
|
||||
"hash_set": "cpp",
|
||||
"qvboxlayout": "cpp",
|
||||
"qsvgrenderer": "cpp",
|
||||
"printf.h": "c",
|
||||
"sha.h": "c",
|
||||
"qwidget": "cpp",
|
||||
"qtabbar": "cpp",
|
||||
"qhboxlayout": "cpp",
|
||||
"qpushbutton": "cpp",
|
||||
"qtabwidget": "cpp",
|
||||
"qstring": "cpp",
|
||||
"qdbusconnection": "cpp",
|
||||
"qdbusmessage": "cpp",
|
||||
"qmainwindow": "cpp",
|
||||
"qlineedit": "cpp",
|
||||
"qscrollarea": "cpp",
|
||||
"qlabel": "cpp",
|
||||
"qdebug": "cpp",
|
||||
"qobject": "cpp",
|
||||
"qspaceritem": "cpp",
|
||||
"qmessagebox": "cpp",
|
||||
"qtextstream": "cpp",
|
||||
"qvariant": "cpp",
|
||||
"qjsondocument": "cpp",
|
||||
"qjsonobject": "cpp",
|
||||
"qtimer": "cpp",
|
||||
"qprogressbar": "cpp",
|
||||
"qdialogbuttonbox": "cpp",
|
||||
"qgridlayout": "cpp",
|
||||
"qdir": "cpp",
|
||||
"qstandardpaths": "cpp",
|
||||
"qapplication": "cpp",
|
||||
"qfileinfo": "cpp"
|
||||
}
|
||||
}
|
||||
"files.associations": {
|
||||
".env*": "dotenv",
|
||||
"stdio.h": "c",
|
||||
"string.h": "c",
|
||||
"stdlib.h": "c",
|
||||
"_malloc.h": "c",
|
||||
"*.mdx": "markdown",
|
||||
"*.scss": "tailwindcss",
|
||||
"*.x": "objective-c",
|
||||
"libimobiledevice.h": "c",
|
||||
"clay.h": "c",
|
||||
"sdl_main.h": "c",
|
||||
"sdl.h": "c",
|
||||
"sdl_ttf.h": "c",
|
||||
"sdl_image.h": "c",
|
||||
"iostream": "cpp",
|
||||
"fstream": "cpp",
|
||||
"__bit_reference": "cpp",
|
||||
"__config": "cpp",
|
||||
"__locale": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__threading_support": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"array": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"complex": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"execution": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"ios": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"locale": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"tuple": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp",
|
||||
"vector": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"__hash_table": "cpp",
|
||||
"__node_handle": "cpp",
|
||||
"__tree": "cpp",
|
||||
"any": "cpp",
|
||||
"charconv": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"map": "cpp",
|
||||
"optional": "cpp",
|
||||
"span": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"valarray": "cpp",
|
||||
"*.moc": "cpp",
|
||||
"unistd.h": "c",
|
||||
"shared_mutex": "c",
|
||||
"mobile_image_mounter.h": "c",
|
||||
"service.h": "c",
|
||||
"property_list_service.h": "c",
|
||||
"memory": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"deque": "cpp",
|
||||
"future": "cpp",
|
||||
"latch": "cpp",
|
||||
"list": "cpp",
|
||||
"numbers": "cpp",
|
||||
"print": "cpp",
|
||||
"queue": "cpp",
|
||||
"regex": "cpp",
|
||||
"set": "cpp",
|
||||
"source_location": "cpp",
|
||||
"stack": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"chrono": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"csignal": "cpp",
|
||||
"exception": "cpp",
|
||||
"expected": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"random": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"ranges": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"stdfloat": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"thread": "cpp",
|
||||
"hash_map": "cpp",
|
||||
"hash_set": "cpp",
|
||||
"qvboxlayout": "cpp",
|
||||
"qsvgrenderer": "cpp",
|
||||
"printf.h": "c",
|
||||
"sha.h": "c",
|
||||
"qwidget": "cpp",
|
||||
"qtabbar": "cpp",
|
||||
"qhboxlayout": "cpp",
|
||||
"qpushbutton": "cpp",
|
||||
"qtabwidget": "cpp",
|
||||
"qstring": "cpp",
|
||||
"qdbusconnection": "cpp",
|
||||
"qdbusmessage": "cpp",
|
||||
"qmainwindow": "cpp",
|
||||
"qlineedit": "cpp",
|
||||
"qscrollarea": "cpp",
|
||||
"qlabel": "cpp",
|
||||
"qdebug": "cpp",
|
||||
"qobject": "cpp",
|
||||
"qspaceritem": "cpp",
|
||||
"qmessagebox": "cpp",
|
||||
"qtextstream": "cpp",
|
||||
"qvariant": "cpp",
|
||||
"qjsondocument": "cpp",
|
||||
"qjsonobject": "cpp",
|
||||
"qtimer": "cpp",
|
||||
"qprogressbar": "cpp",
|
||||
"qdialogbuttonbox": "cpp",
|
||||
"qgridlayout": "cpp",
|
||||
"qdir": "cpp",
|
||||
"qstandardpaths": "cpp",
|
||||
"qapplication": "cpp",
|
||||
"qfileinfo": "cpp",
|
||||
"qcoreapplication": "cpp",
|
||||
"qdialog": "cpp",
|
||||
"qcombobox": "cpp",
|
||||
"qtconcurrent": "cpp"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ file(GLOB PROJECT_SOURCES
|
||||
)
|
||||
|
||||
add_subdirectory(src/core/airplay)
|
||||
add_subdirectory(src/lib/ipatool-go)
|
||||
|
||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
@@ -129,6 +130,7 @@ find_package(Qt6 REQUIRED COMPONENTS Quick)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Location)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Positioning)
|
||||
find_package(Qt6 REQUIRED COMPONENTS QuickWidgets)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
|
||||
|
||||
qt_add_executable(iDescriptor
|
||||
@@ -182,7 +184,9 @@ target_link_libraries(iDescriptor PRIVATE
|
||||
PkgConfig::PLIST
|
||||
PkgConfig::QRENCODE
|
||||
airplay
|
||||
ipatool-go
|
||||
)
|
||||
target_link_libraries(iDescriptor PRIVATE Qt6::Widgets)
|
||||
|
||||
# Add compile definition for source directory
|
||||
target_compile_definitions(iDescriptor PRIVATE
|
||||
|
||||
+81
-130
@@ -1,16 +1,39 @@
|
||||
#include "appdownloadbasedialog.h"
|
||||
#include "libipatool-go.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
void downloadProgressCallback(long long current, long long total,
|
||||
void *userData)
|
||||
{
|
||||
// Cast the user data back to the dialog instance.
|
||||
AppDownloadBaseDialog *dialog =
|
||||
static_cast<AppDownloadBaseDialog *>(userData);
|
||||
if (dialog) {
|
||||
int percentage = 0;
|
||||
if (total > 0) {
|
||||
percentage = static_cast<int>((current * 100) / total);
|
||||
}
|
||||
// Safely call the update method on the GUI thread.
|
||||
QMetaObject::invokeMethod(dialog, "updateProgressBar",
|
||||
Qt::QueuedConnection, Q_ARG(int, percentage));
|
||||
}
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::updateProgressBar(int percentage)
|
||||
{
|
||||
|
||||
if (m_progressBar) {
|
||||
m_progressBar->setValue(percentage);
|
||||
}
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::addProgressBar(int index)
|
||||
{
|
||||
@@ -43,146 +66,74 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
|
||||
m_actionButton = nullptr; // Derived classes set this
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::startDownloadProcess(const QStringList &args,
|
||||
void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
|
||||
const QString &outputDir,
|
||||
int index)
|
||||
{
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
QString timestamp =
|
||||
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
|
||||
QString logFilePath =
|
||||
QDir::temp().filePath(QString("%1_%2.log").arg(m_appName, timestamp));
|
||||
bool acquireLicense = true;
|
||||
|
||||
QFile *logFile = new QFile(logFilePath);
|
||||
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(this, "Error",
|
||||
"Failed to open log file for writing.");
|
||||
if (bundleId.isEmpty()) {
|
||||
QMessageBox::critical(this, "Error", "Bundle ID not provided.");
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
logFile->close();
|
||||
|
||||
m_downloadProcess = new QProcess(this);
|
||||
// m_downloadProcess->setProcessEnvironment(env);
|
||||
// m_downloadProcess->setWorkingDirectory(workingDir);
|
||||
|
||||
m_downloadProcess->setStandardOutputFile(logFilePath, QIODevice::Append);
|
||||
m_downloadProcess->setStandardErrorFile(logFilePath, QIODevice::Append);
|
||||
m_downloadProcess->start("ipatool", args);
|
||||
// TODO: handle errors
|
||||
addProgressBar(index);
|
||||
|
||||
m_progressTimer = new QTimer(this);
|
||||
connect(m_progressTimer, &QTimer::timeout, this,
|
||||
[this, logFilePath, outputDir]() {
|
||||
checkDownloadProgress(logFilePath, m_appName, outputDir);
|
||||
});
|
||||
|
||||
connect(m_downloadProcess,
|
||||
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
|
||||
[this, logFilePath, outputDir](int exitCode,
|
||||
QProcess::ExitStatus exitStatus) {
|
||||
checkDownloadProgress(logFilePath, m_appName, outputDir);
|
||||
m_progressTimer->stop();
|
||||
m_progressTimer->deleteLater();
|
||||
m_downloadProcess->deleteLater();
|
||||
// m_progressBar->setValue(100);
|
||||
// QMessageBox::information(
|
||||
// this, "Download Complete",
|
||||
// QString("Successfully downloaded
|
||||
// %1.").arg(m_appName));
|
||||
// accept();
|
||||
// } else {
|
||||
// QMessageBox::critical(
|
||||
// this, "Download Failed",
|
||||
// QString("Failed to download %1. Exit code: %2")
|
||||
// .arg(m_appName)
|
||||
// .arg(exitCode));
|
||||
// reject();
|
||||
// }
|
||||
});
|
||||
|
||||
m_progressTimer->start(1000);
|
||||
|
||||
if (m_actionButton)
|
||||
m_actionButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void AppDownloadBaseDialog::checkDownloadProgress(const QString &logFilePath,
|
||||
const QString &appName,
|
||||
const QString &outputDir)
|
||||
{
|
||||
QFile logFile(logFilePath);
|
||||
if (!logFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return;
|
||||
// // C-style callback function for progress updates from the Go library
|
||||
// auto progressCallback = [this](long long current, long long total) {
|
||||
|
||||
QString fileContents = QString::fromUtf8(logFile.readAll());
|
||||
logFile.close();
|
||||
// // Use invokeMethod to call a slot on the GUI thread safely
|
||||
// // QMetaObject::invokeMethod(this, "updateProgressBar",
|
||||
// // Qt::QueuedConnection, Q_ARG(int,
|
||||
// // percentage));
|
||||
// updateProgressBar(percentage);
|
||||
// };
|
||||
|
||||
int jsonStart = fileContents.indexOf('{');
|
||||
qDebug() << "JSON Start:" << jsonStart;
|
||||
if (jsonStart != -1) {
|
||||
QString jsonString = fileContents.mid(jsonStart);
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc =
|
||||
QJsonDocument::fromJson(jsonString.toUtf8(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
qDebug() << "Parsed JSON successfully";
|
||||
QJsonObject jsonObj = doc.object();
|
||||
QString level = jsonObj.value("level").toString();
|
||||
bool success = jsonObj.value("success").toBool();
|
||||
QFuture<int> future =
|
||||
QtConcurrent::run([bundleId, outputDir, acquireLicense, this]() {
|
||||
// Call the Go function directly
|
||||
return IpaToolDownloadApp(
|
||||
bundleId.toUtf8().data(), outputDir.toUtf8().data(), "",
|
||||
acquireLicense, downloadProgressCallback, this);
|
||||
});
|
||||
|
||||
m_progressTimer->stop();
|
||||
// if (m_actionButton)
|
||||
// m_actionButton->setEnabled(true);
|
||||
QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);
|
||||
connect(watcher, &QFutureWatcher<int>::finished, this,
|
||||
[this, watcher, outputDir]() {
|
||||
int result = watcher->result();
|
||||
watcher->deleteLater();
|
||||
|
||||
if (level == "error") {
|
||||
QString errorMsg = jsonObj.contains("error")
|
||||
? jsonObj.value("error").toString()
|
||||
: "Unknown error";
|
||||
QMessageBox::critical(
|
||||
this, "Download Failed",
|
||||
QString("Failed to download %1. Error: %2")
|
||||
.arg(m_appName, errorMsg));
|
||||
reject();
|
||||
return;
|
||||
} else if (level == "info" && success) {
|
||||
m_progressBar->setValue(100);
|
||||
// QMessageBox::information(
|
||||
// this, "Download Successful",
|
||||
// QString("Successfully downloaded %1. Would you like to "
|
||||
// "open the directory?")
|
||||
// .arg(m_appName));
|
||||
if (QMessageBox::Yes ==
|
||||
QMessageBox::question(
|
||||
this, "Open Directory",
|
||||
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 (result == 0) { // 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));
|
||||
}
|
||||
}
|
||||
accept();
|
||||
} else { // Failure
|
||||
QMessageBox::critical(
|
||||
this, "Download Failed",
|
||||
QString("Failed to download %1. Error code: %2")
|
||||
.arg(m_appName)
|
||||
.arg(result));
|
||||
reject();
|
||||
}
|
||||
accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRegularExpression re(R"(downloading\s+(\d+)%\s+\|)");
|
||||
QRegularExpressionMatchIterator i = re.globalMatch(fileContents);
|
||||
int percent = -1;
|
||||
while (i.hasNext()) {
|
||||
QRegularExpressionMatch match = i.next();
|
||||
percent = match.captured(1).toInt();
|
||||
}
|
||||
if (percent != -1) {
|
||||
m_progressBar->setValue(percent);
|
||||
}
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
@@ -14,8 +14,11 @@ public:
|
||||
explicit AppDownloadBaseDialog(const QString &appName,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateProgressBar(int percentage);
|
||||
|
||||
protected:
|
||||
void startDownloadProcess(const QStringList &args,
|
||||
void startDownloadProcess(const QString &bundleId,
|
||||
const QString &workingDir, int index);
|
||||
void checkDownloadProgress(const QString &logFilePath,
|
||||
const QString &appName,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "appdownloaddialog.h"
|
||||
#include "clickablelabel.h"
|
||||
#include "libipatool-go.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
@@ -82,15 +83,5 @@ void AppDownloadDialog::onDownloadClicked()
|
||||
layout()->removeWidget(m_actionButton);
|
||||
m_actionButton->deleteLater();
|
||||
|
||||
QStringList args = {"download",
|
||||
"-b",
|
||||
m_bundleId,
|
||||
"-o",
|
||||
m_outputDir,
|
||||
"--purchase",
|
||||
"--keychain-passphrase",
|
||||
"iDescriptor",
|
||||
"--format",
|
||||
"json"};
|
||||
startDownloadProcess(args, m_outputDir, buttonIndex);
|
||||
startDownloadProcess(m_bundleId, m_outputDir, buttonIndex);
|
||||
}
|
||||
|
||||
+12
-12
@@ -112,22 +112,22 @@ void AppInstallDialog::onInstallClicked()
|
||||
}
|
||||
m_deviceCombo->setEnabled(false);
|
||||
QString selectedDevice = m_deviceCombo->currentData().toString();
|
||||
QStringList args = {"download",
|
||||
"-b",
|
||||
m_bundleId,
|
||||
"-o",
|
||||
"./",
|
||||
"--purchase",
|
||||
"--keychain-passphrase",
|
||||
"iDescriptor",
|
||||
"--format",
|
||||
"json"};
|
||||
// QStringList args = {"download",
|
||||
// "-b",
|
||||
// m_bundleId,
|
||||
// "-o",
|
||||
// "./",
|
||||
// "--purchase",
|
||||
// "--keychain-passphrase",
|
||||
// "iDescriptor",
|
||||
// "--format",
|
||||
// "json"};
|
||||
m_actionButton->setEnabled(false);
|
||||
|
||||
int buttonIndex = m_layout->indexOf(m_actionButton);
|
||||
layout()->removeWidget(m_actionButton);
|
||||
m_actionButton->deleteLater();
|
||||
m_actionButton = nullptr; // Reset to avoid double deletion
|
||||
|
||||
startDownloadProcess(args, QDir::currentPath(), buttonIndex);
|
||||
// TODO: need to implement the actual installation logic
|
||||
// startDownloadProcess(args, QDir::currentPath(), buttonIndex);
|
||||
}
|
||||
|
||||
+86
-77
@@ -3,23 +3,35 @@
|
||||
#include "appdownloadbasedialog.h"
|
||||
#include "appdownloaddialog.h"
|
||||
#include "appinstalldialog.h"
|
||||
#include "libipatool-go.h"
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFrame>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QImage>
|
||||
#include <QInputDialog>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPixmap>
|
||||
#include <QProcess>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
@@ -27,22 +39,32 @@
|
||||
#include <QStyle>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include <QAction>
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPixmap>
|
||||
#include <QUrl>
|
||||
// 2FA callback for login
|
||||
static char *getAuthCodeCallback()
|
||||
{
|
||||
static QByteArray buffer;
|
||||
QString code;
|
||||
QMetaObject::invokeMethod(
|
||||
qApp,
|
||||
[&]() {
|
||||
bool ok;
|
||||
code = QInputDialog::getText(
|
||||
nullptr, "Two-Factor Authentication",
|
||||
"Enter the 2FA code:", QLineEdit::Normal, QString(), &ok);
|
||||
},
|
||||
Qt::BlockingQueuedConnection);
|
||||
|
||||
if (code.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
buffer = code.toUtf8();
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
// Callback: void(QPixmap)
|
||||
void fetchAppIconFromApple(const QString &bundleId,
|
||||
@@ -165,7 +187,8 @@ QString LoginDialog::getPassword() const { return m_passwordEdit->text(); }
|
||||
|
||||
AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false)
|
||||
{
|
||||
m_searchProcess = new QProcess(this);
|
||||
// m_searchProcess = new QProcess(this);
|
||||
m_searchWatcher = new QFutureWatcher<QString>(this);
|
||||
m_debounceTimer = new QTimer(this);
|
||||
setupUI();
|
||||
}
|
||||
@@ -196,42 +219,20 @@ void AppsWidget::setupUI()
|
||||
m_statusLabel->setStyleSheet("margin-right: 20px;");
|
||||
|
||||
// --- Status and Login Button ---
|
||||
// int init_result = IpaToolInitialize();
|
||||
// Example: Asynchronous QProcess with interactive input
|
||||
QProcess process;
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
process.setProcessEnvironment(env);
|
||||
// process.
|
||||
// TODO: keychain passphrase
|
||||
process.start("ipatool", QStringList()
|
||||
<< "auth" << "info" << "--non-interactive"
|
||||
<< "--format" << "json"
|
||||
<< "--keychain-passphrase" << "iDescriptor");
|
||||
process.waitForFinished();
|
||||
|
||||
// connect(process, &QProcess::readyReadStandardOutput, [process]() {
|
||||
// QString output = process->readAllStandardOutput();
|
||||
// qDebug() << output;
|
||||
// if (output.contains("Enter 2FA code")) {
|
||||
// // Show dialog to user, get code, then:
|
||||
// process->write("123456\n"); // Replace with actual code
|
||||
// }
|
||||
// });
|
||||
|
||||
// process->start("ipatool auth info --non-interactive --format json
|
||||
// --keychain-passphrase \"iDescriptor\""); qDebug() << "IpaToolInitialize
|
||||
// result:" << init_result;
|
||||
if (process.exitCode() != 0) {
|
||||
qDebug() << "IpaToolInitialize failed with error code:"
|
||||
<< process.exitCode();
|
||||
// TODO: need a singleton for IpaTool
|
||||
int init_result = IpaToolInitialize();
|
||||
if (init_result != 0) {
|
||||
qDebug() << "IpaToolInitialize failed with error code:" << init_result;
|
||||
m_statusLabel->setText("Failed to initialize");
|
||||
} else {
|
||||
qDebug() << "IpaToolInitialize succeeded";
|
||||
QString jsonAccountInfo = process.readAllStandardOutput();
|
||||
if (!jsonAccountInfo.isEmpty()) {
|
||||
char *accountInfoCStr = IpaToolGetAccountInfo();
|
||||
if (accountInfoCStr) {
|
||||
QString jsonAccountInfo(accountInfoCStr);
|
||||
free(accountInfoCStr);
|
||||
|
||||
qDebug() << "Account info JSON:" << jsonAccountInfo;
|
||||
|
||||
// Parse JSON with error handling
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(
|
||||
QByteArray(jsonAccountInfo.toUtf8()), &parseError);
|
||||
@@ -256,8 +257,6 @@ void AppsWidget::setupUI()
|
||||
qDebug() << "JSON parse error:" << parseError.errorString();
|
||||
m_statusLabel->setText("Not signed in");
|
||||
}
|
||||
|
||||
// free(jsonAccountInfo); // Free the allocated memory
|
||||
} else {
|
||||
m_statusLabel->setText("Not signed in");
|
||||
}
|
||||
@@ -326,8 +325,7 @@ void AppsWidget::setupUI()
|
||||
m_debounceTimer->setSingleShot(true);
|
||||
connect(m_debounceTimer, &QTimer::timeout, this,
|
||||
&AppsWidget::performSearch);
|
||||
connect(m_searchProcess,
|
||||
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
|
||||
connect(m_searchWatcher, &QFutureWatcher<QString>::finished, this,
|
||||
&AppsWidget::onSearchFinished);
|
||||
}
|
||||
|
||||
@@ -501,7 +499,7 @@ void AppsWidget::onDownloadIpaClicked(const QString &name,
|
||||
void AppsWidget::onLoginClicked()
|
||||
{
|
||||
if (m_isLoggedIn) {
|
||||
// Logout
|
||||
IpaToolRevokeCredentials();
|
||||
m_isLoggedIn = false;
|
||||
m_loginButton->setText("Sign In");
|
||||
m_statusLabel->setText("Not signed in");
|
||||
@@ -511,10 +509,23 @@ void AppsWidget::onLoginClicked()
|
||||
LoginDialog dialog(this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString email = dialog.getEmail();
|
||||
if (!email.isEmpty()) {
|
||||
QString password = dialog.getPassword();
|
||||
if (email.isEmpty() || password.isEmpty()) {
|
||||
QMessageBox::warning(this, "Login Failed",
|
||||
"Email and password cannot be empty.");
|
||||
return;
|
||||
}
|
||||
int result = IpaToolLoginWithCallback(email.toUtf8().data(),
|
||||
password.toUtf8().data(),
|
||||
getAuthCodeCallback);
|
||||
if (result == 0) {
|
||||
m_isLoggedIn = true;
|
||||
m_loginButton->setText("Sign Out");
|
||||
m_statusLabel->setText("Signed in as " + email);
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, "Login Failed",
|
||||
"Login failed. Please check your credentials and 2FA code.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -536,9 +547,9 @@ void AppsWidget::onSearchTextChanged() { m_debounceTimer->start(300); }
|
||||
|
||||
void AppsWidget::performSearch()
|
||||
{
|
||||
if (m_searchProcess->state() == QProcess::Running) {
|
||||
m_searchProcess->kill();
|
||||
m_searchProcess->waitForFinished();
|
||||
if (m_searchWatcher->isRunning()) {
|
||||
m_searchWatcher->cancel();
|
||||
m_searchWatcher->waitForFinished();
|
||||
}
|
||||
|
||||
QString searchTerm = m_searchEdit->text().trimmed();
|
||||
@@ -549,25 +560,23 @@ void AppsWidget::performSearch()
|
||||
|
||||
showStatusMessage(QString("Searching for \"%1\"...").arg(searchTerm));
|
||||
|
||||
QStringList args;
|
||||
args << "search" << searchTerm << "--non-interactive"
|
||||
<< "--keychain-passphrase"
|
||||
<< "iDescriptor"
|
||||
<< "--format"
|
||||
<< "json";
|
||||
|
||||
m_searchProcess->start("ipatool", args);
|
||||
auto searchFn = [searchTerm]() -> QString {
|
||||
char *resultsCStr = IpaToolSearch(searchTerm.toUtf8().data(), 20);
|
||||
if (!resultsCStr) {
|
||||
return QString();
|
||||
}
|
||||
QString results(resultsCStr);
|
||||
free(resultsCStr);
|
||||
return results;
|
||||
};
|
||||
m_searchWatcher->setFuture(QtConcurrent::run(searchFn));
|
||||
}
|
||||
|
||||
void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
void AppsWidget::onSearchFinished()
|
||||
{
|
||||
QString jsonOutput = m_searchProcess->readAllStandardOutput();
|
||||
if (jsonOutput.isEmpty() && exitCode != 0) {
|
||||
QString errorOutput = m_searchProcess->readAllStandardError();
|
||||
qDebug() << "Search process failed:" << errorOutput;
|
||||
showStatusMessage(
|
||||
QString("Search failed: %1")
|
||||
.arg(errorOutput.isEmpty() ? "Unknown error" : errorOutput));
|
||||
QString jsonOutput = m_searchWatcher->result();
|
||||
if (jsonOutput.isEmpty()) {
|
||||
showStatusMessage("No apps found or search failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -583,15 +592,15 @@ void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
}
|
||||
|
||||
QJsonObject rootObj = doc.object();
|
||||
if (rootObj.value("level").toString() == "error") {
|
||||
if (!rootObj.value("success").toBool()) {
|
||||
QString errorMessage =
|
||||
rootObj.value("error").toString("Unknown search error.");
|
||||
showStatusMessage(QString("Search error: %1").arg(errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray appsArray = rootObj.value("apps").toArray();
|
||||
if (appsArray.isEmpty()) {
|
||||
QJsonArray resultsArray = rootObj.value("results").toArray();
|
||||
if (resultsArray.isEmpty()) {
|
||||
showStatusMessage("No apps found.");
|
||||
return;
|
||||
}
|
||||
@@ -606,10 +615,10 @@ void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
int col = 0;
|
||||
const int maxCols = 3;
|
||||
|
||||
for (const QJsonValue &appValue : appsArray) {
|
||||
for (const QJsonValue &appValue : resultsArray) {
|
||||
QJsonObject appObj = appValue.toObject();
|
||||
QString name = appObj.value("name").toString();
|
||||
QString bundleId = appObj.value("bundleID").toString();
|
||||
QString name = appObj.value("trackName").toString();
|
||||
QString bundleId = appObj.value("bundleId").toString();
|
||||
QString description = "Version: " + appObj.value("version").toString();
|
||||
|
||||
createAppCard(name, bundleId, description, "", gridLayout, row, col);
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@
|
||||
#include <QDialog>
|
||||
#include <QFile>
|
||||
#include <QFrame>
|
||||
#include <QFutureWatcher>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QProcess>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
@@ -43,7 +43,7 @@ private slots:
|
||||
void onDownloadIpaClicked(const QString &name, const QString &bundleId);
|
||||
void onSearchTextChanged();
|
||||
void performSearch();
|
||||
void onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void onSearchFinished();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
@@ -63,7 +63,7 @@ private:
|
||||
// Search
|
||||
QLineEdit *m_searchEdit;
|
||||
QTimer *m_debounceTimer;
|
||||
QProcess *m_searchProcess;
|
||||
QFutureWatcher<QString> *m_searchWatcher;
|
||||
};
|
||||
|
||||
#endif // APPSWIDGET_H
|
||||
|
||||
@@ -18,10 +18,11 @@ DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
|
||||
tabWidget->addTab(new DeviceInfoWidget(device, this), "");
|
||||
|
||||
FileExplorerWidget *explorer = new FileExplorerWidget(device, this);
|
||||
explorer->setMinimumHeight(300);
|
||||
// FIXME:race condition with lockdownd_client_new_with_handshake
|
||||
// FileExplorerWidget *explorer = new FileExplorerWidget(device, this);
|
||||
// explorer->setMinimumHeight(300);
|
||||
|
||||
tabWidget->addTab(explorer, "");
|
||||
// tabWidget->addTab(explorer, "");
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "gallerywidget.h"
|
||||
|
||||
GalleryWidget::GalleryWidget(QWidget *parent) : QWidget{parent} {}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef GALLERYWIDGET_H
|
||||
#define GALLERYWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class GalleryWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GalleryWidget(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
#endif // GALLERYWIDGET_H
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/uncore/Desktop/clones/ipatool
|
||||
Reference in New Issue
Block a user