update media logic to use different afc clients

This commit is contained in:
uncor3
2025-10-04 14:31:34 +00:00
parent 411ae160c2
commit 7f0b2d6136
11 changed files with 108 additions and 45 deletions
+39 -6
View File
@@ -14,6 +14,7 @@
#include <QPushButton>
#include <QSignalBlocker>
#include <QSplitter>
#include <QTemporaryDir>
#include <QTreeWidget>
#include <QVariant>
#include <libimobiledevice/afc.h>
@@ -73,11 +74,43 @@ void AfcExplorerWidget::onItemDoubleClicked(QListWidgetItem *item)
m_history.push(nextPath);
loadPath(nextPath);
} else {
auto *previewDialog = new MediaPreviewDialog(m_device, nextPath, this);
previewDialog->setAttribute(Qt::WA_DeleteOnClose);
previewDialog->show();
// TODO: we need this ?
emit fileSelected(nextPath);
const QString lowerFileName = name.toLower();
const bool isPreviewable =
lowerFileName.endsWith(".mp4") || lowerFileName.endsWith(".m4v") ||
lowerFileName.endsWith(".mov") || lowerFileName.endsWith(".avi") ||
lowerFileName.endsWith(".mkv") || lowerFileName.endsWith(".jpg") ||
lowerFileName.endsWith(".jpeg") || lowerFileName.endsWith(".png") ||
lowerFileName.endsWith(".gif") || lowerFileName.endsWith(".bmp");
if (isPreviewable) {
auto *previewDialog = new MediaPreviewDialog(
m_device, m_currentAfcClient, nextPath, this);
previewDialog->setAttribute(Qt::WA_DeleteOnClose);
previewDialog->show();
// TODO: we need this ?
emit fileSelected(nextPath);
} else {
// TODO: maybe delete in deconstructor
QTemporaryDir *tempDir = new QTemporaryDir();
if (tempDir->isValid()) {
qDebug() << "Created temp dir:" << tempDir->path();
QString localPath = tempDir->path() + "/" + name;
int result = export_file_to_path(
m_currentAfcClient, nextPath.toUtf8().constData(),
localPath.toUtf8().constData());
qDebug() << "Export result:" << result << "to" << localPath;
if (result == 0) {
QDesktopServices::openUrl(QUrl::fromLocalFile(localPath));
} else {
QMessageBox::warning(
this, "Export Failed",
"Could not export the file from the device.");
}
} else {
QMessageBox::critical(
this, "Error", "Could not create a temporary directory.");
}
}
}
}
@@ -287,7 +320,7 @@ void AfcExplorerWidget::exportSelectedFile(QListWidgetItem *item,
}
}
// Helper function to export to a specific local path
// TODO : abstract to services
int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
const char *device_path,
const char *local_path)
+2 -2
View File
@@ -139,8 +139,8 @@ void GalleryWidget::setupUI()
return;
qDebug() << "Opening preview for" << filePath;
auto *previewDialog =
new MediaPreviewDialog(m_device, filePath, this);
auto *previewDialog = new MediaPreviewDialog(
m_device, m_device->afcClient, filePath, this);
previewDialog->setAttribute(Qt::WA_DeleteOnClose);
previewDialog->show();
});
+24 -9
View File
@@ -627,21 +627,36 @@ void JailbrokenWidget::disconnectSSH()
}
if (iproxyProcess) {
iproxyProcess->terminate(); // Ask it to terminate nicely
if (!iproxyProcess->waitForFinished(1000)) { // Wait 1 sec
iproxyProcess->kill(); // Forcefully kill it
iproxyProcess->waitForFinished(1000); // Wait for it to die
}
delete iproxyProcess; // Avoid memory leak
iproxyProcess->kill();
delete iproxyProcess;
iproxyProcess = nullptr;
}
m_terminal->hide();
m_connectButton->setText("Connect SSH Terminal");
m_infoLabel->setText("SSH disconnected");
m_sshConnected = false;
m_isInitialized = false;
m_connectButton->setEnabled(true);
m_connectButton->setEnabled(false);
}
JailbrokenWidget::~JailbrokenWidget() { disconnectSSH(); }
// todo: crash at exit
JailbrokenWidget::~JailbrokenWidget()
{
if (m_sshTimer) {
m_sshTimer->stop();
}
if (m_sshChannel) {
ssh_channel_close(m_sshChannel);
ssh_channel_free(m_sshChannel);
}
if (m_sshSession) {
ssh_disconnect(m_sshSession);
ssh_free(m_sshSession);
}
if (iproxyProcess) {
iproxyProcess->kill();
}
}
+4
View File
@@ -1,5 +1,6 @@
#include "mainwindow.h"
#include <QApplication>
#include <QStyleFactory>
int main(int argc, char *argv[])
{
@@ -8,6 +9,9 @@ int main(int argc, char *argv[])
QCoreApplication::setOrganizationName("iDescriptor");
// QCoreApplication::setOrganizationDomain("iDescriptor.com");
QCoreApplication::setApplicationName("iDescriptor");
// QCoreApplication::setApplicationVersion(IDESCRIPTOR_VERSION);
QApplication::setStyle(QStyleFactory::create("macOS"));
MainWindow *w = MainWindow::sharedInstance();
w->show();
+5 -5
View File
@@ -26,6 +26,7 @@
#include <QtGlobal>
// todo : need to pass afc as well
MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
afc_client_t afcClient,
const QString &filePath, QWidget *parent)
: QDialog(parent), m_device(device), m_filePath(filePath),
m_isVideo(isVideoFile(filePath)), m_mainLayout(nullptr),
@@ -37,7 +38,7 @@ MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
m_progressTimer(nullptr), m_loadingLabel(nullptr), m_statusLabel(nullptr),
m_zoomInBtn(nullptr), m_zoomOutBtn(nullptr), m_zoomResetBtn(nullptr),
m_fitToWindowBtn(nullptr), m_zoomFactor(1.0), m_isRepeatEnabled(true),
m_isDraggingTimeline(false), m_videoDuration(0)
m_isDraggingTimeline(false), m_videoDuration(0), m_afcClient(afcClient)
{
setWindowTitle(QFileInfo(filePath).fileName() + " - iDescriptor");
@@ -181,10 +182,9 @@ void MediaPreviewDialog::setupVideoView()
void MediaPreviewDialog::loadMedia()
{
if (m_isVideo) {
loadVideo();
} else {
loadImage();
return loadVideo();
}
loadImage();
}
void MediaPreviewDialog::loadImage()
@@ -213,7 +213,7 @@ void MediaPreviewDialog::loadVideo()
// Get streamer URL from the singleton manager
QUrl streamUrl = MediaStreamerManager::sharedInstance()->getStreamUrl(
m_device, m_filePath);
m_device, m_afcClient, m_filePath);
if (streamUrl.isEmpty()) {
m_statusLabel->setText("Failed to start video stream");
return;
+4 -1
View File
@@ -16,6 +16,7 @@
#include <QVBoxLayout>
#include <QVideoWidget>
#include <QtGlobal>
#include <libimobiledevice/afc.h>
/**
* @brief A dialog for previewing images and videos from iOS devices
@@ -32,7 +33,7 @@ class MediaPreviewDialog : public QDialog
public:
explicit MediaPreviewDialog(iDescriptorDevice *device,
const QString &filePath,
afc_client_t afcClient, const QString &filePath,
QWidget *parent = nullptr);
~MediaPreviewDialog();
@@ -124,6 +125,8 @@ private:
bool m_isRepeatEnabled;
bool m_isDraggingTimeline;
qint64 m_videoDuration;
afc_client_t m_afcClient;
};
#endif // MEDIAPREVIEWDIALOG_H
+14 -13
View File
@@ -14,12 +14,13 @@
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient,
const char *path);
MediaStreamer::MediaStreamer(iDescriptorDevice *device, const QString &filePath,
QObject *parent)
: QTcpServer(parent), m_device(device), m_filePath(filePath),
m_cachedFileSize(-1), m_fileSizeCached(false)
MediaStreamer::MediaStreamer(iDescriptorDevice *device, afc_client_t afcClient,
const QString &filePath, QObject *parent)
: QTcpServer(parent), m_device(device), m_afcClient(afcClient),
m_filePath(filePath), m_cachedFileSize(-1), m_fileSizeCached(false)
{
// Listen on localhost with automatic port assignment
// todo: use qhostadress::localhost for jailbroken widget
if (!listen(QHostAddress::LocalHost, 0)) {
qWarning() << "MediaStreamer failed to start:" << errorString();
} else {
@@ -258,11 +259,12 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
context->bytesRemaining = endByte - startByte + 1;
context->afcHandle = 0;
qDebug() << "m_filepath" << m_filePath;
// Open file on device
const QByteArray pathBytes = m_filePath.toUtf8();
afc_error_t openResult =
afc_file_open(m_device->afcClient, pathBytes.constData(),
AFC_FOPEN_RDONLY, &context->afcHandle);
afc_file_open(m_afcClient, pathBytes.constData(), AFC_FOPEN_RDONLY,
&context->afcHandle);
if (openResult != AFC_E_SUCCESS || context->afcHandle == 0) {
qWarning() << "Failed to open file on device:" << m_filePath;
@@ -273,11 +275,11 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
// Seek to start position if needed
if (startByte > 0) {
afc_error_t seekResult = afc_file_seek(
m_device->afcClient, context->afcHandle, startByte, SEEK_SET);
afc_error_t seekResult =
afc_file_seek(m_afcClient, context->afcHandle, startByte, SEEK_SET);
if (seekResult != AFC_E_SUCCESS) {
qWarning() << "Failed to seek in file:" << m_filePath;
afc_file_close(m_device->afcClient, context->afcHandle);
afc_file_close(m_afcClient, context->afcHandle);
delete context;
socket->disconnectFromHost();
return;
@@ -334,7 +336,7 @@ qint64 MediaStreamer::getFileSize()
char **info = nullptr;
const QByteArray pathBytes = m_filePath.toUtf8();
afc_error_t result =
afc_get_file_info(m_device->afcClient, pathBytes.constData(), &info);
afc_get_file_info(m_afcClient, pathBytes.constData(), &info);
if (result != AFC_E_SUCCESS || !info) {
qWarning() << "Failed to get file info for:" << m_filePath;
@@ -410,9 +412,8 @@ void MediaStreamer::streamNextChunk(StreamingContext *context)
auto buffer = std::make_unique<char[]>(bytesToRead);
uint32_t bytesRead = 0;
afc_error_t readResult =
afc_file_read(context->device->afcClient, context->afcHandle,
buffer.get(), bytesToRead, &bytesRead);
afc_error_t readResult = afc_file_read(
m_afcClient, context->afcHandle, buffer.get(), bytesToRead, &bytesRead);
if (readResult != AFC_E_SUCCESS || bytesRead == 0) {
qWarning() << "AFC read error or EOF during streaming";
+5 -2
View File
@@ -6,6 +6,7 @@
#include <QMutex>
#include <QTcpServer>
#include <QUrl>
#include <libimobiledevice/afc.h>
QT_BEGIN_NAMESPACE
class QTcpSocket;
@@ -28,8 +29,8 @@ class MediaStreamer : public QTcpServer
Q_OBJECT
public:
explicit MediaStreamer(iDescriptorDevice *device, const QString &filePath,
QObject *parent = nullptr);
explicit MediaStreamer(iDescriptorDevice *device, afc_client_t afcClient,
const QString &filePath, QObject *parent = nullptr);
~MediaStreamer();
/**
@@ -93,6 +94,8 @@ private:
// Connection management
QList<QTcpSocket *> m_activeConnections;
QMutex m_connectionsMutex;
afc_client_t m_afcClient;
};
#endif // MEDIASTREAMER_H
+2 -1
View File
@@ -12,6 +12,7 @@ MediaStreamerManager *MediaStreamerManager::sharedInstance()
}
QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device,
afc_client_t afcClient,
const QString &filePath)
{
@@ -36,7 +37,7 @@ QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device,
}
// Create new streamer
auto *streamer = new MediaStreamer(device, filePath, this);
auto *streamer = new MediaStreamer(device, afcClient, filePath, this);
if (!streamer->isListening()) {
qWarning() << "MediaStreamerManager: Failed to create streamer for"
<< filePath;
+3 -1
View File
@@ -7,6 +7,7 @@
#include <QMutex>
#include <QObject>
#include <QUrl>
#include <libimobiledevice/afc.h>
/**
* @brief Singleton manager for MediaStreamer instances
@@ -32,7 +33,8 @@ public:
* @param filePath The file path on the device
* @return URL to stream the file, or empty URL if failed
*/
QUrl getStreamUrl(iDescriptorDevice *device, const QString &filePath);
QUrl getStreamUrl(iDescriptorDevice *device, afc_client_t afcClient,
const QString &filePath);
/**
* @brief Release a streamer for the specified file
+6 -5
View File
@@ -82,8 +82,8 @@ QPixmap PhotoModel::generateVideoThumbnail(iDescriptorDevice *device,
});
// Get the streaming URL and start playback
QUrl streamUrl =
MediaStreamerManager::sharedInstance()->getStreamUrl(device, filePath);
QUrl streamUrl = MediaStreamerManager::sharedInstance()->getStreamUrl(
device, device->afcClient, filePath);
if (streamUrl.isEmpty()) {
qWarning() << "Could not get stream URL for video thumbnail:"
<< filePath;
@@ -280,7 +280,8 @@ void PhotoModel::requestThumbnail(int index)
QFuture<QPixmap> future;
if (isVideo) {
// Load video thumbnail asynchronously
future = QtConcurrent::run([=]() {
// todo: implement
future = QtConcurrent::run([this]() {
// Check disk cache first
// if (QFile::exists(cachePath)) {
// QPixmap cached(cachePath);
@@ -304,12 +305,12 @@ void PhotoModel::requestThumbnail(int index)
// << QFileInfo(info.filePath).fileName();
// }
// }
return QIcon::fromTheme("video-x-generic").pixmap(m_thumbnailSize);
return QPixmap(); // Placeholder until implemented
// return thumbnail;
});
} else {
// Load image thumbnail asynchronously (existing logic)
future = QtConcurrent::run([=]() {
future = QtConcurrent::run([info, cachePath, this]() {
return loadThumbnailFromDevice(m_device, info.filePath,
m_thumbnailSize, cachePath);
});