From 4baecf84ca931a7dd9aa1a132ebb5035412210d7 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Mon, 6 Aug 2018 20:52:47 -0400 Subject: [PATCH] Qt: add option to update RetroArch (Windows only for now) --- CHANGES.md | 3 +- Makefile.common | 4 +- intl/msg_hash_ja.h | 14 ++ intl/msg_hash_us.h | 14 ++ msg_hash.h | 8 + qb/config.libs.sh | 6 +- ui/drivers/qt/ui_qt_window.cpp | 368 ++++++++++++++++++++++++++++++++- ui/drivers/ui_qt.cpp | 7 + ui/drivers/ui_qt.h | 19 ++ 9 files changed, 427 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d3f4ff1797..0ddf01b29a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,10 +16,11 @@ - LOCALIZATION: Update Spanish translation. - MIDI: Add MIDI support to the libretro API. Dosbox is the first proof of concept core implementing libretro MIDI. - MIDI: Add a Windows driver for MIDI, based on winmm. -- MENU/QT/WIMP: QT QSlider styling for Dark Theme. +- MENU/QT/WIMP: Qt QSlider styling for Dark Theme. - MENU/QT/WIMP: Remove button ghostly inside highlighting. - MENU/QT/WIMP: Initial grid view. - MENU/QT/WIMP: Drag&drop to add new playlist items, add option to add/edit/delete playlists. +- MENU/QT/WIMP: Add menu option to update RetroArch (Windows only for now). - METAL: Initial work-in-progress video driver for Metal. macOS-only right now, and currently requires macOS 10.13. - METAL: Supports XMB/MaterialUI, has a menu display driver. Has a font rendering driver. - METAL/SLANG: Slang shaders should be compatible with Metal video driver. diff --git a/Makefile.common b/Makefile.common index d32048cbb4..3cd2c39654 100644 --- a/Makefile.common +++ b/Makefile.common @@ -345,9 +345,9 @@ MOC_HEADERS += ui/drivers/ui_qt.h \ ui/drivers/qt/ui_qt_load_core_window.h \ ui/drivers/qt/flowlayout.h -DEFINES += $(QT5CORE_CFLAGS) $(QT5GUI_CFLAGS) $(QT5WIDGETS_CFLAGS) $(QT5CONCURRENT_CFLAGS) -DHAVE_MAIN +DEFINES += $(QT5CORE_CFLAGS) $(QT5GUI_CFLAGS) $(QT5WIDGETS_CFLAGS) $(QT5CONCURRENT_CFLAGS) $(QT5NETWORK_CFLAGS) -DHAVE_MAIN #DEFINES += $(QT5WEBENGINE_CFLAGS) -LIBS += $(QT5CORE_LIBS) $(QT5GUI_LIBS) $(QT5WIDGETS_LIBS) $(QT5CONCURRENT_LIBS) +LIBS += $(QT5CORE_LIBS) $(QT5GUI_LIBS) $(QT5WIDGETS_LIBS) $(QT5CONCURRENT_LIBS) $(QT5NETWORK_LIBS) #LIBS += $(QT5WEBENGINE_LIBS) NEED_CXX_LINKER = 1 diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index e9c47d8414..264689ad8c 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -3488,6 +3488,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THEME_CUSTOM, "カスタム...") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_TITLE, "設定") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS, + "ツール(&T)") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP, "ヘルプ(&H)") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT, @@ -3534,6 +3536,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_WARNING, "警告") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ERROR, "エラー") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR, + "ネットワークエラー") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_RESTART_TO_TAKE_EFFECT, "変更はRetroArchを再起動した後に反映されます。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_LOG, @@ -3564,6 +3568,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY, "ファイルは空きです。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED, "ファイルを読み込みのために開けません。") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_WRITE_OPEN_FAILED, + "ファイルを書き込みのために開けません。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, "ファイルは存在しません。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_SUGGEST_LOADED_CORE_FIRST, @@ -3656,6 +3662,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_QUESTION, "質問") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE, "ファイル削除に失敗しました。") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE, + "ファイルの名前変更に失敗しました。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_GATHERING_LIST_OF_FILES, "ファイルの一覧を構築しています...") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ADDING_FILES_TO_PLAYLIST, @@ -3696,3 +3704,9 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_UPDATE_PLAYLIST_ENTRY, "プレイリストエントリーを更新するに失敗しました。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS, "必須フィールドがすべて入力されていることを確認してください。") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_NIGHTLY, + "RetroArchをアップデート (nightly)") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FINISHED, + "更新に成功しました。変更を適用にするには、RetroArchを再起動する必要があります。") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FAILED, + "更新に失敗しました。") diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index fdf7e53f45..6d7213d99d 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -3764,6 +3764,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THEME_CUSTOM, "Custom...") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_TITLE, "Options") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS, + "&Tools") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP, "&Help") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT, @@ -3810,6 +3812,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_WARNING, "Warning") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ERROR, "Error") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR, + "Network Error") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_RESTART_TO_TAKE_EFFECT, "Please restart the program for the changes to take effect.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_LOG, @@ -3848,6 +3852,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY, "File is empty.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED, "Could not open file for reading.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_WRITE_OPEN_FAILED, + "Could not open file for writing.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, "File does not exist.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_SUGGEST_LOADED_CORE_FIRST, @@ -4186,6 +4192,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_QUESTION, "Question") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE, "Could not delete file.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE, + "Could not rename file.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_GATHERING_LIST_OF_FILES, "Gathering list of files...") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ADDING_FILES_TO_PLAYLIST, @@ -4226,3 +4234,9 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_UPDATE_PLAYLIST_ENTRY, "Error updating playlist entry.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS, "Please fill out all required fields.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_NIGHTLY, + "Update RetroArch (nightly)") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FINISHED, + "RetroArch updated successfully. Please restart the application for the changes to take effect.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FAILED, + "Update failed.") diff --git a/msg_hash.h b/msg_hash.h index f5adeca60b..14c7d03b9a 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1898,6 +1898,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_SHOW_HIDDEN_FILES, MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_LIST_MAX_COUNT, MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_GRID_MAX_COUNT, + MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS, MENU_ENUM_LABEL_VALUE_QT_MENU_HELP, MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER, MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART, @@ -1934,6 +1935,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_FILE_PATH_IS_BLANK, MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY, MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED, + MENU_ENUM_LABEL_VALUE_QT_FILE_WRITE_OPEN_FAILED, MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, MENU_ENUM_LABEL_VALUE_QT_ZOOM, MENU_ENUM_LABEL_VALUE_QT_VIEW, @@ -1947,6 +1949,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM, MENU_ENUM_LABEL_VALUE_QT_QUESTION, MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE, + MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE, MENU_ENUM_LABEL_VALUE_QT_GATHERING_LIST_OF_FILES, MENU_ENUM_LABEL_VALUE_QT_ADDING_FILES_TO_PLAYLIST, MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY, @@ -1968,6 +1971,10 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS, MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT, MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_DOCUMENTATION, + MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR, + MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_NIGHTLY, + MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FINISHED, + MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FAILED, MENU_LABEL(MIDI_INPUT), MENU_LABEL(MIDI_OUTPUT), @@ -2045,6 +2052,7 @@ enum msg_hash_enums MSG_CHEAT_SEARCH_ADD_MATCH_SUCCESS, MSG_CHEAT_SEARCH_ADD_MATCH_FAIL, MSG_CHEAT_SEARCH_DELETE_MATCH_SUCCESS, + MSG_LAST }; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index f4499099ff..1a1da11dde 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -279,15 +279,17 @@ if [ "$HAVE_QT" != 'no' ] && [ "$MOC_PATH" != 'none' ]; then check_pkgconf QT5GUI Qt5Gui 5.2 check_pkgconf QT5WIDGETS Qt5Widgets 5.2 check_pkgconf QT5CONCURRENT Qt5Concurrent 5.2 + check_pkgconf QT5NETWORK Qt5Network 5.2 #check_pkgconf QT5WEBENGINE Qt5WebEngine 5.4 check_val '' QT5CORE -lQt5Core QT5CORE check_val '' QT5GUI -lQt5Gui QT5GUI check_val '' QT5WIDGETS -lQt5Widgets QT5WIDGETS - check_val '' QT5CONCURRENT -lQt5Widgets QT5CONCURRENT + check_val '' QT5CONCURRENT -lQt5Concurrent QT5CONCURRENT + check_val '' QT5NETWORK -lQt5Network QT5NETWORK #check_val '' QT5WEBENGINE -lQt5WebEngine QT5WEBENGINE - if [ "$HAVE_QT5CORE" = "no" ] || [ "$HAVE_QT5GUI" = "no" ] || [ "$HAVE_QT5WIDGETS" = "no" ] || [ "$HAVE_QT5CONCURRENT" = "no" ]; then + if [ "$HAVE_QT5CORE" = "no" ] || [ "$HAVE_QT5GUI" = "no" ] || [ "$HAVE_QT5WIDGETS" = "no" ] || [ "$HAVE_QT5CONCURRENT" = "no" ] || [ "$HAVE_QT5NETWORK" = "no" ]; then die : 'Notice: Not building Qt support, required libraries were not found.' HAVE_QT=no else diff --git a/ui/drivers/qt/ui_qt_window.cpp b/ui/drivers/qt/ui_qt_window.cpp index 1678a1512d..65eee52013 100644 --- a/ui/drivers/qt/ui_qt_window.cpp +++ b/ui/drivers/qt/ui_qt_window.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "../ui_qt.h" #include "ui_qt_load_core_window.h" @@ -56,10 +57,12 @@ extern "C" { #include "../../../content.h" #include "../../../menu/menu_driver.h" #include "../../../tasks/tasks_internal.h" +#include "../../../config.def.h" #include #include #include #include +#include #include } @@ -81,6 +84,10 @@ extern "C" { #define KATAKANA_START 0x30A1U #define KATAKANA_END 0x30F6U #define HIRA_KATA_OFFSET (KATAKANA_START - HIRAGANA_START) +#define USER_AGENT "RetroArch-WIMP/1.0" +#define DOCS_URL "http://docs.libretro.com/" +#define PARTIAL_EXTENSION ".partial" +#define TEMP_EXTENSION ".update_tmp" static ui_window_qt_t ui_window = {0}; @@ -908,6 +915,10 @@ MainWindow::MainWindow(QWidget *parent) : ,m_allPlaylistsGridMaxCount(0) ,m_playlistEntryDialog(NULL) ,m_statusMessageElapsedTimer() + ,m_networkManager(new QNetworkAccessManager(this)) + ,m_updateProgressDialog(new QProgressDialog()) + ,m_updateFile() + ,m_updateReply() { settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); @@ -927,6 +938,8 @@ MainWindow::MainWindow(QWidget *parent) : qRegisterMetaType >("ThumbnailWidget"); + m_updateProgressDialog->cancel(); + m_gridProgressWidget = new QWidget(); gridProgressLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PROGRESS), m_gridProgressWidget); @@ -1146,6 +1159,8 @@ MainWindow::MainWindow(QWidget *parent) : #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) resizeDocks(QList() << m_searchDock, QList() << 1, Qt::Vertical); #endif + + removeUpdateTempFiles(); } MainWindow::~MainWindow() @@ -1160,6 +1175,31 @@ MainWindow::~MainWindow() removeGridItems(); } +void MainWindow::removeUpdateTempFiles() +{ + /* a QDir with no path means the current working directory */ + QDir dir; + QStringList dirList = dir.entryList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name); + int i; + + for (i = 0; i < dirList.count(); i++) + { + QString path(dir.path() + "/" + dirList.at(i)); + QFile file(path); + + if (path.endsWith(TEMP_EXTENSION)) + { + QByteArray pathArray = path.toUtf8(); + const char *pathData = pathArray.constData(); + + if (file.remove()) + RARCH_LOG("[Qt]: removed temporary update file %s\n", pathData); + else + RARCH_LOG("[Qt]: could not remove temporary update file %s\n", pathData); + } + } +} + void MainWindow::onPlaylistFilesDropped(QStringList files) { addFilesToPlaylist(files); @@ -1215,7 +1255,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (currentPlaylistPath == ALL_PLAYLISTS_TOKEN) { - ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CANNOT_ADD_TO_ALL_PLAYLISTS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CANNOT_ADD_TO_ALL_PLAYLISTS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } @@ -1239,7 +1279,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (selectedName.isEmpty() || selectedPath.isEmpty() || selectedDatabase.isEmpty()) { - ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } @@ -1269,7 +1309,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) else if (files.count() == 1) { /* If adding a single file, tell user that it doesn't exist. */ - ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } } @@ -1469,11 +1509,11 @@ void MainWindow::showWelcomeScreen() "Documentation for RetroArch, libretro and cores:
\n" "https://docs.libretro.com/"); - if (!ui_window.qtWindow->settings()->value("show_welcome_screen", true).toBool()) + if (!m_settings->value("show_welcome_screen", true).toBool()) return; - if (!ui_window.qtWindow->showMessageBox(welcomeText, MainWindow::MSGBOX_TYPE_INFO, Qt::ApplicationModal)) - ui_window.qtWindow->settings()->setValue("show_welcome_screen", false); + if (!showMessageBox(welcomeText, MainWindow::MSGBOX_TYPE_INFO, Qt::ApplicationModal)) + m_settings->setValue("show_welcome_screen", false); } @@ -1784,7 +1824,7 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) if (!updateCurrentPlaylistEntry(contentHash)) { - ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_UPDATE_PLAYLIST_ENTRY), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_UPDATE_PLAYLIST_ENTRY), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } } @@ -1966,12 +2006,12 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) { if (currentPlaylistFile.exists()) { - if (ui_window.qtWindow->showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST)).arg(selectedItem->text()), MainWindow::MSGBOX_TYPE_QUESTION, Qt::ApplicationModal, false)) + if (showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST)).arg(selectedItem->text()), MainWindow::MSGBOX_TYPE_QUESTION, Qt::ApplicationModal, false)) { if (currentPlaylistFile.remove()) reloadPlaylists(); else - ui_window.qtWindow->showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); } } } @@ -2823,7 +2863,7 @@ void MainWindow::deleteCurrentPlaylistItem() if (!ok) return; - if (!ui_window.qtWindow->showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM)).arg(contentHash["label"]), MainWindow::MSGBOX_TYPE_QUESTION, Qt::ApplicationModal, false)) + if (!showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM)).arg(contentHash["label"]), MainWindow::MSGBOX_TYPE_QUESTION, Qt::ApplicationModal, false)) return; playlist = playlist_init(playlistData, COLLECTION_SIZE); @@ -4587,7 +4627,313 @@ void MainWindow::showAbout() void MainWindow::showDocs() { - QDesktopServices::openUrl(QUrl("http://docs.libretro.com/")); + QDesktopServices::openUrl(QUrl(DOCS_URL)); +} + +void MainWindow::onUpdateNetworkError(QNetworkReply::NetworkError code) +{ + QNetworkReply *reply = m_updateReply.data(); + QByteArray errorStringArray; + const char *errorStringData = NULL; + + m_updateProgressDialog->reset(); + + if (!reply) + return; + + errorStringArray = reply->errorString().toUtf8(); + errorStringData = errorStringArray.constData(); + + RARCH_ERR("[Qt]: Network error code %d received: %s\n", code, errorStringData); + + /* Deleting the reply here seems to cause a strange heap-use-after-free crash. */ + /* + reply->disconnect(); + reply->abort(); + reply->deleteLater(); + */ +} + +void MainWindow::onUpdateNetworkSslErrors(const QList &errors) +{ + QNetworkReply *reply = m_updateReply.data(); + int i; + + if (!reply) + return; + + for (i = 0; i < errors.count(); i++) + { + const QSslError &error = errors.at(i); + QString string = QString("Ignoring SSL error code ") + QString::number(error.error()) + ": " + error.errorString(); + QByteArray stringArray = string.toUtf8(); + const char *stringData = stringArray.constData(); + RARCH_ERR("[Qt]: %s\n", stringData); + } + + /* ignore all SSL errors for now, like self-signed, expired etc. */ + reply->ignoreSslErrors(); +} + +void MainWindow::onUpdateDownloadCanceled() +{ + m_updateProgressDialog->reset(); +} + +void MainWindow::onRetroArchUpdateDownloadFinished() +{ + QNetworkReply *reply = m_updateReply.data(); + QNetworkReply::NetworkError error; + int code; + + m_updateProgressDialog->reset(); + + if (!reply) + return; + + error = reply->error(); + code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (m_updateFile.isOpen()) + m_updateFile.close(); + + if (code != 200) + { + showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR)) + ": HTTP Code " + QString::number(code), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt]: RetroArch update failed with HTTP status code: %d\n", code); + reply->disconnect(); + reply->abort(); + reply->deleteLater(); + return; + } + + if (error == QNetworkReply::NoError) + { + int index = m_updateFile.fileName().lastIndexOf(PARTIAL_EXTENSION); + QString newFileName = m_updateFile.fileName().left(index); + QFile newFile(newFileName); + + /* rename() requires the old file to be deleted first if it exists */ + if (newFile.exists() && !newFile.remove()) + RARCH_ERR("[Qt]: RetroArch update finished, but old file could not be deleted.\n"); + else + { + if (!m_updateFile.rename(newFileName)) + RARCH_ERR("[Qt]: RetroArch update finished, but temp file could not be renamed.\n"); + else + { + RARCH_LOG("[Qt]: RetroArch update finished downloading successfully.\n"); + + extractArchive(newFileName); + } + } + } + else + { + QByteArray errorArray = reply->errorString().toUtf8(); + const char *errorData = errorArray.constData(); + + RARCH_ERR("[Qt]: RetroArch update ended prematurely: %s\n", errorData); + showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR)) + ": Code " + QString::number(code) + ": " + errorData, MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + } + + reply->disconnect(); + reply->close(); + reply->deleteLater(); +} + +static void extractCB(void *task_data, void *user_data, const char *err) +{ + decompress_task_data_t *dec = (decompress_task_data_t*)task_data; + MainWindow *mainwindow = (MainWindow*)user_data; + + if (err) + RARCH_ERR("%s", err); + + if (dec) + { + if (filestream_exists(dec->source_file)) + filestream_delete(dec->source_file); + + free(dec->source_file); + free(dec); + } + + mainwindow->onUpdateRetroArchFinished(string_is_empty(err)); +} + +void MainWindow::onUpdateRetroArchFinished(bool success) +{ + m_updateProgressDialog->reset(); + + if (!success) + { + RARCH_ERR("[Qt]: RetroArch update failed.\n"); + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FAILED), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + return; + } + + RARCH_LOG("[Qt]: RetroArch update finished successfully.\n"); + + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FINISHED), MainWindow::MSGBOX_TYPE_INFO, Qt::ApplicationModal, false); +} + +int MainWindow::extractArchive(QString path) +{ + QByteArray pathArray = path.toUtf8(); + const char *file = pathArray.constData(); + file_archive_transfer_t state; + struct archive_extract_userdata userdata; + struct string_list *file_list = file_archive_get_file_list(file, NULL); + bool returnerr = true; + unsigned i; + + if (!file_list || file_list->size == 0) + { + showMessageBox("Error: Archive is empty.", MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt]: Downloaded archive is empty?\n"); + return -1; + } + + for (i = 0; i < file_list->size; i++) + { + QFile fileObj(file_list->elems[i].data); + + if (fileObj.exists()) + { + if (!fileObj.remove()) + { + /* if we cannot delete the existing file to update it, rename it for now and delete later */ + QFile fileTemp(fileObj.fileName() + TEMP_EXTENSION); + + if (fileTemp.exists()) + { + if (!fileTemp.remove()) + { + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt]: Could not delete file: %s\n", file_list->elems[i].data); + return -1; + } + } + + if (!fileObj.rename(fileTemp.fileName())) + { + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt]: Could not rename file: %s\n", file_list->elems[i].data); + return -1; + } + } + } + } + + string_list_free(file_list); + + memset(&state, 0, sizeof(state)); + memset(&userdata, 0, sizeof(userdata)); + + state.type = ARCHIVE_TRANSFER_INIT; + + m_updateProgressDialog->setWindowModality(Qt::NonModal); + m_updateProgressDialog->setMinimumDuration(0); + m_updateProgressDialog->setRange(0, 0); + m_updateProgressDialog->setAutoClose(true); + m_updateProgressDialog->setAutoReset(true); + m_updateProgressDialog->setValue(0); + m_updateProgressDialog->setLabelText(QString(msg_hash_to_str(MSG_EXTRACTING)) + "..."); + m_updateProgressDialog->setCancelButtonText(QString()); + m_updateProgressDialog->show(); + + if (!task_push_decompress(file, ".", + NULL, NULL, NULL, + extractCB, this)) + { + m_updateProgressDialog->reset(); + return -1; + } + + return returnerr; +} + +void MainWindow::onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + QNetworkReply *reply = m_updateReply.data(); + int progress = (bytesReceived / (float)bytesTotal) * 100.0f; + + if (!reply) + return; + + m_updateProgressDialog->setValue(progress); +} + +void MainWindow::onUpdateDownloadReadyRead() +{ + QNetworkReply *reply = m_updateReply.data(); + + if (!reply) + return; + + m_updateFile.write(reply->readAll()); +} + +void MainWindow::updateRetroArchNightly() +{ + QUrl url(QUrl(buildbot_server_url).resolved(QUrl("../RetroArch_update.zip"))); + QNetworkRequest request(url); + QNetworkReply *reply = NULL; + QByteArray urlArray = url.toString().toUtf8(); + const char *urlData = urlArray.constData(); + + if (m_updateFile.isOpen()) + { + RARCH_ERR("[Qt]: File is already open.\n"); + return; + } + else + { + QString fileName = QFileInfo(url.toString()).fileName() + PARTIAL_EXTENSION; + QByteArray fileNameArray = fileName.toUtf8(); + const char *fileNameData = fileNameArray.constData(); + + m_updateFile.setFileName(fileName); + + if (!m_updateFile.open(QIODevice::WriteOnly)) + { + showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_WRITE_OPEN_FAILED), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt]: Could not open file for writing: %s\n", fileNameData); + return; + } + } + + RARCH_LOG("[Qt]: Starting update of RetroArch...\n"); + RARCH_LOG("[Qt]: Downloading URL %s\n", urlData); + + request.setHeader(QNetworkRequest::UserAgentHeader, USER_AGENT); + + m_updateProgressDialog->setWindowModality(Qt::NonModal); + m_updateProgressDialog->setMinimumDuration(0); + m_updateProgressDialog->setRange(0, 100); + m_updateProgressDialog->setAutoClose(true); + m_updateProgressDialog->setAutoReset(true); + m_updateProgressDialog->setValue(0); + m_updateProgressDialog->setLabelText(QString(msg_hash_to_str(MSG_DOWNLOADING)) + "..."); + m_updateProgressDialog->setCancelButtonText(tr("Cancel")); + m_updateProgressDialog->show(); + + m_updateReply = m_networkManager->get(request); + reply = m_updateReply.data(); + + /* make sure any previous connection is removed first */ + disconnect(m_updateProgressDialog, SIGNAL(canceled()), reply, SLOT(abort())); + disconnect(m_updateProgressDialog, SIGNAL(canceled()), m_updateProgressDialog, SLOT(reset())); + connect(m_updateProgressDialog, SIGNAL(canceled()), reply, SLOT(abort())); + connect(m_updateProgressDialog, SIGNAL(canceled()), m_updateProgressDialog, SLOT(reset())); + + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onUpdateNetworkError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(sslErrors(const QList&)), this, SLOT(onUpdateNetworkSslErrors(const QList&))); + connect(reply, SIGNAL(finished()), this, SLOT(onRetroArchUpdateDownloadFinished())); + connect(reply, SIGNAL(readyRead()), this, SLOT(onUpdateDownloadReadyRead())); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onUpdateDownloadProgress(qint64, qint64))); + } const QPixmap getInvader() diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 6f33952208..d398475856 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -224,6 +224,8 @@ static void* ui_companion_qt_init(void) QMenu *editMenu = NULL; QMenu *viewMenu = NULL; QMenu *viewClosedDocksMenu = NULL; + QMenu *toolsMenu = NULL; + QMenu *updaterMenu = NULL; QMenu *helpMenu = NULL; QRect desktopRect; QDockWidget *thumbnailDock = NULL; @@ -323,6 +325,11 @@ static void* ui_companion_qt_init(void) viewMenu->addSeparator(); viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS), mainwindow->viewOptionsDialog(), SLOT(showDialog())); + toolsMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS)); + updaterMenu = toolsMenu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ONLINE_UPDATER)); +#ifdef Q_OS_WIN + updaterMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_NIGHTLY), mainwindow, SLOT(updateRetroArchNightly())); +#endif helpMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP)); helpMenu->addAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_DOCUMENTATION)), mainwindow, SLOT(showDocs())); helpMenu->addAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT)) + "...", mainwindow, SLOT(showAbout())); diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 40b3f7635c..98bbf74d46 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -35,6 +35,8 @@ #include #include #include +#include +#include extern "C" { #include @@ -67,6 +69,9 @@ class QScrollArea; class QSlider; class QDragEnterEvent; class QDropEvent; +class QNetworkAccessManager; +class QNetworkReply; +class QProgressDialog; class LoadCoreWindow; class MainWindow; class ThumbnailWidget; @@ -392,6 +397,8 @@ public slots: void onFileDropWidgetContextMenuRequested(const QPoint &pos); void showAbout(); void showDocs(); + void updateRetroArchNightly(); + void onUpdateRetroArchFinished(bool success); private slots: void onLoadCoreClicked(const QStringList &extensionFilters = QStringList()); @@ -423,6 +430,12 @@ private slots: void onGridItemDoubleClicked(); void onGridItemClicked(); void onPlaylistFilesDropped(QStringList files); + void onUpdateNetworkError(QNetworkReply::NetworkError code); + void onUpdateNetworkSslErrors(const QList &errors); + void onRetroArchUpdateDownloadFinished(); + void onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onUpdateDownloadReadyRead(); + void onUpdateDownloadCanceled(); private: void setCurrentCoreLabel(); @@ -433,6 +446,8 @@ private: void loadImageDeferred(GridItem *item, QString path); void calcGridItemSize(GridItem *item, int zoomValue); bool updateCurrentPlaylistEntry(const QHash &contentHash); + int extractArchive(QString path); + void removeUpdateTempFiles(); QVector > getPlaylistItems(QString pathString); LoadCoreWindow *m_loadCoreWindow; @@ -493,6 +508,10 @@ private: int m_allPlaylistsGridMaxCount; PlaylistEntryDialog *m_playlistEntryDialog; QElapsedTimer m_statusMessageElapsedTimer; + QNetworkAccessManager *m_networkManager; + QProgressDialog *m_updateProgressDialog; + QFile m_updateFile; + QPointer m_updateReply; protected: void closeEvent(QCloseEvent *event);