diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 46ac91e..4fc6775 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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) diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index d3afa64..1d6a8ca 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -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(); }); diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index ce2d571..130f03a 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -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(); + } +} diff --git a/src/main.cpp b/src/main.cpp index b998ca3..92a0848 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "mainwindow.h" #include +#include 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(); diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 195752f..04d1f65 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -26,6 +26,7 @@ #include // 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; diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h index e7436b2..f19b76e 100644 --- a/src/mediapreviewdialog.h +++ b/src/mediapreviewdialog.h @@ -16,6 +16,7 @@ #include #include #include +#include /** * @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 \ No newline at end of file diff --git a/src/mediastreamer.cpp b/src/mediastreamer.cpp index 068b78b..64c7cf4 100644 --- a/src/mediastreamer.cpp +++ b/src/mediastreamer.cpp @@ -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(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"; diff --git a/src/mediastreamer.h b/src/mediastreamer.h index 764f85d..55add9e 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -6,6 +6,7 @@ #include #include #include +#include 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 m_activeConnections; QMutex m_connectionsMutex; + + afc_client_t m_afcClient; }; #endif // MEDIASTREAMER_H \ No newline at end of file diff --git a/src/mediastreamermanager.cpp b/src/mediastreamermanager.cpp index 2f46a16..620259a 100644 --- a/src/mediastreamermanager.cpp +++ b/src/mediastreamermanager.cpp @@ -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; diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 95f748c..073b466 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -7,6 +7,7 @@ #include #include #include +#include /** * @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 diff --git a/src/photomodel.cpp b/src/photomodel.cpp index b2b1c74..9d9f758 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -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 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); });