mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
update media logic to use different afc clients
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user