diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c794b5..df8f2aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,6 @@ endif() find_package(PkgConfig REQUIRED) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) -find_package(Qt6 REQUIRED COMPONENTS Core) # Add QTermWidget # Prefer CMake-native qtermwidget6, fallback to pkg-config if needed @@ -89,13 +88,13 @@ find_program(CARGO_EXECUTABLE cargo REQUIRED) message(STATUS "Using idevice-rs Rust implementation") set(IDEVICE_RS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/idevice-rs) -set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/release/libidevice_ffi.a) +set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/debug/libidevice_ffi.a) # This command builds the Rust library and declares its output file. # Any target that uses this output file will automatically depend on this command. add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} - COMMAND ${CARGO_EXECUTABLE} build --release --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml + COMMAND ${CARGO_EXECUTABLE} build --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml WORKING_DIRECTORY ${IDEVICE_RS_SOURCE_DIR} COMMENT "Building idevice-rs FFI library" VERBATIM @@ -271,6 +270,18 @@ src/networkdevicemanager.cpp src/networkdevicemanager.h src/*.ui resources.qrc +src/gallerywidget.cpp +src/gallerywidget.h +src/photomodel.cpp +src/photomodel.h +src/core/services/load_heic.cpp +src/servicemanager.cpp +src/servicemanager.h +src/core/services/get_file_tree.cpp +src/core/helpers/read_afc_file_to_byte_array.cpp +src/heartbeat.h +src/zloadingwidget.h +src/zloadingwidget.cpp ) if(APPLE) @@ -429,7 +440,6 @@ if(APPLE) ${SECURITY_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK} ) -target_link_libraries(iDescriptor PRIVATE Qt6::Core) endif() # Add compile definition for source directory diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 25b71b7..591d3e2 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -65,7 +65,7 @@ AppContext::AppContext(QObject *parent) : QObject{parent} plist_print(fileData); const std::string wifiMacAddress = PlistNavigator(fileData)["WiFiMACAddress"].getString(); - plist_free(fileData); + // plist_free(fileData); qDebug() << "Found pairing file for MAC" << QString::fromStdString(wifiMacAddress); bool isCompatible = !wifiMacAddress.empty(); @@ -229,7 +229,7 @@ void AppContext::addDevice(QString udid, iDescriptorDevice *device = new iDescriptorDevice{ .udid = udid.toStdString(), .conn_type = conn_type, - .device = initResult.device, + .provider = initResult.provider, .deviceInfo = initResult.deviceInfo, .afcClient = initResult.afcClient, .afc2Client = initResult.afc2Client, @@ -417,20 +417,19 @@ AppContext::~AppContext() void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) { - // qDebug() << "New selection -" - // << " Type:" << selection.type - // << " UDID:" << QString::fromStdString(selection.udid) - // << " ECID:" << selection.ecid << " Section:" << - // selection.section; - // if (m_currentSelection.type == selection.type && - // m_currentSelection.udid == selection.udid && - // m_currentSelection.ecid == selection.ecid && - // m_currentSelection.section == selection.section) { - // qDebug() << "setCurrentDeviceSelection: No change in selection"; - // return; // No change - // } - // m_currentSelection = selection; - // emit currentDeviceSelectionChanged(m_currentSelection); + qDebug() << "New selection -" + << " Type:" << selection.type + << " UDID:" << QString::fromStdString(selection.udid) + << " ECID:" << selection.ecid << " Section:" << selection.section; + if (m_currentSelection.type == selection.type && + m_currentSelection.udid == selection.udid && + m_currentSelection.ecid == selection.ecid && + m_currentSelection.section == selection.section) { + qDebug() << "setCurrentDeviceSelection: No change in selection"; + return; // No change + } + m_currentSelection = selection; + emit currentDeviceSelectionChanged(m_currentSelection); } const DeviceSelection &AppContext::getCurrentDeviceSelection() const diff --git a/src/core/helpers/read_afc_file_to_byte_array.cpp b/src/core/helpers/read_afc_file_to_byte_array.cpp index 3ab98d4..9739de7 100644 --- a/src/core/helpers/read_afc_file_to_byte_array.cpp +++ b/src/core/helpers/read_afc_file_to_byte_array.cpp @@ -18,75 +18,77 @@ */ #include "../../iDescriptor.h" +#include "../../servicemanager.h" #include #include -QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path) +QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, + const char *path) { - uint64_t fd_handle = 0; - afc_error_t fd_err = - afc_file_open(afcClient, path, AFC_FOPEN_RDONLY, &fd_handle); + AfcFileHandle *handle = nullptr; + IdeviceFfiError *err_open = // Use distinct variable name + ServiceManager::safeAfcFileOpen(device, path, AfcRdOnly, &handle); - if (fd_err != AFC_E_SUCCESS) { - qDebug() << "Could not open file" << path << "Error:" << fd_err; + if (err_open) { + qDebug() << "Could not open file" << path + << "Error:" << err_open->message; + idevice_error_free(err_open); // Free the error object return QByteArray(); } - // TODO:Maybe use afc_get_file_info_plist instead? - char **info = NULL; - afc_get_file_info(afcClient, path, &info); - uint64_t fileSize = 0; - if (info) { - for (int i = 0; info[i]; i += 2) { - if (strcmp(info[i], "st_size") == 0) { - fileSize = std::stoull(info[i + 1]); - break; - } - } - afc_dictionary_free(info); - } + AfcFileInfo info = {}; + IdeviceFfiError *err_info = // Use distinct variable name + ServiceManager::safeAfcGetFileInfo(device, path, &info); + if (err_info) { + qDebug() << "Could not get file info for file" << path + << "Error:" << err_info->message; + idevice_error_free(err_info); // Free the error object + ServiceManager::safeAfcFileClose(device, handle); // Close handle + return QByteArray(); + } + // Note: afc_file_info_free will be called later if the function returns + // successfully or when returning early after the file size check. + + qDebug() << "File size of" << path << "is" << info.size; + size_t fileSize = info.size; if (fileSize == 0) { - afc_file_close(afcClient, fd_handle); + ServiceManager::safeAfcFileClose(device, handle); + afc_file_info_free(&info); // Free internal strings of info return QByteArray(); } QByteArray buffer; - buffer.resize(fileSize); + buffer.reserve(fileSize); - uint64_t totalBytesRead = 0; - const uint32_t CHUNK_SIZE = 1024 * 1024; // Read in 1MB chunks - char *p = buffer.data(); + uint8_t *chunkData = nullptr; + size_t bytesRead = 0; + IdeviceFfiError *read_err = ServiceManager::safeAfcFileRead( + device, handle, &chunkData, fileSize, &bytesRead); - while (totalBytesRead < fileSize) { - uint32_t bytesToRead = - std::min((uint64_t)CHUNK_SIZE, fileSize - totalBytesRead); - uint32_t bytesReadThisChunk = 0; - afc_error_t read_err = - afc_file_read(afcClient, fd_handle, p + totalBytesRead, bytesToRead, - &bytesReadThisChunk); - - if (read_err != AFC_E_SUCCESS) { - qDebug() << "AFC Error: Read failed for file" << path - << "Error:" << read_err; - afc_file_close(afcClient, fd_handle); - return QByteArray(); - } - - if (bytesReadThisChunk == 0) { - // Premature end of file - break; - } - totalBytesRead += bytesReadThisChunk; + if (read_err) { + qDebug() << "AFC Error: Read failed for file" << path + << "Error:" << read_err->message; + idevice_error_free(read_err); + ServiceManager::safeAfcFileClose(device, handle); + afc_file_info_free(&info); // Free internal strings of info + return QByteArray(); } - afc_file_close(afcClient, fd_handle); + // Only append and free `chunkData` if `afc_file_read` was successful + buffer.append(reinterpret_cast(chunkData), bytesRead); + afc_file_read_data_free(chunkData, + bytesRead); // Free memory owned by Rust FFI - if (totalBytesRead != fileSize) { + ServiceManager::safeAfcFileClose(device, handle); + + if (bytesRead != fileSize) { qDebug() << "AFC Error: Read mismatch for file" << path - << "Read:" << totalBytesRead << "Expected:" << fileSize; - return QByteArray(); // Read failed + << "Read:" << bytesRead << "Expected:" << fileSize; + afc_file_info_free(&info); // Free internal strings of info + return QByteArray(); // Read failed } + afc_file_info_free(&info); // Free internal strings of info on success path return buffer; } \ No newline at end of file diff --git a/src/core/services/get_file_tree.cpp b/src/core/services/get_file_tree.cpp index 9796205..e53e5bc 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -18,68 +18,113 @@ */ #include "../../iDescriptor.h" +#include "../../servicemanager.h" #include #include -#include -#include #include -AFCFileTree get_file_tree(afc_client_t afcClient, const std::string &path) +AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path) { - + qDebug() << "Getting file tree for path:" << QString::fromStdString(path); AFCFileTree result; result.currentPath = path; + result.success = false; - char **dirs = NULL; - if (afc_read_directory(afcClient, path.c_str(), &dirs) != AFC_E_SUCCESS) { - result.success = false; + char **dirs = nullptr; + size_t count = 0; + + // Use safe wrapper to read directory + IdeviceFfiError *err = + ServiceManager::safeAfcReadDirectory(device, path.c_str(), &dirs); + + if (err) { + qDebug() << "Failed to read directory:" << path.c_str() + << "Error:" << err->message << "Code:" << err->code; + idevice_error_free(err); return result; } + if (!dirs) { + result.success = true; + return result; + } + + // Iterate through directory entries for (int i = 0; dirs[i]; i++) { + qDebug() << "Found entry:" << dirs[i]; std::string entryName = dirs[i]; if (entryName == "." || entryName == "..") continue; - char **info = NULL; std::string fullPath = path; if (fullPath.back() != '/') fullPath += "/"; fullPath += entryName; + + if (!checkDir) { + result.entries.push_back({entryName, false}); + continue; + } + + // Get file info using safe wrapper + AfcFileInfo info = {}; + IdeviceFfiError *info_err = + ServiceManager::safeAfcGetFileInfo(device, fullPath.c_str(), &info); + + if (info_err) { + qDebug() << "Failed to get file info for:" << fullPath.c_str() + << "Error:" << info_err->message; + } + bool isDir = false; - if (afc_get_file_info(afcClient, fullPath.c_str(), &info) == - AFC_E_SUCCESS && - info) { - if (entryName == "var") { - qDebug() << "File info for var:" << info[0] << info[1] - << info[2] << info[3] << info[4] << info[5]; - } - for (int j = 0; info[j]; j += 2) { - if (strcmp(info[j], "st_ifmt") == 0) { - if (strcmp(info[j + 1], "S_IFDIR") == 0) { - isDir = true; - } else if (strcmp(info[j + 1], "S_IFLNK") == 0) { - /*symlink*/ - char **dir_contents = NULL; - if (afc_read_directory(afcClient, fullPath.c_str(), - &dir_contents) == - AFC_E_SUCCESS) { - isDir = true; - if (dir_contents) { - afc_dictionary_free(dir_contents); - } - } - } - break; + if (!info_err) { + qDebug() << "Entry:" << entryName.c_str() << "Type:" << info.st_ifmt + << "Size:" << info.size; + if (strcmp(info.st_ifmt, "S_IFDIR") == 0) { + isDir = true; + } else if (strcmp(info.st_ifmt, "S_IFLNK") == 0) { + // Check if symlink points to a directory + char **dir_contents = nullptr; + IdeviceFfiError *link_err = + ServiceManager::safeAfcReadDirectory( + device, fullPath.c_str(), &dir_contents); + + if (!link_err) { + isDir = true; + // if (dir_contents) { + // // FIXME: is this ok ? + // for (int j = 0; dir_contents[j]; j++) { + // free(dir_contents[j]); + // } + // free(dir_contents); + // } + } + + if (link_err) { + idevice_error_free(link_err); } } - afc_dictionary_free(info); + + // Free file info + // afc_file_info_free(&info); } + + if (info_err) { + idevice_error_free(info_err); + } + result.entries.push_back({entryName, isDir}); } + + // Free the directory list if (dirs) { - afc_dictionary_free(dirs); + // for (int i = 0; dirs[i]; i++) { + // free(dirs[i]); + // } + // free(dirs); } + result.success = true; return result; } \ No newline at end of file diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 8ef71bc..49fdd65 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -27,11 +27,13 @@ #include #include +#include "../../heartbeat.h" #include #include #include #include +#include std::string safeGetXML(const char *key, pugi::xml_node dict) { for (pugi::xml_node child = dict.first_child(); child; @@ -360,14 +362,9 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } } -// [DeviceMonitor] Device connected: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" -// Device added: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" -// Initializing iDescriptor device with UDID: -// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" Failed to create idevice handle -// Initialization failed, cleaning up resources. FfiInvalidArg -// init_idescriptor_device success ?: false -// Failed to initialize device with UDID: -// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" +// FIXME:spawn on a new thread? +// wireless connections sometimes take more than 10sec to connect +// and ofc it freezes the ui iDescriptorInitDeviceResult init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) { @@ -389,6 +386,9 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) uint32_t actual_device_id = 0; IdevicePairingFile *pairing_file = nullptr; IdeviceHandle *deviceHandle = nullptr; + HeartbeatClientHandle *heartbeat = nullptr; + HeartBeatThread *heartbeatThread = nullptr; + // FIXME: remove debug std::stringstream ss; @@ -484,43 +484,30 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) uint16_t heartbeat_port; bool heartbeat_ssl; - if (isWireless) { - // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", - // &heartbeat_port, &heartbeat_ssl); - // if (err) { - // qDebug() << "Failed to start Heartbeat service"; - // goto cleanup; - // } + // if (isWireless) { + // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", + // &heartbeat_port, &heartbeat_ssl); - // Start heartbeat client to keep connection alive - HeartbeatClientHandle *heartbeat = nullptr; - err = heartbeat_connect(provider, &heartbeat); - - if (err) { - qDebug() << "Failed to connect to Heartbeat client"; - goto cleanup; - } - - // // After getting the heartbeat port from lockdown - // IdeviceHandle *deviceHandle = nullptr; - - // // Then create IdeviceHandle from the socket - // err = idevice_new(socket, "heartbeat", &deviceHandle); - // if (err) { - // qDebug() << "Failed to create idevice handle"; - // goto cleanup; - // } - - // // Now use with heartbeat_new - // HeartbeatClientHandle *heartbeat = nullptr; - // err = heartbeat_new(deviceHandle, &heartbeat); - // if (err) { - // qDebug() << "Failed to create Heartbeat client"; - // goto cleanup; - // } - - qDebug() << "Heartbeat client created successfully"; + // Start heartbeat client to keep connection alive + err = heartbeat_connect(provider, &heartbeat); + if (err) { + qDebug() << "Failed to start Heartbeat service"; + goto cleanup; } + heartbeatThread = new HeartBeatThread(heartbeat); + heartbeatThread->start(); + + // while (!heartbeatThread->initialCompleted()) { + // sleep(1); + // } + + if (err) { + qDebug() << "Failed to connect to Heartbeat client"; + goto cleanup; + } + + qDebug() << "Heartbeat client created successfully"; + // } // 5. Start AFC service uint16_t afc_port; @@ -539,21 +526,21 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) goto cleanup; } - // 7. AFC2 is optional - uint16_t afc2_port; - bool afc2_ssl; - err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, - &afc2_ssl); - if (!err) { - err = afc_client_connect(provider, &afc2_client); - } + // // 7. AFC2 is optional + // uint16_t afc2_port; + // bool afc2_ssl; + // err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, + // &afc2_ssl); + // if (!err) { + // err = afc_client_connect(provider, &afc2_client); + // } get_device_info_xml(udid.toUtf8().constData(), lockdown, infoXml); - infoXml.print(ss, " "); // " " for indentation - qDebug().noquote() << "--- Full Device Info XML ---" - << QString::fromStdString(ss.str()); + // infoXml.print(ss, " "); // " " for indentation + // qDebug().noquote() << "--- Full Device Info XML ---" + // << QString::fromStdString(ss.str()); - result.device = provider; + result.provider = provider; result.success = true; result.afcClient = afc_client; result.afc2Client = afc2_client; diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index def6524..d6e5f3a 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -56,29 +56,29 @@ void DeviceMenuWidget::init() // Create and add widgets to the stacked widget m_deviceInfoWidget = new DeviceInfoWidget(device, this); // m_installedAppsWidget = new InstalledAppsWidget(device, this); - // m_galleryWidget = new GalleryWidget(device, this); + m_galleryWidget = new GalleryWidget(device, this); // m_fileExplorerWidget = new FileExplorerWidget(device, this); // Set minimum heights - // m_galleryWidget->setMinimumHeight(300); + m_galleryWidget->setMinimumHeight(300); // m_fileExplorerWidget->setMinimumHeight(300); stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info // stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps - // stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery + stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery // stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files // Set default to Info tab stackedWidget->setCurrentWidget(m_deviceInfoWidget); // Connect to current changed signal for lazy loading - // connect(stackedWidget, &QStackedWidget::currentChanged, this, - // [this](int index) { - // if (index == 2) { // Gallery tab - // qDebug() << "Switched to Gallery tab"; - // m_galleryWidget->load(); - // } - // }); + connect(stackedWidget, &QStackedWidget::currentChanged, this, + [this](int index) { + if (stackedWidget->widget(index) == + m_galleryWidget) { // Gallery tab + m_galleryWidget->load(); + } + }); QWidget *loadingWidget = stackedWidget->widget(0); stackedWidget->removeWidget(loadingWidget); @@ -91,8 +91,9 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) stackedWidget->setCurrentWidget(m_deviceInfoWidget); // } else if (tabName == "Apps") { // stackedWidget->setCurrentWidget(m_installedAppsWidget); - // } else if (tabName == "Gallery") { - // stackedWidget->setCurrentWidget(m_galleryWidget); + } else if (tabName == "Gallery") { + qDebug() << "Switching to Gallery tab"; + stackedWidget->setCurrentWidget(m_galleryWidget); } else if (tabName == "Files") { // stackedWidget->setCurrentWidget(m_fileExplorerWidget); } else { diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index ea94647..326bb8d 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -42,7 +42,7 @@ private: iDescriptorDevice *device; // Pointer to the iDescriptor device DeviceInfoWidget *m_deviceInfoWidget; // InstalledAppsWidget *m_installedAppsWidget; - // GalleryWidget *m_galleryWidget; + GalleryWidget *m_galleryWidget; // FileExplorerWidget *m_fileExplorerWidget; signals: }; diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 0561698..b2fc4cf 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -404,7 +404,7 @@ void DiskUsageWidget::fetchData() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; - if (!m_device || !m_device->device) { + if (!m_device || !m_device->provider) { result["error"] = "Invalid device."; return result; } @@ -418,7 +418,7 @@ void DiskUsageWidget::fetchData() m_device->deviceInfo.diskInfo.totalSystemCapacity); // Create provider wrapper from existing handle - Provider provider = Provider::adopt(m_device->device); + Provider provider = Provider::adopt(m_device->provider); // Apps usage uint64_t totalAppsSpace = 0; @@ -466,7 +466,7 @@ void DiskUsageWidget::fetchData() } } result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - plist_free(client_opts); // client_opts is consumed by browse, but + // plist_free(client_opts); // client_opts is consumed by browse, but // Media usage uint64_t mediaSpace = 0; diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index de862f5..924a85e 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -18,9 +18,9 @@ */ #include "gallerywidget.h" -#include "exportmanager.h" +// #include "exportmanager.h" #include "iDescriptor.h" -#include "mediapreviewdialog.h" +// #include "mediapreviewdialog.h" #include "photomodel.h" #include "servicemanager.h" #include @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -53,20 +54,6 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) m_stackedWidget(nullptr), m_albumSelectionWidget(nullptr), m_albumListView(nullptr), m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr) -{ -} -/*Load is called when the tab is active*/ -void GalleryWidget::load() -{ - if (m_loaded) - return; - - m_loaded = true; - - setupUI(); -} - -void GalleryWidget::setupUI() { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); @@ -86,10 +73,36 @@ void GalleryWidget::setupUI() // Add stacked widget to main layout m_mainLayout->addWidget(m_stackedWidget); setLayout(m_mainLayout); + m_loadingWidget = new ZLoadingWidget(true, this); + m_stackedWidget->addWidget(m_loadingWidget); + m_stackedWidget->setCurrentWidget(m_loadingWidget); - // Start with album selection view and load albums - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + QVBoxLayout *errorLayout = new QVBoxLayout(); + errorLayout->setAlignment(Qt::AlignCenter); + QLabel *errorLabel = new QLabel("Failed to load albums."); + errorLabel->setStyleSheet("font-weight: bold; color: red;"); + errorLayout->addWidget(errorLabel); + m_retryButton = new QPushButton("Retry", this); + connect(m_retryButton, &QPushButton::clicked, this, [this]() { + m_stackedWidget->setCurrentWidget(m_loadingWidget); + QTimer::singleShot(100, this, &GalleryWidget::loadAlbumList); + }); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + m_errorWidget = new QWidget(); + m_errorWidget->setLayout(errorLayout); + + m_stackedWidget->addWidget(m_errorWidget); + m_stackedWidget->setCurrentWidget(m_loadingWidget); setControlsEnabled(false); // Disable controls until album is selected +} +/*Load is called when the tab is active*/ +void GalleryWidget::load() +{ + if (m_loaded) + return; + + m_loaded = true; + loadAlbumList(); } @@ -115,7 +128,8 @@ void GalleryWidget::setupControlsLayout() QLabel *filterLabel = new QLabel("Filter:"); filterLabel->setStyleSheet("font-weight: bold;"); m_filterComboBox = new QComboBox(); - m_filterComboBox->addItem("All Media", static_cast(PhotoModel::All)); + m_filterComboBox->addItem("All Media", + static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Images Only", static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Videos Only", @@ -208,39 +222,39 @@ void GalleryWidget::onExportSelected() return; } - if (ExportManager::sharedInstance()->isExporting()) { - QMessageBox::information(this, "Export in Progress", - "An export is already in progress."); - return; - } + // if (ExportManager::sharedInstance()->isExporting()) { + // QMessageBox::information(this, "Export in Progress", + // "An export is already in progress."); + // return; + // } - QModelIndexList selectedIndexes = - m_listView->selectionModel()->selectedIndexes(); - QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); + // QModelIndexList selectedIndexes = + // m_listView->selectionModel()->selectedIndexes(); + // QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); - if (filePaths.isEmpty()) { - QMessageBox::information(this, "No Items", - "No valid items selected for export."); - return; - } + // if (filePaths.isEmpty()) { + // QMessageBox::information(this, "No Items", + // "No valid items selected for export."); + // return; + // } - QString exportDir = selectExportDirectory(); - if (exportDir.isEmpty()) { - return; - } + // QString exportDir = selectExportDirectory(); + // if (exportDir.isEmpty()) { + // return; + // } - // Convert QStringList to QList - QList exportItems; - for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); - exportItems.append(ExportItem(filePath, fileName)); - } + // // Convert QStringList to QList + // QList exportItems; + // for (const QString &filePath : filePaths) { + // QString fileName = filePath.split('/').last(); + // exportItems.append(ExportItem(filePath, fileName)); + // } - qDebug() << "Starting export of selected files:" << exportItems.size() - << "items to" << exportDir; + // qDebug() << "Starting export of selected files:" << exportItems.size() + // << "items to" << exportDir; - ExportManager::sharedInstance()->startExport(m_device, exportItems, - exportDir); + // ExportManager::sharedInstance()->startExport(m_device, exportItems, + // exportDir); } void GalleryWidget::onExportAll() @@ -248,47 +262,49 @@ void GalleryWidget::onExportAll() if (!m_model) return; - if (ExportManager::sharedInstance()->isExporting()) { - QMessageBox::information(this, "Export in Progress", - "An export is already in progress."); - return; - } + // if (ExportManager::sharedInstance()->isExporting()) { + // QMessageBox::information(this, "Export in Progress", + // "An export is already in progress."); + // return; + // } - QStringList filePaths = m_model->getFilteredFilePaths(); + // QStringList filePaths = m_model->getFilteredFilePaths(); - if (filePaths.isEmpty()) { - QMessageBox::information(this, "No Items", "No items to export."); - return; - } + // if (filePaths.isEmpty()) { + // QMessageBox::information(this, "No Items", "No items to export."); + // return; + // } - QString message = - QString("Export all %1 items currently shown?").arg(filePaths.size()); - int reply = QMessageBox::question(this, "Export All", message, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No); + // QString message = + // QString("Export all %1 items currently + // shown?").arg(filePaths.size()); + // int reply = QMessageBox::question(this, "Export All", message, + // QMessageBox::Yes | QMessageBox::No, + // QMessageBox::No); - if (reply != QMessageBox::Yes) { - return; - } + // if (reply != QMessageBox::Yes) { + // return; + // } - QString exportDir = selectExportDirectory(); - if (exportDir.isEmpty()) { - return; - } + // QString exportDir = selectExportDirectory(); + // if (exportDir.isEmpty()) { + // return; + // } - // Convert QStringList to QList - QList exportItems; - for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); - exportItems.append(ExportItem(filePath, fileName)); - } + // // Convert QStringList to QList + // QList exportItems; + // for (const QString &filePath : filePaths) { + // QString fileName = filePath.split('/').last(); + // exportItems.append(ExportItem(filePath, fileName)); + // } - qDebug() << "Starting export of all filtered files:" << exportItems.size() - << "items to" << exportDir; + // qDebug() << "Starting export of all filtered files:" << + // exportItems.size() + // << "items to" << exportDir; - // Start export and the manager will show its own dialog - ExportManager::sharedInstance()->startExport(m_device, exportItems, - exportDir); + // // Start export and the manager will show its own dialog + // ExportManager::sharedInstance()->startExport(m_device, exportItems, + // exportDir); } QString GalleryWidget::selectExportDirectory() @@ -392,11 +408,11 @@ void GalleryWidget::setupPhotoGalleryView() if (filePath.isEmpty()) return; - qDebug() << "Opening preview for" << filePath; - auto *previewDialog = new MediaPreviewDialog( - m_device, m_device->afcClient, filePath, this); - previewDialog->setAttribute(Qt::WA_DeleteOnClose); - previewDialog->show(); + // qDebug() << "Opening preview for" << filePath; + // auto *previewDialog = new MediaPreviewDialog( + // m_device, m_device->afcClient, filePath, this); + // previewDialog->setAttribute(Qt::WA_DeleteOnClose); + // previewDialog->show(); }); connect(m_listView, &QListView::customContextMenuRequested, this, @@ -405,15 +421,20 @@ void GalleryWidget::setupPhotoGalleryView() void GalleryWidget::loadAlbumList() { - AFCFileTree dcimTree = ServiceManager::safeGetFileTree(m_device, "/DCIM"); + qDebug() << "Before reading DCIM directory"; + AFCFileTree dcimTree = + ServiceManager::safeGetFileTree(m_device, "/DCIM", true); if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; + m_stackedWidget->setCurrentWidget(m_errorWidget); QMessageBox::warning(this, "Error", "Could not access DCIM directory on device."); return; } + m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + qDebug() << "DCIM directory read successfully, found" << dcimTree.entries.size() << "entries"; @@ -505,8 +526,8 @@ void GalleryWidget::setControlsEnabled(bool enabled) QIcon GalleryWidget::loadAlbumThumbnail(const QString &albumPath) { // Get album directory contents - AFCFileTree albumTree = - ServiceManager::safeGetFileTree(m_device, albumPath.toStdString()); + AFCFileTree albumTree = ServiceManager::safeGetFileTree( + m_device, albumPath.toStdString(), false); if (!albumTree.success) { qDebug() << "Failed to read album directory:" << albumPath; @@ -606,21 +627,21 @@ void GalleryWidget::onPhotoContextMenu(const QPoint &pos) exportAction->setEnabled(m_listView->selectionModel()->hasSelection()); - connect(previewAction, &QAction::triggered, this, [this, index]() { - // Re-use the double-click logic - if (!index.isValid()) - return; + // connect(previewAction, &QAction::triggered, this, [this, index]() { + // // Re-use the double-click logic + // if (!index.isValid()) + // return; - QString filePath = m_model->data(index, Qt::UserRole).toString(); - if (filePath.isEmpty()) - return; + // QString filePath = m_model->data(index, Qt::UserRole).toString(); + // if (filePath.isEmpty()) + // return; - qDebug() << "Opening preview for" << filePath; - auto *previewDialog = new MediaPreviewDialog( - m_device, m_device->afcClient, filePath, this); - previewDialog->setAttribute(Qt::WA_DeleteOnClose); - previewDialog->show(); - }); + // qDebug() << "Opening preview for" << filePath; + // auto *previewDialog = new MediaPreviewDialog( + // m_device, m_device->afcClient, filePath, this); + // previewDialog->setAttribute(Qt::WA_DeleteOnClose); + // previewDialog->show(); + // }); connect(exportAction, &QAction::triggered, this, &GalleryWidget::onExportSelected); diff --git a/src/gallerywidget.h b/src/gallerywidget.h index 4dae062..d8073e0 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -22,6 +22,7 @@ #include "iDescriptor.h" #include "photomodel.h" +#include "zloadingwidget.h" #include QT_BEGIN_NAMESPACE @@ -57,7 +58,6 @@ private slots: void onBackToAlbums(); private: - void setupUI(); void setupControlsLayout(); void setupAlbumSelectionView(); void setupPhotoGalleryView(); @@ -77,6 +77,9 @@ private: QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; QStackedWidget *m_stackedWidget; + ZLoadingWidget *m_loadingWidget; + QWidget *m_errorWidget; + QPushButton *m_retryButton; // Album selection view QWidget *m_albumSelectionWidget; diff --git a/src/heartbeat.h b/src/heartbeat.h new file mode 100644 index 0000000..7026d4b --- /dev/null +++ b/src/heartbeat.h @@ -0,0 +1,72 @@ +#ifndef HEARTBEATTHREAD_H +#define HEARTBEATTHREAD_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IdeviceFFI; + +class HeartBeatThread : public QThread +{ + Q_OBJECT +public: + HeartBeatThread(HeartbeatClientHandle *heartbeat, QObject *parent = nullptr) + : QThread(parent), m_hb(Heartbeat::adopt(heartbeat)) + { + } + + void run() override + { + qDebug() << "Heartbeat thread started"; + try { + // Start with initial interval (15 seconds as per the tool example) + u_int64_t interval = 15; + + while (!isInterruptionRequested()) { + // 1. Wait for Marco with current interval + Result result = m_hb.get_marco(interval); + if (result.is_err()) { + qDebug() + << "Failed to get marco:" + << QString::fromStdString(result.unwrap_err().message); + break; + } + + // 2. Get the new interval from device + interval = result.unwrap(); + qDebug() << "Received marco, new interval:" << interval; + + // 3. Send Polo response + Result polo_result = m_hb.send_polo(); + if (polo_result.is_err()) { + qDebug() << "Failed to send polo:" + << QString::fromStdString( + polo_result.unwrap_err().message); + break; + } + qDebug() << "Sent polo successfully"; + + interval += 5; + m_initialCompleted = true; + } + } catch (const std::exception &e) { + qDebug() << "Heartbeat error:" << e.what(); + } + } + + bool initialCompleted() const { return m_initialCompleted; } + +private: + Heartbeat m_hb; + bool m_initialCompleted = false; +}; +#endif // HEARTBEATTHREAD_H \ No newline at end of file diff --git a/src/iDescriptor.h b/src/iDescriptor.h index cd89726..dd52673 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -25,6 +25,7 @@ #include #include +// #include "idevice.h" #include #include #include @@ -186,7 +187,7 @@ struct DeviceInfo { struct iDescriptorDevice { std::string udid; DeviceMonitorThread::IdeviceConnectionType conn_type; - IdeviceProviderHandle *device; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; AfcClientHandle *afcClient; AfcClientHandle *afc2Client; @@ -198,7 +199,7 @@ struct iDescriptorDevice { struct iDescriptorInitDeviceResult { bool success = false; IdeviceFfiError error; - IdeviceProviderHandle *device; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; AfcClientHandle *afcClient; AfcClientHandle *afc2Client; @@ -324,8 +325,8 @@ struct AFCFileTree { std::string currentPath; }; -// AFCFileTree get_file_tree(afc_client_t afcClient, -// const std::string &path = "/"); +AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path = "/"); bool detect_jailbroken(AfcClientHandle *afc); @@ -456,8 +457,8 @@ struct NetworkDevice { QPixmap load_heic(const QByteArray &data); -// QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, -// const char *path); +QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, + const char *path); bool isDarkMode(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ec28c5e..19291ff 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -450,16 +450,16 @@ MainWindow::MainWindow(QWidget *parent) connect(NetworkDeviceManager::sharedInstance(), &NetworkDeviceManager::deviceAdded, this, [this](const NetworkDevice &device) { - // const iDescriptorDevice *idevice = - // AppContext::sharedInstance()->getDeviceByMacAddress( - // device.macAddress); - // if (idevice) { - // qDebug() << "Network device matched to connected device:" - // << QString::fromStdString( - // idevice->deviceInfo.deviceName) - // << "MAC:" << device.macAddress; - // // You can now use 'idevice' as needed - // } + const iDescriptorDevice *idevice = + AppContext::sharedInstance()->getDeviceByMacAddress( + device.macAddress); + if (idevice) { + qDebug() << "Network device matched to connected device:" + << QString::fromStdString( + idevice->deviceInfo.deviceName) + << "MAC:" << device.macAddress; + // You can now use 'idevice' as needed + } // FIXME: both macAddress and udid can be used to get pairing // file diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h index 5aca232..5cf897d 100644 --- a/src/mediapreviewdialog.h +++ b/src/mediapreviewdialog.h @@ -36,7 +36,6 @@ #include #include #include -#include /** * @brief A dialog for previewing images and videos from iOS devices @@ -149,4 +148,4 @@ private: afc_client_t m_afcClient; }; -#endif // MEDIAPREVIEWDIALOG_H \ No newline at end of file +#endif // MEDIAPREVIEWDIALOG_H diff --git a/src/mediastreamer.h b/src/mediastreamer.h index be59d71..0c8eba6 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -25,7 +25,6 @@ #include #include #include -#include QT_BEGIN_NAMESPACE class QTcpSocket; @@ -117,4 +116,4 @@ private: afc_client_t m_afcClient; }; -#endif // MEDIASTREAMER_H \ No newline at end of file +#endif // MEDIASTREAMER_H diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 587f710..0e53c90 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -26,7 +26,6 @@ #include #include #include -#include /** * @brief Singleton manager for MediaStreamer instances @@ -81,4 +80,4 @@ private: QMutex m_streamersMutex; }; -#endif // MEDIASTREAMERMANAGER_H \ No newline at end of file +#endif // MEDIASTREAMERMANAGER_H diff --git a/src/photomodel.cpp b/src/photomodel.cpp index dfb12c7..eb5e688 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -19,7 +19,7 @@ #include "photomodel.h" #include "iDescriptor.h" -#include "mediastreamermanager.h" +// #include "mediastreamermanager.h" #include "servicemanager.h" #include #include @@ -84,30 +84,32 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, { QPixmap thumbnail; - uint64_t fileHandle = 0; + AfcFileHandle *fileHandle = nullptr; - afc_error_t openResult = ServiceManager::safeAfcFileOpen( - device, filePath.toUtf8().constData(), AFC_FOPEN_RDONLY, &fileHandle); + IdeviceFfiError *err = + ServiceManager::safeAfcFileOpen(device, filePath.toUtf8().constData(), + AfcFopenMode::AfcRdOnly, &fileHandle); - if (openResult != AFC_E_SUCCESS || fileHandle == 0) { + if (err || fileHandle == nullptr) { qWarning() << "Failed to open video file for thumbnail:" << filePath; + if (err) { + // idevice_error_free(err); + } return {}; } // Get file size - char **fileInfo = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), &fileInfo); + AfcFileInfo *info = nullptr; + err = ServiceManager::safeAfcGetFileInfo( + device, filePath.toUtf8().constData(), info); uint64_t fileSize = 0; - if (infoResult == AFC_E_SUCCESS && fileInfo) { - for (int i = 0; fileInfo[i]; i += 2) { - if (strcmp(fileInfo[i], "st_size") == 0) { - fileSize = strtoull(fileInfo[i + 1], nullptr, 10); - break; - } + if (!err && info) { + fileSize = info->size; + // afc_file_info_free(info); + if (err) { + // idevice_error_free(err); } - afc_dictionary_free(fileInfo); } if (fileSize == 0) { @@ -127,7 +129,7 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, // Context for streaming read from device struct StreamContext { iDescriptorDevice *device; - uint64_t fileHandle; + AfcFileHandle *fileHandle; uint64_t fileSize; uint64_t currentPos; }; @@ -137,27 +139,31 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, // Custom read function that reads from device on-demand auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { - StreamContext *ctx = static_cast(opaque); + // StreamContext *ctx = static_cast(opaque); - if (ctx->currentPos >= ctx->fileSize) { - return AVERROR_EOF; - } + // if (ctx->currentPos >= ctx->fileSize) { + // return AVERROR_EOF; + // } - uint32_t toRead = - std::min(static_cast(bufSize), - static_cast(ctx->fileSize - ctx->currentPos)); - uint32_t bytesRead = 0; + // uint32_t toRead = + // std::min(static_cast(bufSize), + // static_cast(ctx->fileSize - ctx->currentPos)); + // uint32_t bytesRead = 0; - afc_error_t result = ServiceManager::safeAfcFileRead( - ctx->device, ctx->fileHandle, reinterpret_cast(buf), toRead, - &bytesRead); + // IdeviceFfiError *err = ServiceManager::safeAfcFileRead( + // ctx->device, ctx->fileHandle, reinterpret_cast(buf), + // toRead, &bytesRead); - if (result != AFC_E_SUCCESS || bytesRead == 0) { - return AVERROR(EIO); - } + // if (err || bytesRead == 0) { + // if (err) { + // idevice_error_free(err); + // } + // return AVERROR(EIO); + // } - ctx->currentPos += bytesRead; - return static_cast(bytesRead); + // ctx->currentPos += bytesRead; + // return static_cast(bytesRead); + return 0; }; // Custom seek function using AFC seek @@ -188,11 +194,12 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, return -1; } - // Use AFC seek - afc_error_t result = ServiceManager::safeAfcFileSeek( + IdeviceFfiError *err = ServiceManager::safeAfcFileSeek( ctx->device, ctx->fileHandle, newPos, seekWhence); - if (result != AFC_E_SUCCESS) { + if (err) { + qDebug() << "AFC seek error:" << err->message; + // idevice_error_free(err); return -1; } @@ -615,16 +622,19 @@ void PhotoModel::populatePhotoPaths() QByteArray albumPathBytes = m_albumPath.toUtf8(); const char *albumPathCStr = albumPathBytes.constData(); - char **albumInfo = nullptr; - afc_error_t infoResult = + AfcFileInfo albumInfo = {}; + + IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo(m_device, albumPathCStr, &albumInfo); - if (infoResult != AFC_E_SUCCESS) { + if (err) { qDebug() << "Album path does not exist or cannot be accessed:" - << m_albumPath << "Error:" << infoResult; + << m_albumPath << "Error:" << err->message; + idevice_error_free(err); return; } - if (albumInfo) { - afc_dictionary_free(albumInfo); + // FIXME: should we continue if albumInfo is null? + if (albumInfo.size) { + afc_file_info_free(&albumInfo); } // Fix: Store the QByteArray to keep the C string valid @@ -633,13 +643,12 @@ void PhotoModel::populatePhotoPaths() qDebug() << "Photo directory:" << m_albumPath; qDebug() << "Photo directory C string:" << photoDir; - // Use ServiceManager for thread-safe AFC operations char **files = nullptr; - afc_error_t readResult = - ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); - if (readResult != AFC_E_SUCCESS) { + err = ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); + if (err) { qDebug() << "Failed to read photo directory:" << photoDir - << "Error:" << readResult; + << "Error:" << err->message; + idevice_error_free(err); return; } @@ -663,7 +672,8 @@ void PhotoModel::populatePhotoPaths() m_allPhotos.append(info); } } - afc_dictionary_free(files); + // free(files); + // afc_dictionary_free(files); } // Apply initial filtering and sorting, which will also reset the model @@ -792,74 +802,22 @@ QStringList PhotoModel::getFilteredFilePaths() const // Helper methods QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const { - plist_t info = nullptr; - afc_error_t afc_err = ServiceManager::safeAfcGetFileInfoPlist( - m_device, filePath.toUtf8().constData(), &info); + AfcFileInfo *info = nullptr; + IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo( + m_device, filePath.toUtf8().constData(), info); + if (!err && info) { + uint64_t birthtime_ns = info->st_birthtime; + // Convert nanoseconds since epoch to QDateTime + // The timestamp appears to be in nanoseconds since Unix epoch + uint64_t seconds = birthtime_ns / 1000000000ULL; + QDateTime dateTime = QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - if (afc_err == AFC_E_SUCCESS && info) { - plist_t birthtime_node = plist_dict_get_item(info, "st_birthtime"); - if (birthtime_node && - plist_get_node_type(birthtime_node) == PLIST_UINT) { - uint64_t birthtime_ns = 0; - plist_get_uint_val(birthtime_node, &birthtime_ns); - - // Convert nanoseconds since epoch to QDateTime - // The timestamp appears to be in nanoseconds since Unix epoch - uint64_t seconds = birthtime_ns / 1000000000ULL; - QDateTime dateTime = - QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - - plist_free(info); - if (dateTime.isValid()) { - return dateTime; - } - } - - // Fallback to st_mtime (modification time) if birthtime not available - plist_t mtime_node = plist_dict_get_item(info, "st_mtime"); - if (mtime_node && plist_get_node_type(mtime_node) == PLIST_UINT) { - uint64_t mtime_ns = 0; - plist_get_uint_val(mtime_node, &mtime_ns); - - // Convert nanoseconds since epoch to QDateTime - uint64_t seconds = mtime_ns / 1000000000ULL; - QDateTime dateTime = - QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - - plist_free(info); - if (dateTime.isValid()) { - return dateTime; - } - } - - plist_free(info); - } - - // Final fallback: try to extract date from filename pattern like - // IMG_20231025_143052.jpg - QFileInfo fileInfo(filePath); - QString baseName = fileInfo.baseName(); - - QRegularExpression dateRegex( - R"((\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2}))"); - QRegularExpressionMatch match = dateRegex.match(baseName); - - if (match.hasMatch()) { - int year = match.captured(1).toInt(); - int month = match.captured(2).toInt(); - int day = match.captured(3).toInt(); - int hour = match.captured(4).toInt(); - int minute = match.captured(5).toInt(); - int second = match.captured(6).toInt(); - - QDateTime dateTime(QDate(year, month, day), - QTime(hour, minute, second)); + // afc_file_info_free(info); if (dateTime.isValid()) { return dateTime; } } - // Ultimate fallback: return current time return QDateTime::currentDateTime(); } diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index 1fe4772..d006064 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -18,146 +18,164 @@ */ #include "servicemanager.h" +#include "iDescriptor.h" -afc_error_t -ServiceManager::safeAfcReadDirectory(iDescriptorDevice *device, +IdeviceFfiError * +ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, const char *path, char ***dirs, - std::optional altAfc) + std::optional altAfc) { - return executeAfcOperation( + size_t count = 0; + return executeAfcClientOperation( device, - [path, dirs](afc_client_t client) { - return afc_read_directory(client, path, dirs); + [path, dirs, &count, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_list_directory(client, path, dirs, &count); + /*TODO -1 is unknown error*/ + if (err && (err->code == -1 || err->code == -11)) { + qDebug() << "Reconnecting AFC client for path:" << path; + // afc_client_free(client); + // err = afc_client_connect(device->provider, &client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + + err = afc_list_directory( + const_cast(device)->afcClient, path, + dirs, &count); + } + return err; }, altAfc); } -afc_error_t -ServiceManager::safeAfcGetFileInfo(iDescriptorDevice *device, const char *path, - char ***info, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcGetFileInfo(const iDescriptorDevice *device, + const char *path, AfcFileInfo *info, + std::optional altAfc) { - return executeAfcOperation( + return executeAfcClientOperation( device, - [path, info](afc_client_t client) { - return afc_get_file_info(client, path, info); + [path, info, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_get_file_info(client, path, info); + /*TODO -1 is unknown error*/ + if (err && err->code == -1) { + // afc_client_free(client); + // err = afc_client_connect(device->provider, &client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + + err = afc_get_file_info( + const_cast(device)->afcClient, path, + info); + } + return err; }, altAfc); } -afc_error_t -ServiceManager::safeAfcGetFileInfoPlist(iDescriptorDevice *device, - const char *path, plist_t *info, - std::optional altAfc) +IdeviceFfiError *ServiceManager::safeAfcFileOpen( + const iDescriptorDevice *device, const char *path, AfcFopenMode mode, + AfcFileHandle **handle, std::optional altAfc) { - return executeAfcOperation( + return executeAfcClientOperation( device, - [path, info](afc_client_t client) { - return afc_get_file_info_plist(client, path, info); + [path, mode, handle, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_file_open(client, path, mode, handle); + /*TODO -1 is unknown error*/ + if (err && err->code == -1) { + // afc_client_free(client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + err = afc_file_open( + const_cast(device)->afcClient, path, + mode, handle); + } + return err; }, altAfc); } -afc_error_t ServiceManager::safeAfcFileOpen(iDescriptorDevice *device, - const char *path, - afc_file_mode_t mode, - uint64_t *handle, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileRead(const iDescriptorDevice *device, + AfcFileHandle *handle, uint8_t **data, + uintptr_t length, size_t *bytes_read) { return executeAfcOperation( device, - [path, mode, handle](afc_client_t client) { - return afc_file_open(client, path, mode, handle); + [data, length, bytes_read](AfcFileHandle *handle) { + return afc_file_read(handle, data, length, bytes_read); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileRead(iDescriptorDevice *device, - uint64_t handle, char *data, - uint32_t length, - uint32_t *bytes_read, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileWrite(const iDescriptorDevice *device, + AfcFileHandle *handle, const uint8_t *data, + uint32_t length) { return executeAfcOperation( device, - [handle, data, length, bytes_read](afc_client_t client) { - return afc_file_read(client, handle, data, length, bytes_read); + [data, length](AfcFileHandle *handle) { + return afc_file_write(handle, data, length); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileWrite(iDescriptorDevice *device, - uint64_t handle, const char *data, - uint32_t length, - uint32_t *bytes_written, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileClose(const iDescriptorDevice *device, + AfcFileHandle *handle) { return executeAfcOperation( - device, - [handle, data, length, bytes_written](afc_client_t client) { - return afc_file_write(client, handle, data, length, bytes_written); - }, - altAfc); + device, [](AfcFileHandle *handle) { return afc_file_close(handle); }, + handle); } -afc_error_t ServiceManager::safeAfcFileClose(iDescriptorDevice *device, - uint64_t handle, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileSeek(const iDescriptorDevice *device, + AfcFileHandle *handle, int64_t offset, + int whence) { + off_t *newPos = nullptr; return executeAfcOperation( device, - [handle](afc_client_t client) { - return afc_file_close(client, handle); + [offset, whence, newPos](AfcFileHandle *handle) { + return afc_file_seek(handle, offset, whence, newPos); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileSeek(iDescriptorDevice *device, - uint64_t handle, int64_t offset, - int whence, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileTell(const iDescriptorDevice *device, + AfcFileHandle *handle, off_t *position) { return executeAfcOperation( device, - [handle, offset, whence](afc_client_t client) { - return afc_file_seek(client, handle, offset, whence); + [position](AfcFileHandle *handle) { + return afc_file_tell(handle, position); }, - altAfc); -} - -afc_error_t ServiceManager::safeAfcFileTell(iDescriptorDevice *device, - uint64_t handle, uint64_t *position, - std::optional altAfc) -{ - return executeAfcOperation( - device, - [handle, position](afc_client_t client) { - return afc_file_tell(client, handle, position); - }, - altAfc); + handle); } QByteArray -ServiceManager::safeReadAfcFileToByteArray(iDescriptorDevice *device, - const char *path, - std::optional altAfc) +ServiceManager::safeReadAfcFileToByteArray(const iDescriptorDevice *device, + const char *path) { - return executeOperation( - device, - [path](afc_client_t client) -> QByteArray { - return read_afc_file_to_byte_array(client, path); - }, - altAfc); + return executeOperation(device, [path, device]() -> QByteArray { + return read_afc_file_to_byte_array(device, path); + }); } -AFCFileTree ServiceManager::safeGetFileTree(iDescriptorDevice *device, +AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, const std::string &path, - std::optional altAfc) + bool checkDir) { return executeOperation( - device, - [path](afc_client_t client) -> AFCFileTree { - return get_file_tree(client, path.c_str()); - }, - altAfc); + device, [path, device, checkDir]() -> AFCFileTree { + return get_file_tree(device, checkDir, path); + }); } \ No newline at end of file diff --git a/src/servicemanager.h b/src/servicemanager.h index 446489a..27227ad 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -23,7 +23,6 @@ #include "iDescriptor.h" #include #include -#include #include #include @@ -39,9 +38,10 @@ class ServiceManager { public: template - static T executeOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return T{}; // Return default-constructed value for the type @@ -61,16 +61,19 @@ public: } // Determine which client to use - afc_client_t client = altAfc ? *altAfc : device->afcClient; + AfcClientHandle *client = altAfc ? *altAfc : device->afcClient; + return operation(client); } template - static T executeOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { + qDebug() << "[executeOperation] Device or mutex is null"; return T{}; // Return default-constructed value for the type } @@ -78,21 +81,24 @@ public: // Double-check device is still valid after acquiring lock if (!device->afcClient) { + qDebug() << "[executeOperation] AFC client is null"; return T{}; } if (altAfc && !*altAfc) { // altAfc was explicitly provided but is null, which is an // invalid state. + qDebug() << "[executeOperation] altAfc is null"; return T{}; } return operation(); } template - static T executeOperation(iDescriptorDevice *device, - std::function operation, T failureValue, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, T failureValue, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return failureValue; @@ -115,8 +121,9 @@ public: } static void - executeOperation(iDescriptorDevice *device, std::function operation, - std::optional altAfc = std::nullopt) + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return; @@ -138,84 +145,109 @@ public: operation(); } - static afc_error_t - executeAfcOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static IdeviceFfiError *executeAfcOperation( + const iDescriptorDevice *device, + std::function operation, + AfcFileHandle *handle) { try { if (!device || !device->mutex) { - return AFC_E_UNKNOWN_ERROR; + // FIXME: we have to free error + return new IdeviceFfiError{1, "DEVICE_OR_MUTEX_IS_NULL"}; } std::lock_guard lock(*device->mutex); // Double-check device is still valid after acquiring lock if (!device->afcClient) { - return AFC_E_UNKNOWN_ERROR; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; + } + + if (!handle) { + return new IdeviceFfiError{1, "FILE HANDLE IS NULL"}; + } + + return operation(handle); + } catch (const std::exception &e) { + qDebug() << "Exception in executeAfcOperation:" << e.what(); + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; + } + } + + static IdeviceFfiError *executeAfcClientOperation( + const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) + { + try { + if (!device || !device->mutex) { + // FIXME: we have to free error + qDebug() + << "[executeAfcClientOperation] Device or mutex is null"; + return new IdeviceFfiError{1, "DEVICE_OR_MUTEX_IS_NULL"}; + } + + std::lock_guard lock(*device->mutex); + + // Double-check device is still valid after acquiring lock + if (!device->afcClient) { + qDebug() << "[executeAfcClientOperation] AFC client is null"; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; } if (altAfc && !*altAfc) { // altAfc was explicitly provided but is null, which is an // invalid state. - return AFC_E_INVALID_ARG; + qDebug() << "[executeAfcClientOperation] altAfc is null"; + return new IdeviceFfiError{1, "ALT_AFC_CLIENT_IS_NULL"}; } // Determine which client to use - afc_client_t client = altAfc ? *altAfc : device->afcClient; + AfcClientHandle *client = altAfc ? *altAfc : device->afcClient; return operation(client); } catch (const std::exception &e) { qDebug() << "Exception in executeAfcOperation:" << e.what(); - return AFC_E_UNKNOWN_ERROR; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; } } // Specific AFC operation wrappers - static afc_error_t - safeAfcReadDirectory(iDescriptorDevice *device, const char *path, - char ***dirs, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcGetFileInfo(iDescriptorDevice *device, const char *path, - char ***info, - std::optional altAfc = std::nullopt); + static IdeviceFfiError *safeAfcReadDirectory( + const iDescriptorDevice *device, const char *path, char ***dirs, + std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcGetFileInfoPlist(iDescriptorDevice *device, const char *path, - plist_t *info, - std::optional altAfc = std::nullopt); + static IdeviceFfiError * + safeAfcGetFileInfo(const iDescriptorDevice *device, const char *path, + AfcFileInfo *info, + std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileOpen(iDescriptorDevice *device, const char *path, - afc_file_mode_t mode, uint64_t *handle, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileRead(iDescriptorDevice *device, uint64_t handle, char *data, - uint32_t length, uint32_t *bytes_read, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileWrite(iDescriptorDevice *device, uint64_t handle, - const char *data, uint32_t length, uint32_t *bytes_written, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileClose(iDescriptorDevice *device, uint64_t handle, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileSeek(iDescriptorDevice *device, uint64_t handle, int64_t offset, - int whence, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileTell(iDescriptorDevice *device, uint64_t handle, - uint64_t *position, - std::optional altAfc = std::nullopt); + static IdeviceFfiError * + safeAfcFileOpen(const iDescriptorDevice *device, const char *path, + AfcFopenMode mode, AfcFileHandle **handle, + std::optional altAfc = std::nullopt); + static IdeviceFfiError *safeAfcFileRead(const iDescriptorDevice *device, + AfcFileHandle *handle, + uint8_t **data, uintptr_t length, + size_t *bytes_read); + static IdeviceFfiError *safeAfcFileWrite(const iDescriptorDevice *device, + AfcFileHandle *handle, + const uint8_t *data, + uint32_t length); + static IdeviceFfiError *safeAfcFileClose(const iDescriptorDevice *device, + AfcFileHandle *handle); + static IdeviceFfiError *safeAfcFileSeek(const iDescriptorDevice *device, + AfcFileHandle *handle, + int64_t offset, int whence); + static IdeviceFfiError *safeAfcFileTell(const iDescriptorDevice *device, + AfcFileHandle *handle, + off_t *position); // Utility functions - static QByteArray safeReadAfcFileToByteArray( - iDescriptorDevice *device, const char *path, - std::optional altAfc = std::nullopt); - static AFCFileTree - safeGetFileTree(iDescriptorDevice *device, const std::string &path = "/", - std::optional altAfc = std::nullopt); + static QByteArray + safeReadAfcFileToByteArray(const iDescriptorDevice *device, + const char *path); + static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, + const std::string &path, bool checkDir); }; #endif // SERVICEMANAGER_H diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp new file mode 100644 index 0000000..65debd0 --- /dev/null +++ b/src/zloadingwidget.cpp @@ -0,0 +1,37 @@ +#include "zloadingwidget.h" + +#include "qprocessindicator.h" +#include +#include + +ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) : QWidget{parent} +{ + m_loadingIndicator = new QProcessIndicator(); + m_loadingIndicator->setType(QProcessIndicator::line_rotate); + m_loadingIndicator->setFixedSize(64, 32); + if (start) { + m_loadingIndicator->start(); + } + + QHBoxLayout *loadingLayout = new QHBoxLayout(); + loadingLayout->setSpacing(1); + + loadingLayout->addWidget(m_loadingIndicator); + setLayout(loadingLayout); +} + +void ZLoadingWidget::stop() +{ + if (m_loadingIndicator) { + m_loadingIndicator->stop(); + } +} + +ZLoadingWidget::~ZLoadingWidget() +{ + if (m_loadingIndicator) { + m_loadingIndicator->stop(); + delete m_loadingIndicator; + m_loadingIndicator = nullptr; + } +} \ No newline at end of file diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h new file mode 100644 index 0000000..1c8050e --- /dev/null +++ b/src/zloadingwidget.h @@ -0,0 +1,19 @@ +#ifndef ZLOADINGWIDGET_H +#define ZLOADINGWIDGET_H + +#include + +class ZLoadingWidget : public QWidget +{ + Q_OBJECT +public: + explicit ZLoadingWidget(bool start = true, QWidget *parent = nullptr); + ~ZLoadingWidget(); + void stop(); + +private: + class QProcessIndicator *m_loadingIndicator = nullptr; +signals: +}; + +#endif // ZLOADINGWIDGET_H