From f4a45a4dd9d97e771cf62d5cb2a949386b989a57 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 27 Jun 2024 00:12:53 +0000 Subject: [PATCH 01/12] Fetch translations from Crowdin --- intl/msg_hash_fr.h | 62 +++++++++++++++++++++++++++++++++++++++++++- intl/msg_hash_ja.h | 64 ++++++++++++++++++++++++++++++++++++++++++++-- intl/msg_hash_ru.h | 14 +++++++++- 3 files changed, 136 insertions(+), 4 deletions(-) diff --git a/intl/msg_hash_fr.h b/intl/msg_hash_fr.h index c794a75f129..1e4357fbce8 100644 --- a/intl/msg_hash_fr.h +++ b/intl/msg_hash_fr.h @@ -1196,10 +1196,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "Synchronisation destructive avec le Cloud" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "Synchronisation : Sauvegardes/Sauvegardes instantanées" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "Synchronisation : Fichiers de configuration" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "Lorsque cette option est activée, les sauvegardes/sauvegardes instantanées seront synchronisés avec le cloud." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "Lorsque cette option est activée, les fichiers de configuration seront synchronisés avec le cloud." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "Lorsque cette option est désactivée, les fichiers sont déplacés vers un dossier de sauvegarde avant d'être remplacés ou supprimés." @@ -2527,7 +2539,55 @@ MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y, "Décalage de la fenêtre d'affichage sur l'axe Y.\nCette option sera ignorée si l'option 'Échelle à l'entier' est activée." ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_X, + "Biais d'ancrage X de la fenêtre d'affichage" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_X, + "Biais d'ancrage X de la fenêtre d'affichage" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_Y, + "Biais d'ancrage Y de la fenêtre d'affichage" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_Y, + "Biais d'ancrage Y de la fenêtre d'affichage" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_X, + "Biais de la fenêtre d'affichage personnalisé utilisé pour décaler la fenêtre d'affichage horizontalement (si plus large que la hauteur du contenu). 0.0 signifie tout à gauche et 1.0 signifie tout à droite." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_Y, + "Biais de la fenêtre d'affichage personnalisé utilisé pour décaler la fenêtre d'affichage verticalement (si plus grande que la hauteur du contenu). 0.0 signifie tout en haut et 1.0 signifie tout en bas." + ) #if defined(RARCH_MOBILE) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Biais d'ancrage X de la fenêtre d'affichage (orientation portrait)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Biais d'ancrage X de la fenêtre d'affichage (orientation portrait)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Biais d'ancrage Y de la fenêtre d'affichage (orientation portrait)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Biais d'ancrage Y de la fenêtre d'affichage (orientation portrait)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Biais de la fenêtre d'affichage personnalisé utilisé pour décaler la fenêtre d'affichage horizontalement (si plus large que la hauteur du contenu). 0.0 signifie tout à gauche et 1.0 signifie tout à droite. (Orientation portrait)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Biais de la fenêtre d'affichage personnalisé utilisé pour décaler la fenêtre d'affichage verticalement (si plus grande que la hauteur du contenu). 0.0 signifie tout en haut et 1.0 signifie tout en bas. (Orientation portrait)" + ) #endif MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_CUSTOM_WIDTH, diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index 08aed81f7ba..9beb9574350 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -1228,10 +1228,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "破壊的なクラウド同期" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "同期: セーブ/ステート" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "同期: 設定ファイル" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "有効にすると、セーブ/ステートがクラウドに同期されます。" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "有効にすると、設定ファイルがクラウドに同期されます。" + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "無効にすると、ファイルは上書きまたは削除される前にバックアップフォルダに移動されます。" @@ -2545,9 +2557,57 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y, - "表示領域の Y軸位置を定義するために使用されるカスタム表示領域オフセットです。\n[整数倍拡大] が有効の場合は無視されます。" + "表示領域の Y 軸位置を定義するために使用されるカスタム表示領域オフセットです。\n[整数倍拡大] が有効の場合は無視されます。" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_X, + "表示領域 X 座標補正" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_X, + "表示領域 X 座標補正" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_Y, + "表示領域 Y 座標補正" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_Y, + "表示領域 Y 座標補正" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_X, + "表示領域がコンテンツの幅より広い場合、水平方向のオフセットに使用される補正値です。0.0 は左端を、1.0 は右端を表します。" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_Y, + "表示領域がコンテンツの高さより高い場合、垂直方向のオフセットに使用される補正値です。0.0 は上端を、1.0 は下端を表します。" ) #if defined(RARCH_MOBILE) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "表示領域 X 座標補正 (縦向き)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "表示領域 X 座標補正 (縦向き)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "表示領域 Y 座標補正 (縦向き)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "表示領域 Y 座標補正 (縦向き)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "表示領域がコンテンツの幅より広い場合、水平方向のオフセットに使用される補正値です。0.0 は左端を、1.0 は右端を表します (縦向き)。" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "表示領域がコンテンツの高さより高い場合、垂直方向のオフセットに使用される補正値です。0.0 は上端を、1.0 は下端を表します (縦向き)。" + ) #endif MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_CUSTOM_WIDTH, diff --git a/intl/msg_hash_ru.h b/intl/msg_hash_ru.h index 6703f24117d..a86bf0e33ec 100644 --- a/intl/msg_hash_ru.h +++ b/intl/msg_hash_ru.h @@ -1228,10 +1228,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "Деструктивная облачная синхронизация" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "Синхронизация: карты памяти/сохранения" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "Синхронизация: файлы конфигураций" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "Если включено, карты памяти/сохранения будут синхронизироваться с облаком." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "Если включено, файлы конфигураций будут синхронизироваться с облаком." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "Если отключено, перед удалением или перезаписью файлы помещаются в каталог резервирования." From 8935d9db1b3459c561d14654d65f82a77b3c66f6 Mon Sep 17 00:00:00 2001 From: Jonathan Rascher Date: Wed, 26 Jun 2024 22:17:21 -0500 Subject: [PATCH 02/12] Skip core unload when Quit on Close Content is set --- menu/cbs/menu_cbs_ok.c | 5 +++-- retroarch.c | 13 +++++++++---- retroarch.h | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 13842163f4d..0fede1508ae 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -5598,9 +5598,10 @@ int action_ok_close_content(const char *path, const char *label, unsigned type, menu_st->selection_ptr = 0; /* Check if we need to quit */ - check_quit_on_close(); + if (should_quit_on_close()) + return generic_action_ok_command(CMD_EVENT_QUIT); - /* Unload core */ + /* Otherwise, unload core */ ret = generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); /* If close content was selected via any means other than diff --git a/retroarch.c b/retroarch.c index f3dec5dd32b..06821a8bc66 100644 --- a/retroarch.c +++ b/retroarch.c @@ -3723,6 +3723,12 @@ bool command_event(enum event_command cmd, void *data) break; case CMD_EVENT_CLOSE_CONTENT: #ifdef HAVE_MENU + /* If we need to quit, skip unloading the core to avoid performing + * cleanup actions (like writing autosave state) twice. */ + if (should_quit_on_close()) { + command_event(CMD_EVENT_QUIT, NULL); + break; + } /* Closing content via hotkey requires toggling menu * and resetting the position later on to prevent * going to empty Quick Menu */ @@ -3731,8 +3737,6 @@ bool command_event(enum event_command cmd, void *data) menu_state_get_ptr()->flags |= MENU_ST_FLAG_PENDING_CLOSE_CONTENT; command_event(CMD_EVENT_MENU_TOGGLE, NULL); } - /* Check if we need to quit Retroarch */ - check_quit_on_close(); #else command_event(CMD_EVENT_QUIT, NULL); #endif @@ -8212,7 +8216,7 @@ void retroarch_fail(int error_code, const char *error) } /* Called on close content, checks if we need to also exit retroarch */ -void check_quit_on_close(void) +bool should_quit_on_close(void) { #ifdef HAVE_MENU settings_t *settings = config_get_ptr(); @@ -8223,8 +8227,9 @@ void check_quit_on_close(void) || (settings->uints.quit_on_close_content == QUIT_ON_CLOSE_CONTENT_ENABLED) ) - command_event(CMD_EVENT_QUIT, NULL); + return true; #endif + return false; } /* diff --git a/retroarch.h b/retroarch.h index 7da4e394821..2d1900766c1 100644 --- a/retroarch.h +++ b/retroarch.h @@ -206,7 +206,7 @@ void core_options_flush(void); **/ void retroarch_fail(int error_code, const char *error); -void check_quit_on_close(void); +bool should_quit_on_close(void); uint16_t retroarch_get_flags(void); From 83466994aa3352977f6531ce5193f6aadb892fbe Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Thu, 27 Jun 2024 15:21:14 -0400 Subject: [PATCH 03/12] Fix #16562 support bluetooth keyboards on tvos --- ui/drivers/cocoa/cocoa_common.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/drivers/cocoa/cocoa_common.m b/ui/drivers/cocoa/cocoa_common.m index eb4f8d2c9ec..8d8b8d77c66 100644 --- a/ui/drivers/cocoa/cocoa_common.m +++ b/ui/drivers/cocoa/cocoa_common.m @@ -273,8 +273,10 @@ - (void)pressesBegan:(NSSet *)presses /* If we're at the top it doesn't matter who pressed it, we want to leave */ if (press.type == UIPressTypeMenu && [self menuIsAtTop]) [super pressesBegan:presses withEvent:event]; - else if ([self didMicroGamepadPress:press.type]) + else if (!press.key && [self didMicroGamepadPress:press.type]) [self sendKeyForPress:press.type down:true]; + else + [super pressesBegan:[NSSet setWithObject:press] withEvent:event]; } } From fd8bb7b1ef6c1e9604fa71b414255f67d834c18b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 28 Jun 2024 00:12:41 +0000 Subject: [PATCH 04/12] Fetch translations from Crowdin --- intl/msg_hash_gl.h | 4 +-- intl/msg_hash_ja.h | 16 ++++++++++-- intl/msg_hash_tr.h | 62 +++++++++++++++++++++++++++++++++++++++++++++- intl/progress.h | 6 ++--- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/intl/msg_hash_gl.h b/intl/msg_hash_gl.h index db2cdfc339e..6563779ca9e 100644 --- a/intl/msg_hash_gl.h +++ b/intl/msg_hash_gl.h @@ -46,7 +46,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB, - "Núcleos sen Contidos" + "Núcleos sen Contido" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_ADD_TAB, @@ -308,7 +308,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_LABEL_VALUE_GOTO_CONTENTLESS_CORES, - "Núcleos sen Contidos" + "Núcleos sen Contido" ) MSG_HASH( MENU_ENUM_SUBLABEL_GOTO_CONTENTLESS_CORES, diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index 9beb9574350..1b0e3bae596 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -2076,7 +2076,15 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_SCAN_SUBFRAMES, - "ローリングスキャンラインシミュレーション" + "ループ回転スキャンラインシミュレーション" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_SCAN_SUBFRAMES, + "画面を垂直に分割し、サブフレームの数に応じて画面の各部分をレンダリングすることで、複数のサブフレームにわたるブラウン管をカメラで撮影したときのようなループ回転スキャンラインをシミュレートします。" + ) +MSG_HASH( + MENU_ENUM_LABEL_HELP_VIDEO_SCAN_SUBFRAMES, + "画面を垂直に分割し、画面の上端から下端にあるサブフレームの数に応じて画面の各部分をレンダリングすることで、複数のサブフレームにわたるブラウン管をカメラで撮影したときのようなループ回転スキャンラインをシミュレートします。" ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_GPU_SCREENSHOT, @@ -6992,7 +7000,11 @@ MSG_HASH( MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_MODE, - "AIサービス出力" + "AI サービス出力" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_AI_SERVICE_MODE, + "テキストオーバーレイ (画像モード)、テキスト読み上げ (音声)、または NVDA (ナレーター) のようなスクリーンリーダーのいずれかで翻訳を表示します。" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_URL, diff --git a/intl/msg_hash_tr.h b/intl/msg_hash_tr.h index cedda12e4b6..4bdec87015e 100644 --- a/intl/msg_hash_tr.h +++ b/intl/msg_hash_tr.h @@ -1216,10 +1216,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "Bulut Eşitleme Koruması" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "Eşitleyici: Kayıtlar/Durumlar" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "Eşitleyici: Yapılandırma Dosyaları" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "Etkinleştirildiğinde, kayıtlar/durumlar bulut ile eşitlenecektir." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "Etkinleştirildiğinde yapılandırma dosyaları bulut ile eşitlenecektir." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "Devre dışı bırakıldığında, dosyalar üzerine yazılmadan veya silinmeden önce yedek klasörüne taşınır." @@ -2543,7 +2555,55 @@ MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y, "Görünüm penceresinin Y ekseni konumunu tanımlamak için kullanılan özel görünüm alanı ofseti.\n'Tam sayı Ölçeği' etkinse bunlar yok sayılır." ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_X, + "Görünüm Bağlantı Noktası X" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_X, + "Görünüm Bağlantı Noktası X" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_Y, + "Görünüm Bağlantı Noktası Y" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_Y, + "Görünüm Bağlantı Noktası Y" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_X, + "Görüntü alanını yatay olarak dengelemek için kullanılan özel görüntü alanı sapması (içerik yüksekliğinden daha genişse). 0,0 en sol, 1,0 ise en sağ anlamına gelir." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_Y, + "Görüntü alanını dikey olarak dengelemek için kullanılan özel görüntü alanı sapması (içerik yüksekliğinden daha uzunsa). 0,0 üst, 1,0 ise alt anlamına gelir." + ) #if defined(RARCH_MOBILE) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Görünüm Bağlantı Noktası X (Portre Yönü)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Görünüm Bağlantı Noktası X (Portre Yönü)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Görünüm Bağlantı Noktası Y (Portre Yönü)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Görünüm Bağlantı Noktası Y (Portre Yönü)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Görüntü alanını yatay olarak dengelemek için kullanılan özel görüntü alanı sapması (içerik yüksekliğinden daha genişse). 0,0 en sol, 1,0 ise en sağ anlamına gelir. (Portre Yönü)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Görüntü alanını dikey olarak dengelemek için kullanılan özel görüntü alanı sapması (içerik yüksekliğinden daha uzunsa). 0,0 üst, 1,0 ise alt anlamına gelir. (Portre Yönü)" + ) #endif MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_CUSTOM_WIDTH, diff --git a/intl/progress.h b/intl/progress.h index 0ae290ad3ed..a2fc1f7da54 100644 --- a/intl/progress.h +++ b/intl/progress.h @@ -87,7 +87,7 @@ #define LANGUAGE_PROGRESS_ITALIAN_APPROVED 0 /* Japanese */ -#define LANGUAGE_PROGRESS_JAPANESE_TRANSLATED 99 +#define LANGUAGE_PROGRESS_JAPANESE_TRANSLATED 100 #define LANGUAGE_PROGRESS_JAPANESE_APPROVED 0 /* Korean */ @@ -135,8 +135,8 @@ #define LANGUAGE_PROGRESS_SWEDISH_APPROVED 48 /* Turkish */ -#define LANGUAGE_PROGRESS_TURKISH_TRANSLATED 99 -#define LANGUAGE_PROGRESS_TURKISH_APPROVED 99 +#define LANGUAGE_PROGRESS_TURKISH_TRANSLATED 100 +#define LANGUAGE_PROGRESS_TURKISH_APPROVED 100 /* Ukrainian */ #define LANGUAGE_PROGRESS_UKRAINIAN_TRANSLATED 38 From 40c889b5620b0ae7fc71c39d1df4dbc71550b0a4 Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sat, 29 Jun 2024 03:25:33 +0200 Subject: [PATCH 05/12] Add header include for rhmap.h --- libretro-common/include/array/rhmap.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libretro-common/include/array/rhmap.h b/libretro-common/include/array/rhmap.h index d6aec901424..10cb5508748 100644 --- a/libretro-common/include/array/rhmap.h +++ b/libretro-common/include/array/rhmap.h @@ -104,6 +104,7 @@ #include /* for memcpy, memset */ #include /* for ptrdiff_t, size_t */ #include /* for uint32_t */ +#include /* for strdup */ #define RHMAP_LEN(b) ((b) ? RHMAP__HDR(b)->len : 0) #define RHMAP_MAX(b) ((b) ? RHMAP__HDR(b)->maxlen : 0) From 47ec7c078244f44e85ff58a64e2090e5ba32c902 Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sat, 29 Jun 2024 03:46:06 +0200 Subject: [PATCH 06/12] Try to avoid strdup to avoid portability issues --- libretro-common/include/array/rhmap.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/libretro-common/include/array/rhmap.h b/libretro-common/include/array/rhmap.h index 10cb5508748..f0eaaad8ee9 100644 --- a/libretro-common/include/array/rhmap.h +++ b/libretro-common/include/array/rhmap.h @@ -104,7 +104,7 @@ #include /* for memcpy, memset */ #include /* for ptrdiff_t, size_t */ #include /* for uint32_t */ -#include /* for strdup */ +#include /* for strlen */ #define RHMAP_LEN(b) ((b) ? RHMAP__HDR(b)->len : 0) #define RHMAP_MAX(b) ((b) ? RHMAP__HDR(b)->maxlen : 0) @@ -261,7 +261,21 @@ RHMAP__UNUSED static ptrdiff_t rhmap__idx(struct rhmap__hdr* hdr, uint32_t key, } if (!hdr->keys[i]) { - if (add) { hdr->len++; hdr->keys[i] = key; if (str) hdr->key_strs[i] = strdup(str); return (ptrdiff_t)i; } + if (add) + { + int l; + char *t; + + hdr->len++; + hdr->keys[i] = key; + l = strlen(str); + t = malloc(l + 1); + memcpy(t, s, l); + t[l] = '\0'; + if (str) + hdr->key_strs[i] = t; + return (ptrdiff_t)i; + } return (ptrdiff_t)-1; } } From 57c790646fded3b491f977aa67a061781f06aedb Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sat, 29 Jun 2024 03:48:50 +0200 Subject: [PATCH 07/12] Buildfix --- libretro-common/include/array/rhmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro-common/include/array/rhmap.h b/libretro-common/include/array/rhmap.h index f0eaaad8ee9..b302a3010d6 100644 --- a/libretro-common/include/array/rhmap.h +++ b/libretro-common/include/array/rhmap.h @@ -270,7 +270,7 @@ RHMAP__UNUSED static ptrdiff_t rhmap__idx(struct rhmap__hdr* hdr, uint32_t key, hdr->keys[i] = key; l = strlen(str); t = malloc(l + 1); - memcpy(t, s, l); + memcpy(t, str, l); t[l] = '\0'; if (str) hdr->key_strs[i] = t; From 00b1e97c79664dab89871043e3ee1b8f0683c2af Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 1 Jul 2024 00:14:23 +0000 Subject: [PATCH 08/12] Fetch translations from Crowdin --- intl/msg_hash_de.h | 30 +++++++++++++++++++++++++++++- intl/msg_hash_pl.h | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/intl/msg_hash_de.h b/intl/msg_hash_de.h index 367465784ee..04c4d1ca338 100644 --- a/intl/msg_hash_de.h +++ b/intl/msg_hash_de.h @@ -1160,10 +1160,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "Destruktive Cloud-Synchronisation" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "Synchronisieren: Speicherdaten/Savestates" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "Synchronisieren: Konfigurationsdateien" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "Wenn aktiviert, werden Speicherdaten/Savestates mit der Cloud synchronisiert." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "Wenn aktiviert, werden Konfigurationsdateien mit der Cloud synchronisiert." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "Wenn deaktiviert, werden die Dateien in einen Sicherungsordner verschoben, bevor sie überschrieben oder gelöscht werden." @@ -2471,7 +2483,23 @@ MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y, "Benutzerdefinierter Versatz des Ansichtsfensters, welcher die Y-Achsenposition definiert.\nDieser wird ignoriert, wenn \"Ganzzahlige Skalierung\" aktiviert ist." ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_X, + "Benutzerdefinierte Ausrichtung des Ansichtsfensters wird verwendet, um das Ansichtsfenster horizontal zu versetzen (wenn es breiter als die Inhaltshöhe ist). 0.0 bedeutet ganz links und 1.0 bedeutet ganz rechts." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_Y, + "Benutzerdefinierte Ausrichtung des Ansichtsfensters wird verwendet, um das Ansichtsfenster vertikal zu versetzen (wenn es höher als die Inhaltshöhe ist). 0.0 bedeutet oben und 1.0 bedeutet unten." + ) #if defined(RARCH_MOBILE) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Benutzerdefinierte Ausrichtung des Ansichtsfensters wird verwendet, um das Ansichtsfenster horizontal zu versetzen (wenn es breiter als die Inhaltshöhe ist). 0.0 bedeutet ganz links und 1.0 bedeutet ganz rechts. (Hochformat)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Benutzerdefinierte Ausrichtung des Ansichtsfensters wird verwendet, um das Ansichtsfenster vertikal zu versetzen (wenn es höher als die Inhaltshöhe ist). 0.0 bedeutet oben und 1.0 bedeutet unten. (Hochformat)" + ) #endif MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_CUSTOM_WIDTH, diff --git a/intl/msg_hash_pl.h b/intl/msg_hash_pl.h index 7287df36603..61a8d0e823f 100644 --- a/intl/msg_hash_pl.h +++ b/intl/msg_hash_pl.h @@ -1212,10 +1212,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE, "Destrukcyjna synchronizacja w chmurze" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_SAVES, + "Synchronizacja: Zapisy/Stany" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SYNC_CONFIGS, - "Sync: Configuration Files" + "Synchronizacja: Pliki konfiguracyjne" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_SAVES, + "Po włączeniu zapisy/stany zostaną zsynchronizowane z chmurą." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_CONFIGS, + "Po włączeniu pliki konfiguracyjne zostaną zsynchronizowane z chmurą." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE, "Gdy wyłączone, pliki są przenoszone do folderu kopii zapasowej przed nadpisaniem lub usunięciem." @@ -2507,7 +2519,39 @@ MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y, "Własne przesunięcie widoku używane do zdefiniowania położenia osi Y widoku.\nSą one ignorowane, jeśli włączona jest „skala całkowita”." ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_X, + "Podgląd zakotwiczenia X" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_X, + "Podgląd zakotwiczenia X" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_X, + "Własny widok używany do wyrównywania widoku poziomo (jeśli jest większy niż wysokość zawartości). 0.0 oznacza lewy i 1.0 oznacza prawo." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_Y, + "Własny widok używany do wyrównywania widoku poziomo (jeśli jest większy niż wysokość zawartości). 0.0 oznacza lewy i 1.0 oznacza prawo." + ) #if defined(RARCH_MOBILE) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Podgląd kotwicy X (orientacja pionowa)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Podgląd kotwicy X (orientacja pionowa)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_X, + "Własny widok używany do wyrównywania widoku poziomo (jeśli jest większy niż wysokość zawartości). 0.0 oznacza lewo, a 1.0 oznacza prawo. (orientacja pionowa)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_BIAS_PORTRAIT_Y, + "Własny widok używany do przesuwania widoku pionowo (jeśli jest wyższy niż wysokość zawartości). 0.0 oznacza górny i 1.0 oznacza dolny (orientacja pionowa)" + ) #endif MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_VIEWPORT_CUSTOM_WIDTH, From 72db30128c8f8f149d2727c8f439af5969c58a99 Mon Sep 17 00:00:00 2001 From: cwyc Date: Sun, 28 Jan 2024 18:00:14 -0500 Subject: [PATCH 09/12] Android: Improvements to DocumentProvider Added move and rename methods Provider notifies viewer to refresh view when files are changed Bumped up TargetApi annotation for DocumentsContract.buildTreeDocumentUri. Could alternatively use androidx compat class. --- .../provider/RetroDocumentsProvider.java | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java b/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java index c49a7432ffd..4d2f927835f 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java @@ -11,6 +11,7 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; @@ -35,11 +36,13 @@ * offering two different ways of accessing your stored data. This would be confusing for users." * - http://developer.android.com/guide/topics/providers/document-provider.html#43 */ -@TargetApi(Build.VERSION_CODES.KITKAT) +@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class RetroDocumentsProvider extends DocumentsProvider { private static final String ALL_MIME_TYPES = "*/*"; + private String DOCUMENTS_AUTHORITY; + // The default columns to return information about a root if no specific // columns are requested in a query. private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{ @@ -89,6 +92,36 @@ public Cursor queryDocument(String documentId, String[] projection) throws FileN return result; } + @Override + public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException { + File sourceFile = getFileForDocId(sourceDocumentId); + File targetParentFile = getFileForDocId(targetParentDocumentId); + File destination = new File(targetParentFile, sourceFile.getName()); + + boolean success = sourceFile.renameTo(destination); + if(!success){ + throw new FileNotFoundException("Failed to move file " + sourceDocumentId + " to " + targetParentDocumentId); + } + + getContext().getContentResolver().notifyChange(DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, sourceParentDocumentId), null); + getContext().getContentResolver().notifyChange(DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, targetParentDocumentId), null); + return getDocIdForFile(destination); + } + + @Override + public String renameDocument(String documentId, String displayName) throws FileNotFoundException{ + File document = getFileForDocId(documentId); + File destination = new File(document.getParentFile(), displayName); + + boolean success = document.renameTo(destination); + if(!success){ + throw new FileNotFoundException("Failed to rename file " + documentId + " to " + displayName); + } + + getContext().getContentResolver().notifyChange(DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, getDocIdForFile(document.getParentFile())), null); + return getDocIdForFile(destination); + } + @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); @@ -96,6 +129,7 @@ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, for (File file : parent.listFiles()) { includeFile(result, null, file); } + result.setNotificationUri(getContext().getContentResolver(), DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, parentDocumentId)); return result; } @@ -115,6 +149,7 @@ public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHi @Override public boolean onCreate() { + DOCUMENTS_AUTHORITY = getContext().getPackageName() + ".documents"; return true; } @@ -138,14 +173,25 @@ public String createDocument(String parentDocumentId, String mimeType, String di } catch (IOException e) { throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); } + getContext().getContentResolver().notifyChange(DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, parentDocumentId), null); return newFile.getPath(); } @Override public void deleteDocument(String documentId) throws FileNotFoundException { File file = getFileForDocId(documentId); - if (!file.delete()) { - throw new FileNotFoundException("Failed to delete document with id " + documentId); + rm_r(file); + getContext().getContentResolver().notifyChange(DocumentsContract.buildTreeDocumentUri(DOCUMENTS_AUTHORITY, getDocIdForFile(file.getParentFile())), null); + } + + void rm_r (File file) throws FileNotFoundException{ + if(file.isDirectory()){ + for(File child : file.listFiles()) { + rm_r(child); + } + } + if(!file.delete()){ + throw new FileNotFoundException("Could not delete file " + file.getPath()); } } @@ -248,11 +294,11 @@ private void includeFile(MatrixCursor result, String docId, File file) int flags = 0; if (file.isDirectory()) { - if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - } else if (file.canWrite()) { - flags |= Document.FLAG_SUPPORTS_WRITE; + if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_SUPPORTS_RENAME; + } else { + if (file.canWrite()) flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME; } - if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; + if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_MOVE; final String displayName = file.getName(); final String mimeType = getMimeType(file); From 850560d1b6e774753d536690259d84610f623927 Mon Sep 17 00:00:00 2001 From: cwyc Date: Fri, 26 Jan 2024 13:32:34 -0500 Subject: [PATCH 10/12] Android: Remove External Storage permission --- pkg/android/phoenix/AndroidManifest.xml | 3 - .../browser/mainmenu/MainMenuActivity.java | 116 +----------------- .../provider/RetroDocumentsProvider.java | 33 +++-- 3 files changed, 25 insertions(+), 127 deletions(-) diff --git a/pkg/android/phoenix/AndroidManifest.xml b/pkg/android/phoenix/AndroidManifest.xml index 16c235a30be..dafdc7e2e62 100644 --- a/pkg/android/phoenix/AndroidManifest.xml +++ b/pkg/android/phoenix/AndroidManifest.xml @@ -10,8 +10,6 @@ - - @@ -25,7 +23,6 @@ android:isGame="true" android:banner="@drawable/banner" android:extractNativeLibs="true" - android:requestLegacyExternalStorage="true" tools:ignore="UnusedAttribute"> diff --git a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java index 7a7117c9017..bc555a74f99 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java @@ -26,92 +26,7 @@ */ public final class MainMenuActivity extends PreferenceActivity { - final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; public static String PACKAGE_NAME; - boolean checkPermissions = false; - - public void showMessageOKCancel(String message, DialogInterface.OnClickListener onClickListener) - { - new AlertDialog.Builder(this).setMessage(message) - .setPositiveButton("OK", onClickListener).setCancelable(false) - .setNegativeButton("Cancel", null).create().show(); - } - - private boolean addPermission(List permissionsList, String permission) - { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) - { - if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) - { - permissionsList.add(permission); - - // Check for Rationale Option - if (!shouldShowRequestPermissionRationale(permission)) - return false; - } - } - - return true; - } - - public void checkRuntimePermissions() - { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) - { - // Android 6.0+ needs runtime permission checks - List permissionsNeeded = new ArrayList(); - final List permissionsList = new ArrayList(); - - if (!addPermission(permissionsList, Manifest.permission.READ_EXTERNAL_STORAGE)) - permissionsNeeded.add("Read External Storage"); - if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE)) - permissionsNeeded.add("Write External Storage"); - - if (permissionsList.size() > 0) - { - checkPermissions = true; - - if (permissionsNeeded.size() > 0) - { - // Need Rationale - Log.i("MainMenuActivity", "Need to request external storage permissions."); - - String message = "You need to grant access to " + permissionsNeeded.get(0); - - for (int i = 1; i < permissionsNeeded.size(); i++) - message = message + ", " + permissionsNeeded.get(i); - - showMessageOKCancel(message, - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - if (which == AlertDialog.BUTTON_POSITIVE) - { - requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), - REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); - - Log.i("MainMenuActivity", "User accepted request for external storage permissions."); - } - } - }); - } - else - { - requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), - REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); - - Log.i("MainMenuActivity", "Requested external storage permissions."); - } - } - } - - if (!checkPermissions) - { - finalStartup(); - } - } public void finalStartup() { @@ -132,33 +47,6 @@ public void finalStartup() finish(); } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) - { - switch (requestCode) - { - case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: - for (int i = 0; i < permissions.length; i++) - { - if(grantResults[i] == PackageManager.PERMISSION_GRANTED) - { - Log.i("MainMenuActivity", "Permission: " + permissions[i] + " was granted."); - } - else - { - Log.i("MainMenuActivity", "Permission: " + permissions[i] + " was not granted."); - } - } - - break; - default: - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - break; - } - - finalStartup(); - } - public static void startRetroActivity(Intent retro, String contentPath, String corePath, String configFilePath, String imePath, String dataDirPath, String dataSourcePath) { @@ -170,8 +58,8 @@ public static void startRetroActivity(Intent retro, String contentPath, String c retro.putExtra("IME", imePath); retro.putExtra("DATADIR", dataDirPath); retro.putExtra("APK", dataSourcePath); - retro.putExtra("SDCARD", Environment.getExternalStorageDirectory().getAbsolutePath()); String external = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + PACKAGE_NAME + "/files"; + retro.putExtra("SDCARD", external); retro.putExtra("EXTERNAL", external); } @@ -187,6 +75,6 @@ public void onCreate(Bundle savedInstanceState) UserPreferences.updateConfigFile(this); - checkRuntimePermissions(); + finalStartup(); } } diff --git a/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java b/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java index 4d2f927835f..52bd33a058e 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/provider/RetroDocumentsProvider.java @@ -10,6 +10,7 @@ import android.graphics.Point; import android.os.Build; import android.os.CancellationSignal; +import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; @@ -69,19 +70,31 @@ public class RetroDocumentsProvider extends DocumentsProvider { @Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { - final File BASE_DIR = new File(getContext().getFilesDir().getParent()); final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); @SuppressWarnings("ConstantConditions") final String applicationName = getContext().getString(R.string.app_name); - final MatrixCursor.RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR)); - row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR)); - row.add(Root.COLUMN_SUMMARY, null); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, applicationName); - row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); - row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace()); - row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); + final File CORE_DIR = new File(getContext().getFilesDir().getParent()); + final MatrixCursor.RowBuilder core = result.newRow(); + core.add(Root.COLUMN_ROOT_ID, getDocIdForFile(CORE_DIR)); + core.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(CORE_DIR)); + core.add(Root.COLUMN_SUMMARY, "Core Data"); + core.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); + core.add(Root.COLUMN_TITLE, applicationName); + core.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); + core.add(Root.COLUMN_AVAILABLE_BYTES, CORE_DIR.getFreeSpace()); + core.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); + + final File USER_DIR = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + getContext().getPackageName() + "/files/RetroArch"); + final MatrixCursor.RowBuilder user = result.newRow(); + user.add(Root.COLUMN_ROOT_ID, getDocIdForFile(USER_DIR)); + user.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(USER_DIR)); + user.add(Root.COLUMN_SUMMARY, "User Data"); + user.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); + user.add(Root.COLUMN_TITLE, applicationName); + user.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); + user.add(Root.COLUMN_AVAILABLE_BYTES, USER_DIR.getFreeSpace()); + user.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); + return result; } From b7235e426f239825fc3d8fbb5a8cb482153faf96 Mon Sep 17 00:00:00 2001 From: cwyc Date: Sat, 30 Mar 2024 14:53:43 -0400 Subject: [PATCH 11/12] Android: Add function to migrate RetroArch folder from sdcard. --- pkg/android/phoenix/AndroidManifest.xml | 3 + pkg/android/phoenix/res/values/strings.xml | 31 ++ .../browser/mainmenu/MainMenuActivity.java | 20 +- .../MigrateRetroarchFolderActivity.java | 266 ++++++++++++++++++ 4 files changed, 311 insertions(+), 9 deletions(-) create mode 100644 pkg/android/phoenix/res/values/strings.xml create mode 100644 pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java diff --git a/pkg/android/phoenix/AndroidManifest.xml b/pkg/android/phoenix/AndroidManifest.xml index dafdc7e2e62..2d978a820d7 100644 --- a/pkg/android/phoenix/AndroidManifest.xml +++ b/pkg/android/phoenix/AndroidManifest.xml @@ -24,6 +24,9 @@ android:banner="@drawable/banner" android:extractNativeLibs="true" tools:ignore="UnusedAttribute"> + diff --git a/pkg/android/phoenix/res/values/strings.xml b/pkg/android/phoenix/res/values/strings.xml new file mode 100644 index 00000000000..c8e55b6caf3 --- /dev/null +++ b/pkg/android/phoenix/res/values/strings.xml @@ -0,0 +1,31 @@ + + + + Migrate RetroArch Folder + + + Because RetroArch was updated, the location of the RetroArch folder has changed. \n + Would you like to import data from an existing RetroArch folder? + + + Yes, select existing RetroArch folder + + + No, don\'t ask again + + + No, ask next time + + + Copying RetroArch Files… + + + Your RetroArch folder has been migrated. \n + You can find it in the files app under RetroArch > User Data. + + + Your RetroArch folder has been migrated. \n + You can find it in the files app under RetroArch > User Data. \n + There were errors copying some files. + + diff --git a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java index bc555a74f99..cc3f37636df 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java @@ -12,14 +12,6 @@ import android.preference.PreferenceManager; import android.provider.Settings; -import java.util.List; -import java.util.ArrayList; -import android.content.pm.PackageManager; -import android.Manifest; -import android.content.DialogInterface; -import android.app.AlertDialog; -import android.util.Log; - /** * {@link PreferenceActivity} subclass that provides all of the * functionality of the main menu screen. @@ -27,6 +19,7 @@ public final class MainMenuActivity extends PreferenceActivity { public static String PACKAGE_NAME; + final int REQUEST_CODE_START = 120; public void finalStartup() { @@ -63,6 +56,14 @@ public static void startRetroActivity(Intent retro, String contentPath, String c retro.putExtra("EXTERNAL", external); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + if(requestCode == REQUEST_CODE_START) { + finalStartup(); + } + } + @Override public void onCreate(Bundle savedInstanceState) { @@ -75,6 +76,7 @@ public void onCreate(Bundle savedInstanceState) UserPreferences.updateConfigFile(this); - finalStartup(); + Intent i = new Intent(this, MigrateRetroarchFolderActivity.class); + startActivityForResult(i, REQUEST_CODE_START); } } diff --git a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java new file mode 100644 index 00000000000..1916aaa5821 --- /dev/null +++ b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java @@ -0,0 +1,266 @@ +package com.retroarch.browser.mainmenu; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.provider.DocumentsContract; +import android.util.Log; +import android.util.Pair; + +import com.retroarch.R; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; + +@TargetApi(26) +public class MigrateRetroarchFolderActivity extends Activity +{ + final int REQUEST_CODE_GET_OLD_RETROARCH_FOLDER = 125; + + @Override + public void onStart() + { + super.onStart(); + + // Needs v26 for some of the file handling functions below. + // Remove the TargetApi annotation to see which. + // If we don't have it, then just skip migration. + if (android.os.Build.VERSION.SDK_INT < 26) { + finish(); + } + if(true || needToMigrate()){ + askToMigrate(); + }else{ + finish(); + } + } + + boolean needToMigrate() + { + // As the RetroArch folder has been moved from shared storage to app-specific storage, + // people upgrading from older versions using the old location will need to migrate their data. + // We identify these users by checking that the app has been updated from an older version, + // and that the older version did not use the new location. + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean isNewInstall; + try{ + PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); + isNewInstall = info.firstInstallTime == info.lastUpdateTime; + }catch(PackageManager.NameNotFoundException ex) { + isNewInstall = true; + } + + // Avoid asking if new install + if(isNewInstall && !prefs.contains("external_retroarch_folder_needs_migrate")){ + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean("external_retroarch_folder_needs_migrate", false); + editor.apply(); + } + + return prefs.getBoolean("external_retroarch_folder_needs_migrate", true); + } + + void askToMigrate() + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.migrate_retroarch_folder_dialog_title); + builder.setMessage(R.string.migrate_retroarch_folder_dialog_message); + builder.setNegativeButton(R.string.migrate_retroarch_folder_dialog_negative, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MigrateRetroarchFolderActivity.this).edit(); + editor.putBoolean("external_retroarch_folder_needs_migrate", false); + editor.apply(); + MigrateRetroarchFolderActivity.this.finish(); + } + }); + builder.setNeutralButton(R.string.migrate_retroarch_folder_dialog_neutral, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MigrateRetroarchFolderActivity.this).edit(); + editor.putBoolean("external_retroarch_folder_needs_migrate", true); + editor.apply(); + MigrateRetroarchFolderActivity.this.finish(); + } + }); + builder.setPositiveButton(R.string.migrate_retroarch_folder_dialog_positive, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialogInterface, int i) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(new File( + Environment.getExternalStorageDirectory().getAbsolutePath() + "/RetroArch" + ))); + startActivityForResult(intent, REQUEST_CODE_GET_OLD_RETROARCH_FOLDER); + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + public void onActivityResult(int requestCode, int resultCode, Intent resultData) + { + super.onActivityResult(requestCode, resultCode, resultData); + if(requestCode == REQUEST_CODE_GET_OLD_RETROARCH_FOLDER){ + if(resultCode == Activity.RESULT_OK && resultData != null){ + copyFiles(resultData.getData()); + }else{ + //User cancelled or otherwise failed. Go back to the picker screen. + askToMigrate(); + } + } + } + + void copyFiles(Uri sourceDir) + { + final ProgressDialog pd = new ProgressDialog(this); + pd.setMax(100); + pd.setTitle(R.string.migrate_retroarch_folder_inprogress); + pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + pd.setCancelable(false); + + CopyThread thread = new CopyThread() + { + @Override + protected void onPreExecute(){ + super.onPreExecute(); + pd.show(); + } + @Override + protected void onProgressUpdate(Pair... params) + { + super.onProgressUpdate(params); + pd.setProgress(params[0].first); + pd.setMessage(params[0].second); + } + @Override + protected void onPostExecute(Boolean ok) + { + super.onPostExecute(ok); + pd.dismiss(); + postMigrate(ok); + } + }; + + thread.execute(sourceDir); + } + + void postMigrate(boolean ok) + { + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.putBoolean("external_retroarch_folder_needs_migrate", false); + editor.commit(); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(ok ? + R.string.migrate_retroarch_folder_confirm : + R.string.migrate_retroarch_folder_confirm_witherror + ); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialogInterface, int i) { + MigrateRetroarchFolderActivity.this.finish(); + } + }); + builder.create().show(); + } + + class CopyThread extends AsyncTask, Boolean> + { + String PACKAGE_NAME; + ContentResolver resolver; + Uri sourceRoot; + boolean error; + ArrayList progress; + public CopyThread() + { + PACKAGE_NAME = MigrateRetroarchFolderActivity.this.getPackageName(); + resolver = MigrateRetroarchFolderActivity.this.getContentResolver(); + } + @Override + protected Boolean doInBackground(Uri... params) + { + sourceRoot = params[0]; + error = false; + progress = new ArrayList<>(); + + String destination = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + PACKAGE_NAME + "/files/RetroArch"; + copyFolder(sourceRoot, new File(destination)); + return !error; + } + void copyFolder(Uri sourceUri, File dest) + { + //create destination folder + if(!(dest.isDirectory() || dest.mkdirs())) { + Log.e("MigrateRetroarchFolder", "Couldn't make new destination folder " + dest.getPath()); + error = true; + return; + } + + Uri sourceChildrenResolver; + try{ //for subfolders + sourceChildrenResolver = DocumentsContract.buildChildDocumentsUriUsingTree(sourceUri, DocumentsContract.getDocumentId(sourceUri)); + }catch(IllegalArgumentException ex){ //for root selected by document picker + sourceChildrenResolver = DocumentsContract.buildChildDocumentsUriUsingTree(sourceUri, DocumentsContract.getTreeDocumentId(sourceUri)); + } + progress.add(new int[]{0, 1}); + try( + Cursor c = resolver.query(sourceChildrenResolver, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null) + ) { + if(c == null) { + Log.e("MigrateRetroarchFolder", "Could not list files in source folder " + sourceUri.toString()); + error = true; + return; + } + progress.get(progress.size() - 1)[1] = c.getCount(); + while(c.moveToNext()){ //loop through children returned + String childFilename = c.getString(1); + Uri childUri = DocumentsContract.buildDocumentUriUsingTree(sourceUri, c.getString(0)); + String childDocumentId = DocumentsContract.getDocumentId(childUri); + File destFile = new File(dest, childFilename); + + if(c.getString(2).equals(DocumentsContract.Document.MIME_TYPE_DIR)){ //is a folder, recurse + copyFolder(childUri, destFile); + }else{ //is a file, copy it + try( + ParcelFileDescriptor pfd = resolver.openFileDescriptor(childUri, "r"); + ParcelFileDescriptor.AutoCloseInputStream sourceStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + ) { + Files.copy(sourceStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + }catch(Exception ex){ + Log.e("MigrateRetroarchFolder", "Error copying file " + childDocumentId, ex); + error = true; + } + } + progress.get(progress.size() - 1)[0]++; + publishProgress(new Pair(getProgressPercentage(), destFile.getPath())); + } + }catch(Exception ex){ + Log.e("MigrateRetroarchFolder", "Error while copying", ex); + error = true; + } + progress.remove(progress.size() - 1); + } + int getProgressPercentage() + { + float sum = 0; + int lastDenominator = 1; + for(int[] frac : progress){ + sum += ((float) frac[0]) / frac[1] / lastDenominator; + lastDenominator *= frac[1]; + } + return (int) (sum * 100); + } + } +} From 3dab003b19d5b9fa096de2be2672029f80655acb Mon Sep 17 00:00:00 2001 From: cwyc Date: Thu, 4 Apr 2024 16:37:21 -0400 Subject: [PATCH 12/12] Improvements to migration code Will be squashed before commit --- .../MigrateRetroarchFolderActivity.java | 106 ++++++++++++------ 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java index 1916aaa5821..b02b6f6e102 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java @@ -13,6 +13,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; +import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; @@ -23,10 +24,18 @@ import com.retroarch.R; import java.io.File; -import java.lang.ref.WeakReference; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.stream.Stream; + @TargetApi(26) public class MigrateRetroarchFolderActivity extends Activity @@ -34,9 +43,9 @@ public class MigrateRetroarchFolderActivity extends Activity final int REQUEST_CODE_GET_OLD_RETROARCH_FOLDER = 125; @Override - public void onStart() + public void onCreate(Bundle savedInstanceState) { - super.onStart(); + super.onCreate(savedInstanceState); // Needs v26 for some of the file handling functions below. // Remove the TargetApi annotation to see which. @@ -44,7 +53,7 @@ public void onStart() if (android.os.Build.VERSION.SDK_INT < 26) { finish(); } - if(true || needToMigrate()){ + if(needToMigrate()){ askToMigrate(); }else{ finish(); @@ -79,6 +88,7 @@ boolean needToMigrate() void askToMigrate() { AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setCancelable(false); builder.setTitle(R.string.migrate_retroarch_folder_dialog_title); builder.setMessage(R.string.migrate_retroarch_folder_dialog_message); builder.setNegativeButton(R.string.migrate_retroarch_folder_dialog_negative, new DialogInterface.OnClickListener() { @@ -128,7 +138,7 @@ void copyFiles(Uri sourceDir) final ProgressDialog pd = new ProgressDialog(this); pd.setMax(100); pd.setTitle(R.string.migrate_retroarch_folder_inprogress); - pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + pd.setProgressStyle(ProgressDialog.STYLE_SPINNER); pd.setCancelable(false); CopyThread thread = new CopyThread() @@ -139,11 +149,10 @@ protected void onPreExecute(){ pd.show(); } @Override - protected void onProgressUpdate(Pair... params) + protected void onProgressUpdate(String... params) { super.onProgressUpdate(params); - pd.setProgress(params[0].first); - pd.setMessage(params[0].second); + pd.setMessage(params[0]); } @Override protected void onPostExecute(Boolean ok) @@ -161,7 +170,7 @@ void postMigrate(boolean ok) { SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); editor.putBoolean("external_retroarch_folder_needs_migrate", false); - editor.commit(); + editor.apply(); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(ok ? @@ -176,27 +185,24 @@ void postMigrate(boolean ok) builder.create().show(); } - class CopyThread extends AsyncTask, Boolean> + class CopyThread extends AsyncTask { - String PACKAGE_NAME; ContentResolver resolver; - Uri sourceRoot; boolean error; - ArrayList progress; - public CopyThread() + @Override + protected void onPreExecute() { - PACKAGE_NAME = MigrateRetroarchFolderActivity.this.getPackageName(); resolver = MigrateRetroarchFolderActivity.this.getContentResolver(); + error = false; } @Override protected Boolean doInBackground(Uri... params) { - sourceRoot = params[0]; + Uri source = params[0]; error = false; - progress = new ArrayList<>(); - - String destination = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + PACKAGE_NAME + "/files/RetroArch"; - copyFolder(sourceRoot, new File(destination)); + String destination = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + MigrateRetroarchFolderActivity.this.getPackageName() + "/files/RetroArch"; + copyFolder(source, new File(destination)); + patchConfig(); return !error; } void copyFolder(Uri sourceUri, File dest) @@ -214,8 +220,8 @@ void copyFolder(Uri sourceUri, File dest) }catch(IllegalArgumentException ex){ //for root selected by document picker sourceChildrenResolver = DocumentsContract.buildChildDocumentsUriUsingTree(sourceUri, DocumentsContract.getTreeDocumentId(sourceUri)); } - progress.add(new int[]{0, 1}); try( + //list children of directory Cursor c = resolver.query(sourceChildrenResolver, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null) ) { if(c == null) { @@ -223,7 +229,6 @@ void copyFolder(Uri sourceUri, File dest) error = true; return; } - progress.get(progress.size() - 1)[1] = c.getCount(); while(c.moveToNext()){ //loop through children returned String childFilename = c.getString(1); Uri childUri = DocumentsContract.buildDocumentUriUsingTree(sourceUri, c.getString(0)); @@ -243,24 +248,59 @@ void copyFolder(Uri sourceUri, File dest) error = true; } } - progress.get(progress.size() - 1)[0]++; - publishProgress(new Pair(getProgressPercentage(), destFile.getPath())); + publishProgress(destFile.toString()); } }catch(Exception ex){ Log.e("MigrateRetroarchFolder", "Error while copying", ex); error = true; } - progress.remove(progress.size() - 1); } - int getProgressPercentage() - { - float sum = 0; - int lastDenominator = 1; - for(int[] frac : progress){ - sum += ((float) frac[0]) / frac[1] / lastDenominator; - lastDenominator *= frac[1]; + void patchConfig(){ + String appDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + MigrateRetroarchFolderActivity.this.getPackageName() + "/files"; + String legacyDefaultRetroarchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/RetroArch"; + String migratedRetroarchPath = appDir + "/RetroArch"; + + final ArrayList files = new ArrayList<>(); + try ( + Stream s = Files.find(Paths.get(appDir), 30, new BiPredicate() { + @Override + public boolean test(Path path, BasicFileAttributes basicFileAttributes) { + String p = path.toString(); + return p.endsWith(".lpl") || p.endsWith(".cfg"); + } + }) + ){ + // yes it's a roudabout way to gather it in a list, but Stream.collect is throwing type errors + s.forEach(new Consumer() { + @Override + public void accept(Path path) { + files.add(path); + } + }); + }catch(IOException ex){ + Log.e("MigrateRetroarchFolder", "Error searching for config files", ex); + error = true; + return; + } + + for(Path file : files){ + try{ + //back up before patching + Path backupFile = file.resolveSibling(file.getFileName() + ".old"); + Files.copy(file, backupFile, StandardCopyOption.REPLACE_EXISTING); + + //replace old retroarch prefix with new path + //assumes default old path, doesn't help if user has relocated it + //not doing any syntactical analysis because the search string is pretty long and unique + String contents = new String(Files.readAllBytes(file)); + String replaced = contents.replace(legacyDefaultRetroarchPath, migratedRetroarchPath); + Files.write(file, replaced.getBytes(StandardCharsets.UTF_8)); + }catch(IOException ex){ + Log.e("MigrateRetroarchFolder", "Error patching file " + file.toAbsolutePath(), ex); + error = true; + return; + } } - return (int) (sum * 100); } } }