From ea18b4ea1f853c90e84d27eb0a787fd061014449 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 31 Dec 2023 16:28:21 +0100 Subject: [PATCH 01/23] Move import export manager to separate folder --- .../BackupRestoreSettingsFragment.java | 5 ++-- .../ImportExportManager.kt} | 5 ++-- ...agerTest.kt => ImportExportManagerTest.kt} | 25 ++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) rename app/src/main/java/org/schabi/newpipe/settings/{ContentSettingsManager.kt => export/ImportExportManager.kt} (96%) rename app/src/test/java/org/schabi/newpipe/settings/{ContentSettingsManagerTest.kt => ImportExportManagerTest.kt} (87%) diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java index bc24fbe812..842023e503 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -24,6 +24,7 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.settings.export.ImportExportManager; import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.NavigationHelper; @@ -42,7 +43,7 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment { private final SimpleDateFormat exportDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); - private ContentSettingsManager manager; + private ImportExportManager manager; private String importExportDataPathKey; private final ActivityResultLauncher requestImportPathLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), @@ -57,7 +58,7 @@ public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) { final File homeDir = ContextCompat.getDataDir(requireContext()); Objects.requireNonNull(homeDir); - manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir)); + manager = new ImportExportManager(new NewPipeFileLocator(homeDir)); manager.deleteSettingsFile(); importExportDataPathKey = getString(R.string.import_export_data_path); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt similarity index 96% rename from app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt rename to app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index df56de5166..9954a2b36e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -1,8 +1,9 @@ -package org.schabi.newpipe.settings +package org.schabi.newpipe.settings.export import android.content.SharedPreferences import android.util.Log import org.schabi.newpipe.MainActivity.DEBUG +import org.schabi.newpipe.settings.NewPipeFileLocator import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper @@ -11,7 +12,7 @@ import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream -class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { +class ImportExportManager(private val fileLocator: NewPipeFileLocator) { companion object { const val TAG = "ContentSetManager" } diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt similarity index 87% rename from app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt rename to app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt index ec41a77f87..7b219df189 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -17,6 +17,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.Mockito.withSettings import org.mockito.junit.MockitoJUnitRunner +import org.schabi.newpipe.settings.export.ImportExportManager import org.schabi.newpipe.streams.io.StoredFileHelper import us.shandian.giga.io.FileStream import java.io.File @@ -25,10 +26,10 @@ import java.nio.file.Files import java.util.zip.ZipFile @RunWith(MockitoJUnitRunner::class) -class ContentSettingsManagerTest { +class ImportExportManagerTest { companion object { - private val classloader = ContentSettingsManager::class.java.classLoader!! + private val classloader = ImportExportManager::class.java.classLoader!! } private lateinit var fileLocator: NewPipeFileLocator @@ -54,7 +55,7 @@ class ContentSettingsManagerTest { val output = File.createTempFile("newpipe_", "") `when`(storedFileHelper.stream).thenReturn(FileStream(output)) - ContentSettingsManager(fileLocator).exportDatabase(sharedPreferences, storedFileHelper) + ImportExportManager(fileLocator).exportDatabase(sharedPreferences, storedFileHelper) val zipFile = ZipFile(output) val entries = zipFile.entries().toList() @@ -77,7 +78,7 @@ class ContentSettingsManagerTest { val settings = File.createTempFile("newpipe_", "") `when`(fileLocator.settings).thenReturn(settings) - ContentSettingsManager(fileLocator).deleteSettingsFile() + ImportExportManager(fileLocator).deleteSettingsFile() assertFalse(settings.exists()) } @@ -87,7 +88,7 @@ class ContentSettingsManagerTest { val settings = File("non_existent") `when`(fileLocator.settings).thenReturn(settings) - ContentSettingsManager(fileLocator).deleteSettingsFile() + ImportExportManager(fileLocator).deleteSettingsFile() assertFalse(settings.exists()) } @@ -98,7 +99,7 @@ class ContentSettingsManagerTest { Assume.assumeTrue(dir.delete()) `when`(fileLocator.dbDir).thenReturn(dir) - ContentSettingsManager(fileLocator).ensureDbDirectoryExists() + ImportExportManager(fileLocator).ensureDbDirectoryExists() assertTrue(dir.exists()) } @@ -107,7 +108,7 @@ class ContentSettingsManagerTest { val dir = Files.createTempDirectory("newpipe_").toFile() `when`(fileLocator.dbDir).thenReturn(dir) - ContentSettingsManager(fileLocator).ensureDbDirectoryExists() + ImportExportManager(fileLocator).ensureDbDirectoryExists() assertTrue(dir.exists()) } @@ -124,7 +125,7 @@ class ContentSettingsManagerTest { val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) - val success = ContentSettingsManager(fileLocator).extractDb(storedFileHelper) + val success = ImportExportManager(fileLocator).extractDb(storedFileHelper) assertTrue(success) assertFalse(dbJournal.exists()) @@ -143,7 +144,7 @@ class ContentSettingsManagerTest { val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) - val success = ContentSettingsManager(fileLocator).extractDb(storedFileHelper) + val success = ImportExportManager(fileLocator).extractDb(storedFileHelper) assertFalse(success) assertTrue(dbJournal.exists()) @@ -159,7 +160,7 @@ class ContentSettingsManagerTest { val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) - val contains = ContentSettingsManager(fileLocator).extractSettings(storedFileHelper) + val contains = ImportExportManager(fileLocator).extractSettings(storedFileHelper) assertTrue(contains) } @@ -171,7 +172,7 @@ class ContentSettingsManagerTest { val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) - val contains = ContentSettingsManager(fileLocator).extractSettings(storedFileHelper) + val contains = ImportExportManager(fileLocator).extractSettings(storedFileHelper) assertFalse(contains) } @@ -185,7 +186,7 @@ class ContentSettingsManagerTest { val editor = Mockito.mock(SharedPreferences.Editor::class.java) `when`(preferences.edit()).thenReturn(editor) - ContentSettingsManager(fileLocator).loadSharedPreferences(preferences) + ImportExportManager(fileLocator).loadSharedPreferences(preferences) verify(editor, atLeastOnce()).putBoolean(anyString(), anyBoolean()) verify(editor, atLeastOnce()).putString(anyString(), anyString()) From 235fb926382cfed5a6353106c732e85a9140d6da Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 31 Dec 2023 18:11:35 +0100 Subject: [PATCH 02/23] Make checkstyle accept javadocs with long links --- checkstyle/checkstyle.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml index 3377e3b843..ee091fa9f4 100644 --- a/checkstyle/checkstyle.xml +++ b/checkstyle/checkstyle.xml @@ -39,11 +39,13 @@ - + + + From d75a6eaa4164d3fc0ac67bc35048b021b85e9b6f Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 31 Dec 2023 18:21:48 +0100 Subject: [PATCH 03/23] Fix vulnerability with whitelist-aware ObjectInputStream Only a few specific classes are now allowed. --- .../settings/export/ImportExportManager.kt | 5 +- .../export/PreferencesObjectInputStream.java | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 9954a2b36e..298841c5ff 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -8,7 +8,6 @@ import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper import java.io.IOException -import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream @@ -78,7 +77,9 @@ class ImportExportManager(private val fileLocator: NewPipeFileLocator) { try { val preferenceEditor = preferences.edit() - ObjectInputStream(fileLocator.settings.inputStream()).use { input -> + PreferencesObjectInputStream( + fileLocator.settings.inputStream() + ).use { input -> preferenceEditor.clear() @Suppress("UNCHECKED_CAST") val entries = input.readObject() as Map diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java b/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java new file mode 100644 index 0000000000..0d11b0b61c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java @@ -0,0 +1,58 @@ +package org.schabi.newpipe.settings.export; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.Set; + +/** + * An {@link ObjectInputStream} that only allows preferences-related types to be deserialized, to + * prevent injections. The only allowed types are: all primitive types, all boxed primitive types, + * null, strings. HashMap, HashSet and arrays of previously defined types are also allowed. Sources: + * + * cmu.edu + * , + * + * OWASP cheatsheet + * , + * + * Apache's {@code ValidatingObjectInputStream} + * + */ +public class PreferencesObjectInputStream extends ObjectInputStream { + + /** + * Primitive types, strings and other built-in types do not pass through resolveClass() but + * instead have a custom encoding; see + * + * official docs. + */ + private static final Set CLASS_WHITELIST = Set.of( + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.Character", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", + "java.lang.Void", + "java.util.HashMap", + "java.util.HashSet" + ); + + public PreferencesObjectInputStream(final InputStream in) throws IOException { + super(in); + } + + @Override + protected Class resolveClass(final ObjectStreamClass desc) + throws ClassNotFoundException, IOException { + if (CLASS_WHITELIST.contains(desc.getName())) { + return super.resolveClass(desc); + } else { + throw new ClassNotFoundException("Class not allowed: " + desc.getName()); + } + } +} From d8668ed226dccf36666fc325204acdb29a078f6d Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 27 Mar 2024 15:02:27 +0100 Subject: [PATCH 04/23] Show snackbar error when settings import fails --- .../org/schabi/newpipe/error/UserAction.java | 1 + .../BackupRestoreSettingsFragment.java | 17 ++++- .../settings/export/ImportExportManager.kt | 69 ++++++++----------- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index c8701cd779..6ca66e0d2a 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -6,6 +6,7 @@ public enum UserAction { USER_REPORT("user report"), UI_ERROR("ui error"), + DATABASE_IMPORT_EXPORT("database import or export"), SUBSCRIPTION_CHANGE("subscription change"), SUBSCRIPTION_UPDATE("subscription update"), SUBSCRIPTION_GET("get subscription"), diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java index 842023e503..1d00ef287d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -23,7 +23,9 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.settings.export.ImportExportManager; import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; @@ -166,7 +168,7 @@ private void exportDatabase(final StoredFileHelper file, final Uri exportDataUri Toast.makeText(requireContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT) .show(); } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e); + showErrorSnackbar(e, "Exporting database and settings"); } } @@ -202,7 +204,12 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri final Context context = requireContext(); final SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - manager.loadSharedPreferences(prefs); + try { + manager.loadSharedPreferences(prefs); + } catch (IOException | ClassNotFoundException e) { + showErrorSnackbar(e, "Importing preferences"); + return; + } cleanImport(context, prefs); finishImport(importDataUri); }) @@ -211,7 +218,7 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri finishImport(importDataUri); } } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(this, "Importing database", e); + showErrorSnackbar(e, "Importing database and settings"); } } @@ -269,4 +276,8 @@ private void saveLastImportExportDataUri(final Uri importExportDataUri) { .putString(importExportDataPathKey, importExportDataUri.toString()); editor.apply(); } + + private void showErrorSnackbar(final Throwable e, final String request) { + ErrorUtil.showSnackbar(this, new ErrorInfo(e, UserAction.DATABASE_IMPORT_EXPORT, request)); + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 298841c5ff..b4503bdd60 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -73,50 +73,41 @@ class ImportExportManager(private val fileLocator: NewPipeFileLocator) { /** * Remove all shared preferences from the app and load the preferences supplied to the manager. */ + @Throws(IOException::class, ClassNotFoundException::class) fun loadSharedPreferences(preferences: SharedPreferences) { - try { - val preferenceEditor = preferences.edit() + val preferenceEditor = preferences.edit() - PreferencesObjectInputStream( - fileLocator.settings.inputStream() - ).use { input -> - preferenceEditor.clear() - @Suppress("UNCHECKED_CAST") - val entries = input.readObject() as Map - for ((key, value) in entries) { - when (value) { - is Boolean -> { - preferenceEditor.putBoolean(key, value) - } - is Float -> { - preferenceEditor.putFloat(key, value) - } - is Int -> { - preferenceEditor.putInt(key, value) - } - is Long -> { - preferenceEditor.putLong(key, value) - } - is String -> { - preferenceEditor.putString(key, value) - } - is Set<*> -> { - // There are currently only Sets with type String possible - @Suppress("UNCHECKED_CAST") - preferenceEditor.putStringSet(key, value as Set?) - } + PreferencesObjectInputStream( + fileLocator.settings.inputStream() + ).use { input -> + preferenceEditor.clear() + @Suppress("UNCHECKED_CAST") + val entries = input.readObject() as Map + for ((key, value) in entries) { + when (value) { + is Boolean -> { + preferenceEditor.putBoolean(key, value) + } + is Float -> { + preferenceEditor.putFloat(key, value) + } + is Int -> { + preferenceEditor.putInt(key, value) + } + is Long -> { + preferenceEditor.putLong(key, value) + } + is String -> { + preferenceEditor.putString(key, value) + } + is Set<*> -> { + // There are currently only Sets with type String possible + @Suppress("UNCHECKED_CAST") + preferenceEditor.putStringSet(key, value as Set?) } } - preferenceEditor.commit() - } - } catch (e: IOException) { - if (DEBUG) { - Log.e(TAG, "Unable to loadSharedPreferences", e) - } - } catch (e: ClassNotFoundException) { - if (DEBUG) { - Log.e(TAG, "Unable to loadSharedPreferences", e) } + preferenceEditor.commit() } } } From 6afdbd6fd39cd1c5020dafde65215cfa66fa781f Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 27 Mar 2024 15:12:57 +0100 Subject: [PATCH 05/23] Add test: vulnerable settings should fail importing --- .../settings/ImportExportManagerTest.kt | 19 ++++++++++++++++++ .../settings/vulnerable_serialization.zip | Bin 0 -> 3536 bytes 2 files changed, 19 insertions(+) create mode 100644 app/src/test/resources/settings/vulnerable_serialization.zip diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt index 7b219df189..2743ba0988 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -3,6 +3,7 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Assume import org.junit.Before @@ -192,4 +193,22 @@ class ImportExportManagerTest { verify(editor, atLeastOnce()).putString(anyString(), anyString()) verify(editor, atLeastOnce()).putInt(anyString(), anyInt()) } + + @Test + fun `Importing preferences with a serialization injected class should fail`() { + val settings = File.createTempFile("newpipe_", "") + `when`(fileLocator.settings).thenReturn(settings) + + val emptyZip = File(classloader.getResource("settings/vulnerable_serialization.zip")?.file!!) + `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) + Assume.assumeTrue(ImportExportManager(fileLocator).extractSettings(storedFileHelper)) + + val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly()) + val editor = Mockito.mock(SharedPreferences.Editor::class.java) + `when`(preferences.edit()).thenReturn(editor) + + assertThrows(ClassNotFoundException::class.java) { + ImportExportManager(fileLocator).loadSharedPreferences(preferences) + } + } } diff --git a/app/src/test/resources/settings/vulnerable_serialization.zip b/app/src/test/resources/settings/vulnerable_serialization.zip new file mode 100644 index 0000000000000000000000000000000000000000..d57a5f8d0150cd11dec35d7c29ed2bcb9e65a774 GIT binary patch literal 3536 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 D$x)Jd literal 0 HcmV?d00001 From d8423499dc695f06e30209047c9274ba6b6d21f2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 27 Mar 2024 12:31:16 +0100 Subject: [PATCH 06/23] Use JSON for settings imports/exports --- .../BackupRestoreSettingsFragment.java | 20 ++- .../newpipe/settings/NewPipeFileLocator.kt | 21 --- .../settings/export/BackupFileLocator.kt | 28 +++ .../settings/export/ImportExportManager.kt | 156 ++++++++++------ .../org/schabi/newpipe/util/ZipHelper.java | 170 +++++++++++++----- app/src/main/res/values/strings.xml | 1 + .../settings/ImportExportManagerTest.kt | 60 ++----- .../test/resources/settings/newpipe.settings | Bin 2445 -> 0 bytes 8 files changed, 292 insertions(+), 164 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt delete mode 100644 app/src/test/resources/settings/newpipe.settings diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java index 1d00ef287d..f4080acd34 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -21,11 +21,14 @@ import androidx.preference.Preference; import androidx.preference.PreferenceManager; +import com.grack.nanojson.JsonParserException; + import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.settings.export.BackupFileLocator; import org.schabi.newpipe.settings.export.ImportExportManager; import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; @@ -60,8 +63,7 @@ public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) { final File homeDir = ContextCompat.getDataDir(requireContext()); Objects.requireNonNull(homeDir); - manager = new ImportExportManager(new NewPipeFileLocator(homeDir)); - manager.deleteSettingsFile(); + manager = new ImportExportManager(new BackupFileLocator(homeDir)); importExportDataPathKey = getString(R.string.import_export_data_path); @@ -192,9 +194,13 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri } // if settings file exist, ask if it should be imported. - if (manager.extractSettings(file)) { + final boolean hasJsonPrefs = manager.exportHasJsonPrefs(file); + if (hasJsonPrefs || manager.exportHasSerializedPrefs(file)) { new androidx.appcompat.app.AlertDialog.Builder(requireContext()) .setTitle(R.string.import_settings) + .setMessage(hasJsonPrefs ? null : requireContext() + .getString(R.string.import_settings_vulnerable_format)) + .setOnDismissListener(dialog -> finishImport(importDataUri)) .setNegativeButton(R.string.cancel, (dialog, which) -> { dialog.dismiss(); finishImport(importDataUri); @@ -205,8 +211,12 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri final SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); try { - manager.loadSharedPreferences(prefs); - } catch (IOException | ClassNotFoundException e) { + if (hasJsonPrefs) { + manager.loadJsonPrefs(file, prefs); + } else { + manager.loadSerializedPrefs(file, prefs); + } + } catch (IOException | ClassNotFoundException | JsonParserException e) { showErrorSnackbar(e, "Importing preferences"); return; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt b/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt deleted file mode 100644 index c2f93d15f0..0000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.settings - -import java.io.File - -/** - * Locates specific files of NewPipe based on the home directory of the app. - */ -class NewPipeFileLocator(private val homeDir: File) { - - val dbDir by lazy { File(homeDir, "/databases") } - - val db by lazy { File(homeDir, "/databases/newpipe.db") } - - val dbJournal by lazy { File(homeDir, "/databases/newpipe.db-journal") } - - val dbShm by lazy { File(homeDir, "/databases/newpipe.db-shm") } - - val dbWal by lazy { File(homeDir, "/databases/newpipe.db-wal") } - - val settings by lazy { File(homeDir, "/databases/newpipe.settings") } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt b/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt new file mode 100644 index 0000000000..c864e4a0df --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt @@ -0,0 +1,28 @@ +package org.schabi.newpipe.settings.export + +import java.io.File + +/** + * Locates specific files of NewPipe based on the home directory of the app. + */ +class BackupFileLocator(private val homeDir: File) { + companion object { + const val FILE_NAME_DB = "newpipe.db" + @Deprecated( + "Serializing preferences with Java's ObjectOutputStream is vulnerable to injections", + replaceWith = ReplaceWith("FILE_NAME_JSON_PREFS") + ) + const val FILE_NAME_SERIALIZED_PREFS = "newpipe.settings" + const val FILE_NAME_JSON_PREFS = "preferences.json" + } + + val dbDir by lazy { File(homeDir, "/databases") } + + val db by lazy { File(dbDir, FILE_NAME_DB) } + + val dbJournal by lazy { File(dbDir, "$FILE_NAME_DB-journal") } + + val dbShm by lazy { File(dbDir, "$FILE_NAME_DB-shm") } + + val dbWal by lazy { File(dbDir, "$FILE_NAME_DB-wal") } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index b4503bdd60..339ebf644e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -2,8 +2,10 @@ package org.schabi.newpipe.settings.export import android.content.SharedPreferences import android.util.Log -import org.schabi.newpipe.MainActivity.DEBUG -import org.schabi.newpipe.settings.NewPipeFileLocator +import com.grack.nanojson.JsonArray +import com.grack.nanojson.JsonParser +import com.grack.nanojson.JsonParserException +import com.grack.nanojson.JsonWriter import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper @@ -11,9 +13,9 @@ import java.io.IOException import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream -class ImportExportManager(private val fileLocator: NewPipeFileLocator) { +class ImportExportManager(private val fileLocator: BackupFileLocator) { companion object { - const val TAG = "ContentSetManager" + const val TAG = "ImportExportManager" } /** @@ -23,27 +25,41 @@ class ImportExportManager(private val fileLocator: NewPipeFileLocator) { @Throws(Exception::class) fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { file.create() - ZipOutputStream(SharpOutputStream(file.stream).buffered()) - .use { outZip -> - ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db") + ZipOutputStream(SharpOutputStream(file.stream).buffered()).use { outZip -> + try { + // add the database + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_DB, + fileLocator.db.path, + ) - try { - ObjectOutputStream(fileLocator.settings.outputStream()).use { output -> + // add the legacy vulnerable serialized preferences (will be removed in the future) + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_SERIALIZED_PREFS + ) { byteOutput -> + ObjectOutputStream(byteOutput).use { output -> output.writeObject(preferences.all) output.flush() } - } catch (e: IOException) { - if (DEBUG) { - Log.e(TAG, "Unable to exportDatabase", e) - } } - ZipHelper.addFileToZip(outZip, fileLocator.settings.path, "newpipe.settings") + // add the JSON preferences + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_JSON_PREFS + ) { byteOutput -> + JsonWriter + .indent("") + .on(byteOutput) + .`object`(preferences.all) + .done() + } + } catch (e: Exception) { + Log.e(TAG, "Unable to export serialized settings", e) } - } - - fun deleteSettingsFile() { - fileLocator.settings.delete() + } } /** @@ -56,7 +72,12 @@ class ImportExportManager(private val fileLocator: NewPipeFileLocator) { } fun extractDb(file: StoredFileHelper): Boolean { - val success = ZipHelper.extractFileFromZip(file, fileLocator.db.path, "newpipe.db") + val success = ZipHelper.extractFileFromZip( + file, + BackupFileLocator.FILE_NAME_DB, + fileLocator.db.path, + ) + if (success) { fileLocator.dbJournal.delete() fileLocator.dbWal.delete() @@ -66,48 +87,81 @@ class ImportExportManager(private val fileLocator: NewPipeFileLocator) { return success } - fun extractSettings(file: StoredFileHelper): Boolean { - return ZipHelper.extractFileFromZip(file, fileLocator.settings.path, "newpipe.settings") + @Deprecated( + "Serializing preferences with Java's ObjectOutputStream is vulnerable to injections", + replaceWith = ReplaceWith("exportHasJsonPrefs") + ) + fun exportHasSerializedPrefs(zipFile: StoredFileHelper): Boolean { + return ZipHelper.zipContainsFile(zipFile, BackupFileLocator.FILE_NAME_SERIALIZED_PREFS) + } + + fun exportHasJsonPrefs(zipFile: StoredFileHelper): Boolean { + return ZipHelper.zipContainsFile(zipFile, BackupFileLocator.FILE_NAME_JSON_PREFS) } /** * Remove all shared preferences from the app and load the preferences supplied to the manager. */ + @Deprecated( + "Serializing preferences with Java's ObjectOutputStream is vulnerable to injections", + replaceWith = ReplaceWith("loadJsonPrefs") + ) @Throws(IOException::class, ClassNotFoundException::class) - fun loadSharedPreferences(preferences: SharedPreferences) { - val preferenceEditor = preferences.edit() - - PreferencesObjectInputStream( - fileLocator.settings.inputStream() - ).use { input -> - preferenceEditor.clear() - @Suppress("UNCHECKED_CAST") - val entries = input.readObject() as Map - for ((key, value) in entries) { - when (value) { - is Boolean -> { - preferenceEditor.putBoolean(key, value) - } - is Float -> { - preferenceEditor.putFloat(key, value) - } - is Int -> { - preferenceEditor.putInt(key, value) + fun loadSerializedPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) { + ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_SERIALIZED_PREFS) { + PreferencesObjectInputStream(it).use { input -> + val editor = preferences.edit() + editor.clear() + @Suppress("UNCHECKED_CAST") + val entries = input.readObject() as Map + for ((key, value) in entries) { + when (value) { + is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is Set<*> -> { + // There are currently only Sets with type String possible + @Suppress("UNCHECKED_CAST") + editor.putStringSet(key, value as Set?) + } } - is Long -> { - preferenceEditor.putLong(key, value) - } - is String -> { - preferenceEditor.putString(key, value) - } - is Set<*> -> { - // There are currently only Sets with type String possible - @Suppress("UNCHECKED_CAST") - preferenceEditor.putStringSet(key, value as Set?) + } + + if (!editor.commit()) { + Log.e(TAG, "Unable to loadSerializedPrefs") + } + } + } + } + + /** + * Remove all shared preferences from the app and load the preferences supplied to the manager. + */ + @Throws(JsonParserException::class) + fun loadJsonPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) { + ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_JSON_PREFS) { + val editor = preferences.edit() + editor.clear() + + val jsonObject = JsonParser.`object`().from(it) + for ((key, value) in jsonObject) { + when (value) { + is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is JsonArray -> { + editor.putStringSet(key, value.mapNotNull { e -> e as? String }.toSet()) } } } - preferenceEditor.commit() + + if (!editor.commit()) { + Log.e(TAG, "Unable to loadJsonPrefs") + } } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java index bc08e6197f..b2aebac426 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java @@ -1,18 +1,21 @@ package org.schabi.newpipe.util; import org.schabi.newpipe.streams.io.SharpInputStream; +import org.schabi.newpipe.streams.io.StoredFileHelper; import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import org.schabi.newpipe.streams.io.StoredFileHelper; - /** * Created by Christian Schabesberger on 28.01.18. * Copyright 2018 Christian Schabesberger @@ -34,73 +37,154 @@ */ public final class ZipHelper { - private ZipHelper() { } private static final int BUFFER_SIZE = 2048; + @FunctionalInterface + public interface InputStreamConsumer { + void acceptStream(InputStream inputStream) throws IOException; + } + + @FunctionalInterface + public interface OutputStreamConsumer { + void acceptStream(OutputStream outputStream) throws IOException; + } + + + private ZipHelper() { } + + /** - * This function helps to create zip files. - * Caution this will override the original file. + * This function helps to create zip files. Caution this will overwrite the original file. * - * @param outZip The ZipOutputStream where the data should be stored in - * @param file The path of the file that should be added to zip. - * @param name The path of the file inside the zip. - * @throws Exception + * @param outZip the ZipOutputStream where the data should be stored in + * @param nameInZip the path of the file inside the zip + * @param fileOnDisk the path of the file on the disk that should be added to zip */ - public static void addFileToZip(final ZipOutputStream outZip, final String file, - final String name) throws Exception { + public static void addFileToZip(final ZipOutputStream outZip, + final String nameInZip, + final String fileOnDisk) throws IOException { + try (FileInputStream fi = new FileInputStream(fileOnDisk)) { + addFileToZip(outZip, nameInZip, fi); + } + } + + /** + * This function helps to create zip files. Caution this will overwrite the original file. + * + * @param outZip the ZipOutputStream where the data should be stored in + * @param nameInZip the path of the file inside the zip + * @param streamConsumer will be called with an output stream that will go to the output file + */ + public static void addFileToZip(final ZipOutputStream outZip, + final String nameInZip, + final OutputStreamConsumer streamConsumer) throws IOException { + final byte[] bytes; + try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { + streamConsumer.acceptStream(byteOutput); + bytes = byteOutput.toByteArray(); + } + + try (ByteArrayInputStream byteInput = new ByteArrayInputStream(bytes)) { + ZipHelper.addFileToZip(outZip, nameInZip, byteInput); + } + } + + /** + * This function helps to create zip files. Caution this will overwrite the original file. + * + * @param outZip the ZipOutputStream where the data should be stored in + * @param nameInZip the path of the file inside the zip + * @param inputStream the content to put inside the file + */ + public static void addFileToZip(final ZipOutputStream outZip, + final String nameInZip, + final InputStream inputStream) throws IOException { final byte[] data = new byte[BUFFER_SIZE]; - try (FileInputStream fi = new FileInputStream(file); - BufferedInputStream inputStream = new BufferedInputStream(fi, BUFFER_SIZE)) { - final ZipEntry entry = new ZipEntry(name); + try (BufferedInputStream bufferedInputStream = + new BufferedInputStream(inputStream, BUFFER_SIZE)) { + final ZipEntry entry = new ZipEntry(nameInZip); outZip.putNextEntry(entry); int count; - while ((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) { + while ((count = bufferedInputStream.read(data, 0, BUFFER_SIZE)) != -1) { outZip.write(data, 0, count); } } } + /** + * This will extract data from ZipInputStream. Caution this will overwrite the original file. + * + * @param zipFile the zip file to extract from + * @param nameInZip the path of the file inside the zip + * @param fileOnDisk the path of the file on the disk where the data should be extracted to + * @return will return true if the file was found within the zip file + */ + public static boolean extractFileFromZip(final StoredFileHelper zipFile, + final String nameInZip, + final String fileOnDisk) throws IOException { + return extractFileFromZip(zipFile, nameInZip, input -> { + // delete old file first + final File oldFile = new File(fileOnDisk); + if (oldFile.exists()) { + if (!oldFile.delete()) { + throw new IOException("Could not delete " + fileOnDisk); + } + } + + final byte[] data = new byte[BUFFER_SIZE]; + try (FileOutputStream outFile = new FileOutputStream(fileOnDisk)) { + int count; + while ((count = input.read(data)) != -1) { + outFile.write(data, 0, count); + } + } + }); + } + /** * This will extract data from ZipInputStream. - * Caution this will override the original file. * - * @param zipFile The zip file - * @param file The path of the file on the disk where the data should be extracted to. - * @param name The path of the file inside the zip. + * @param zipFile the zip file to extract from + * @param nameInZip the path of the file inside the zip + * @param streamConsumer will be called with the input stream from the file inside the zip * @return will return true if the file was found within the zip file - * @throws Exception */ - public static boolean extractFileFromZip(final StoredFileHelper zipFile, final String file, - final String name) throws Exception { + public static boolean extractFileFromZip(final StoredFileHelper zipFile, + final String nameInZip, + final InputStreamConsumer streamConsumer) + throws IOException { + try (ZipInputStream inZip = new ZipInputStream(new BufferedInputStream( + new SharpInputStream(zipFile.getStream())))) { + ZipEntry ze; + while ((ze = inZip.getNextEntry()) != null) { + if (ze.getName().equals(nameInZip)) { + streamConsumer.acceptStream(inZip); + return true; + } + } + + return false; + } + } + + /** + * @param zipFile the zip file + * @param fileInZip the filename to check + * @return whether the provided filename is in the zip; only the first level is checked + */ + public static boolean zipContainsFile(final StoredFileHelper zipFile, final String fileInZip) + throws Exception { try (ZipInputStream inZip = new ZipInputStream(new BufferedInputStream( new SharpInputStream(zipFile.getStream())))) { - final byte[] data = new byte[BUFFER_SIZE]; - boolean found = false; ZipEntry ze; while ((ze = inZip.getNextEntry()) != null) { - if (ze.getName().equals(name)) { - found = true; - // delete old file first - final File oldFile = new File(file); - if (oldFile.exists()) { - if (!oldFile.delete()) { - throw new Exception("Could not delete " + file); - } - } - - try (FileOutputStream outFile = new FileOutputStream(file)) { - int count = 0; - while ((count = inZip.read(data)) != -1) { - outFile.write(data, 0, count); - } - } - - inZip.closeEntry(); + if (ze.getName().equals(fileInZip)) { + return true; } } - return found; + return false; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4ad8b1d9f..56140441cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -856,4 +856,5 @@ Show more Show less + The settings in the export being imported use a vulnerable format that was deprecated since NewPipe 0.27.0. Make sure the export being imported is from a trusted source, and prefer using only exports obtained from NewPipe 0.27.0 or newer in the future. Support for importing settings in this vulnerable format will soon be removed completely, and then old versions of NewPipe will not be able to import settings of exports from new versions anymore. diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt index 2743ba0988..70420801ce 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences +import com.grack.nanojson.JsonParser import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows @@ -18,6 +19,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.Mockito.withSettings import org.mockito.junit.MockitoJUnitRunner +import org.schabi.newpipe.settings.export.BackupFileLocator import org.schabi.newpipe.settings.export.ImportExportManager import org.schabi.newpipe.streams.io.StoredFileHelper import us.shandian.giga.io.FileStream @@ -33,21 +35,19 @@ class ImportExportManagerTest { private val classloader = ImportExportManager::class.java.classLoader!! } - private lateinit var fileLocator: NewPipeFileLocator + private lateinit var fileLocator: BackupFileLocator private lateinit var storedFileHelper: StoredFileHelper @Before fun setupFileLocator() { - fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly()) + fileLocator = Mockito.mock(BackupFileLocator::class.java, withSettings().stubOnly()) storedFileHelper = Mockito.mock(StoredFileHelper::class.java, withSettings().stubOnly()) } @Test fun `The settings must be exported successfully in the correct format`() { val db = File(classloader.getResource("settings/newpipe.db")!!.file) - val newpipeSettings = File.createTempFile("newpipe_", "") `when`(fileLocator.db).thenReturn(db) - `when`(fileLocator.settings).thenReturn(newpipeSettings) val expectedPreferences = mapOf("such pref" to "much wow") val sharedPreferences = @@ -60,7 +60,7 @@ class ImportExportManagerTest { val zipFile = ZipFile(output) val entries = zipFile.entries().toList() - assertEquals(2, entries.size) + assertEquals(3, entries.size) zipFile.getInputStream(entries.first { it.name == "newpipe.db" }).use { actual -> db.inputStream().use { expected -> @@ -72,26 +72,11 @@ class ImportExportManagerTest { val actualPreferences = ObjectInputStream(actual).readObject() assertEquals(expectedPreferences, actualPreferences) } - } - - @Test - fun `Settings file must be deleted`() { - val settings = File.createTempFile("newpipe_", "") - `when`(fileLocator.settings).thenReturn(settings) - - ImportExportManager(fileLocator).deleteSettingsFile() - - assertFalse(settings.exists()) - } - - @Test - fun `Deleting settings file must do nothing if none exist`() { - val settings = File("non_existent") - `when`(fileLocator.settings).thenReturn(settings) - - ImportExportManager(fileLocator).deleteSettingsFile() - assertFalse(settings.exists()) + zipFile.getInputStream(entries.first { it.name == "preferences.json" }).use { actual -> + val actualPreferences = JsonParser.`object`().from(actual) + assertEquals(expectedPreferences, actualPreferences) + } } @Test @@ -156,38 +141,29 @@ class ImportExportManagerTest { @Test fun `Contains setting must return true if a settings file exists in the zip`() { - val settings = File.createTempFile("newpipe_", "") - `when`(fileLocator.settings).thenReturn(settings) - val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) - val contains = ImportExportManager(fileLocator).extractSettings(storedFileHelper) - - assertTrue(contains) + assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(storedFileHelper)) } @Test fun `Contains setting must return false if a no settings file exists in the zip`() { - val settings = File.createTempFile("newpipe_", "") - `when`(fileLocator.settings).thenReturn(settings) - val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) - val contains = ImportExportManager(fileLocator).extractSettings(storedFileHelper) - - assertFalse(contains) + assertFalse(ImportExportManager(fileLocator).exportHasSerializedPrefs(storedFileHelper)) } @Test fun `Preferences must be set from the settings file`() { - val settings = File(classloader.getResource("settings/newpipe.settings")!!.path) - `when`(fileLocator.settings).thenReturn(settings) + val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) + `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly()) val editor = Mockito.mock(SharedPreferences.Editor::class.java) `when`(preferences.edit()).thenReturn(editor) + `when`(editor.commit()).thenReturn(true) - ImportExportManager(fileLocator).loadSharedPreferences(preferences) + ImportExportManager(fileLocator).loadSerializedPrefs(storedFileHelper, preferences) verify(editor, atLeastOnce()).putBoolean(anyString(), anyBoolean()) verify(editor, atLeastOnce()).putString(anyString(), anyString()) @@ -196,19 +172,15 @@ class ImportExportManagerTest { @Test fun `Importing preferences with a serialization injected class should fail`() { - val settings = File.createTempFile("newpipe_", "") - `when`(fileLocator.settings).thenReturn(settings) - val emptyZip = File(classloader.getResource("settings/vulnerable_serialization.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) - Assume.assumeTrue(ImportExportManager(fileLocator).extractSettings(storedFileHelper)) val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly()) val editor = Mockito.mock(SharedPreferences.Editor::class.java) `when`(preferences.edit()).thenReturn(editor) assertThrows(ClassNotFoundException::class.java) { - ImportExportManager(fileLocator).loadSharedPreferences(preferences) + ImportExportManager(fileLocator).loadSerializedPrefs(storedFileHelper, preferences) } } } diff --git a/app/src/test/resources/settings/newpipe.settings b/app/src/test/resources/settings/newpipe.settings deleted file mode 100644 index 56e6c5d5dd53ba66b0144215b928f6137f67fc30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2445 zcmaJ@zi%8x7#-VjLL8FV`Qea26rcba>tWC@AwsfT$@bXs8f9v`AFEZ+6$YOH59>lXiFZn>X*h?|t{^FSQuFTKUxM znYEg2zINM$?2f4xkNp1A&#Mo9T~PD4)KczDddnp2{FXY(nWvDsJRMi>zNh5#VDub* zAL4tR)oUR!Icat}?AFGUwuKqux74L$JFtmi>&mddYi`Qn+I8o0GUYEX`-i`M_wV2H zYVH%YxM%X3#?>6F3)rzPNV`&-I<-y@Tv7);G`0-8>T|WASe>bZ>_TJU)PZP6@6bdZ z9%l6-@Q$>=Kq2-vVM=x4N~V(AB`+la;Pq}zHOZra^+iNFU8ZtXU7UAfw;Se;JYs1z#%U6?Ueu%DpT+pJEuT!Of|jCT)*Nnk2iU7A*1hui^( zD=HDGC;QI&U<;BA$)Ec3`yc-K;;T*gV5{?iSIg0*(Jyyv5#Y;5-(EcS%^zRSD>bg* zKDgjyR2nW|y=PN$t-8x{N_n#u6JT3b`v}iIK30IkWq}DhEse*!hq^MHv3f&jeP_L0 zxK2f#gT!>bw|>X%SqkfSX>`w4w9e{^t8Q-p483ZdsB*vt+ynCUU$oh43)KX42h z!4@eLKP2YDYl(LuXn0`c{EqGrcCQ_eRY9_--rYPe-2qm}mnLthiEbp{GPG*)96gee zsdQQwl`V%;ws?k$m!pbO;ErW9c(hCS))im5}2_^Pw<68fK2z8dISMv0Drk&AjoMs#>b9it&?BwVX2r$E{;3Vfb9jr!2~|;gdEOfJ zRg-wLmebHAlWVyrlTL29au110>3z4cwz<}8C4S|ZE8h>UY!@zahID4Nlp3^hdvU(< zG$8MR!&QWtmk5#u=!(flW)dW#+6HDd+uY5B6oU7g`vA$VNV({1Y1^Z4NS7l(@kHy2 zpk0{7RK@(2>B#D6b6HdytS;sTafKF5b%USEgn|Y=0qEttZ%)w|ni~KpkK%laVjRSU^ zjkP0)wMJ5$@#E%1cYR0S!jG8J9etsl{&8xzDqo6d(dI)d@yix%W`<6)%&E>_ Date: Sat, 30 Mar 2024 18:42:11 +0100 Subject: [PATCH 07/23] Add test zips and extensive tests for ImportExportManager Now all possible combinations of files in the zip (present or not) are checked at the same time --- .../settings/export/ImportExportManager.kt | 20 +- .../settings/ImportAllCombinationsTest.kt | 184 ++++++++++++++++++ .../settings/ImportExportManagerTest.kt | 16 +- app/src/test/resources/settings/README.md | 4 + .../test/resources/settings/db_noser_json.zip | Bin 0 -> 5428 bytes .../resources/settings/db_noser_nojson.zip | Bin 0 -> 4040 bytes .../test/resources/settings/db_ser_json.zip | Bin 0 -> 7243 bytes .../test/resources/settings/db_ser_nojson.zip | Bin 0 -> 5807 bytes .../resources/settings/db_vulnser_json.zip | Bin 0 -> 6752 bytes .../resources/settings/db_vulnser_nojson.zip | Bin 0 -> 5364 bytes app/src/test/resources/settings/newpipe.zip | Bin 8317 -> 0 bytes .../resources/settings/nodb_noser_json.zip | Bin 0 -> 1410 bytes .../{empty.zip => nodb_noser_nojson.zip} | Bin .../test/resources/settings/nodb_ser_json.zip | Bin 0 -> 3177 bytes .../resources/settings/nodb_ser_nojson.zip | Bin 0 -> 1789 bytes .../resources/settings/nodb_vulnser_json.zip | Bin 0 -> 2734 bytes .../settings/nodb_vulnser_nojson.zip | Bin 0 -> 1346 bytes .../settings/vulnerable_serialization.zip | Bin 3536 -> 0 bytes 18 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt create mode 100644 app/src/test/resources/settings/README.md create mode 100644 app/src/test/resources/settings/db_noser_json.zip create mode 100644 app/src/test/resources/settings/db_noser_nojson.zip create mode 100644 app/src/test/resources/settings/db_ser_json.zip create mode 100644 app/src/test/resources/settings/db_ser_nojson.zip create mode 100644 app/src/test/resources/settings/db_vulnser_json.zip create mode 100644 app/src/test/resources/settings/db_vulnser_nojson.zip delete mode 100644 app/src/test/resources/settings/newpipe.zip create mode 100644 app/src/test/resources/settings/nodb_noser_json.zip rename app/src/test/resources/settings/{empty.zip => nodb_noser_nojson.zip} (100%) create mode 100644 app/src/test/resources/settings/nodb_ser_json.zip create mode 100644 app/src/test/resources/settings/nodb_ser_nojson.zip create mode 100644 app/src/test/resources/settings/nodb_vulnser_json.zip create mode 100644 app/src/test/resources/settings/nodb_vulnser_nojson.zip delete mode 100644 app/src/test/resources/settings/vulnerable_serialization.zip diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 339ebf644e..5558a1b374 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -9,6 +9,7 @@ import com.grack.nanojson.JsonWriter import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper +import java.io.FileNotFoundException import java.io.IOException import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream @@ -110,10 +111,12 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { fun loadSerializedPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) { ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_SERIALIZED_PREFS) { PreferencesObjectInputStream(it).use { input -> - val editor = preferences.edit() - editor.clear() @Suppress("UNCHECKED_CAST") val entries = input.readObject() as Map + + val editor = preferences.edit() + editor.clear() + for ((key, value) in entries) { when (value) { is Boolean -> editor.putBoolean(key, value) @@ -133,19 +136,24 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { Log.e(TAG, "Unable to loadSerializedPrefs") } } + }.let { fileExists -> + if (!fileExists) { + throw FileNotFoundException(BackupFileLocator.FILE_NAME_SERIALIZED_PREFS) + } } } /** * Remove all shared preferences from the app and load the preferences supplied to the manager. */ - @Throws(JsonParserException::class) + @Throws(IOException::class, JsonParserException::class) fun loadJsonPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) { ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_JSON_PREFS) { + val jsonObject = JsonParser.`object`().from(it) + val editor = preferences.edit() editor.clear() - val jsonObject = JsonParser.`object`().from(it) for ((key, value) in jsonObject) { when (value) { is Boolean -> editor.putBoolean(key, value) @@ -162,6 +170,10 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { if (!editor.commit()) { Log.e(TAG, "Unable to loadJsonPrefs") } + }.let { fileExists -> + if (!fileExists) { + throw FileNotFoundException(BackupFileLocator.FILE_NAME_JSON_PREFS) + } } } } diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt new file mode 100644 index 0000000000..862ac3b80a --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt @@ -0,0 +1,184 @@ +package org.schabi.newpipe.settings + +import android.content.SharedPreferences +import org.junit.Assert +import org.junit.Test +import org.mockito.Mockito +import org.schabi.newpipe.settings.export.BackupFileLocator +import org.schabi.newpipe.settings.export.ImportExportManager +import org.schabi.newpipe.streams.io.StoredFileHelper +import us.shandian.giga.io.FileStream +import java.io.File +import java.io.IOException +import java.nio.file.Files + +class ImportAllCombinationsTest { + + companion object { + private val classloader = ImportExportManager::class.java.classLoader!! + } + + private enum class Ser(val id: String) { + YES("ser"), + VULNERABLE("vulnser"), + NO("noser"); + } + + private data class FailData( + val containsDb: Boolean, + val containsSer: Ser, + val containsJson: Boolean, + val filename: String, + val throwable: Throwable, + ) + + private fun testZipCombination( + containsDb: Boolean, + containsSer: Ser, + containsJson: Boolean, + filename: String, + runTest: (test: () -> Unit) -> Unit, + ) { + val zipFile = File(classloader.getResource(filename)?.file!!) + val zip = Mockito.mock(StoredFileHelper::class.java, Mockito.withSettings().stubOnly()) + Mockito.`when`(zip.stream).then { FileStream(zipFile) } + + val fileLocator = Mockito.mock( + BackupFileLocator::class.java, + Mockito.withSettings().stubOnly() + ) + val db = File.createTempFile("newpipe_", "") + val dbJournal = File.createTempFile("newpipe_", "") + val dbWal = File.createTempFile("newpipe_", "") + val dbShm = File.createTempFile("newpipe_", "") + Mockito.`when`(fileLocator.db).thenReturn(db) + Mockito.`when`(fileLocator.dbJournal).thenReturn(dbJournal) + Mockito.`when`(fileLocator.dbShm).thenReturn(dbShm) + Mockito.`when`(fileLocator.dbWal).thenReturn(dbWal) + + if (containsDb) { + runTest { + Assert.assertTrue(ImportExportManager(fileLocator).extractDb(zip)) + Assert.assertFalse(dbJournal.exists()) + Assert.assertFalse(dbWal.exists()) + Assert.assertFalse(dbShm.exists()) + Assert.assertTrue("database file size is zero", Files.size(db.toPath()) > 0) + } + } else { + runTest { + Assert.assertFalse(ImportExportManager(fileLocator).extractDb(zip)) + Assert.assertTrue(dbJournal.exists()) + Assert.assertTrue(dbWal.exists()) + Assert.assertTrue(dbShm.exists()) + Assert.assertEquals(0, Files.size(db.toPath())) + } + } + + val preferences = Mockito.mock(SharedPreferences::class.java, Mockito.withSettings().stubOnly()) + var editor = Mockito.mock(SharedPreferences.Editor::class.java) + Mockito.`when`(preferences.edit()).thenReturn(editor) + Mockito.`when`(editor.commit()).thenReturn(true) + + when (containsSer) { + Ser.YES -> runTest { + Assert.assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) + ImportExportManager(fileLocator).loadSerializedPrefs(zip, preferences) + + Mockito.verify(editor, Mockito.times(1)).clear() + Mockito.verify(editor, Mockito.times(1)).commit() + Mockito.verify(editor, Mockito.atLeastOnce()) + .putBoolean(Mockito.anyString(), Mockito.anyBoolean()) + Mockito.verify(editor, Mockito.atLeastOnce()) + .putString(Mockito.anyString(), Mockito.anyString()) + Mockito.verify(editor, Mockito.atLeastOnce()) + .putInt(Mockito.anyString(), Mockito.anyInt()) + } + Ser.VULNERABLE -> runTest { + Assert.assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) + Assert.assertThrows(ClassNotFoundException::class.java) { + ImportExportManager(fileLocator).loadSerializedPrefs(zip, preferences) + } + + Mockito.verify(editor, Mockito.never()).clear() + Mockito.verify(editor, Mockito.never()).commit() + } + Ser.NO -> runTest { + Assert.assertFalse(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) + Assert.assertThrows(IOException::class.java) { + ImportExportManager(fileLocator).loadSerializedPrefs(zip, preferences) + } + + Mockito.verify(editor, Mockito.never()).clear() + Mockito.verify(editor, Mockito.never()).commit() + } + } + + // recreate editor mock so verify() behaves correctly + editor = Mockito.mock(SharedPreferences.Editor::class.java) + Mockito.`when`(preferences.edit()).thenReturn(editor) + Mockito.`when`(editor.commit()).thenReturn(true) + + if (containsJson) { + runTest { + Assert.assertTrue(ImportExportManager(fileLocator).exportHasJsonPrefs(zip)) + ImportExportManager(fileLocator).loadJsonPrefs(zip, preferences) + + Mockito.verify(editor, Mockito.times(1)).clear() + Mockito.verify(editor, Mockito.times(1)).commit() + Mockito.verify(editor, Mockito.atLeastOnce()) + .putBoolean(Mockito.anyString(), Mockito.anyBoolean()) + Mockito.verify(editor, Mockito.atLeastOnce()) + .putString(Mockito.anyString(), Mockito.anyString()) + Mockito.verify(editor, Mockito.atLeastOnce()) + .putInt(Mockito.anyString(), Mockito.anyInt()) + } + } else { + runTest { + Assert.assertFalse(ImportExportManager(fileLocator).exportHasJsonPrefs(zip)) + Assert.assertThrows(IOException::class.java) { + ImportExportManager(fileLocator).loadJsonPrefs(zip, preferences) + } + + Mockito.verify(editor, Mockito.never()).clear() + Mockito.verify(editor, Mockito.never()).commit() + } + } + } + + @Test + fun `Importing all possible combinations of zip files`() { + val failedAssertions = mutableListOf() + for (containsDb in listOf(true, false)) { + for (containsSer in Ser.entries) { + for (containsJson in listOf(true, false)) { + val filename = "settings/${if (containsDb) "db" else "nodb"}_${ + containsSer.id}_${if (containsJson) "json" else "nojson"}.zip" + testZipCombination(containsDb, containsSer, containsJson, filename) { test -> + try { + test() + } catch (e: Throwable) { + failedAssertions.add( + FailData( + containsDb, containsSer, containsJson, + filename, e + ) + ) + } + } + } + } + } + + if (failedAssertions.isNotEmpty()) { + for (a in failedAssertions) { + println( + "Assertion failed with containsDb=${a.containsDb}, containsSer=${ + a.containsSer}, containsJson=${a.containsJson}, filename=${a.filename}:" + ) + a.throwable.printStackTrace() + println() + } + Assert.fail("${failedAssertions.size} assertions failed") + } + } +} diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt index 70420801ce..a524f64f39 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -109,7 +109,7 @@ class ImportExportManagerTest { `when`(fileLocator.dbShm).thenReturn(dbShm) `when`(fileLocator.dbWal).thenReturn(dbWal) - val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) + val zip = File(classloader.getResource("settings/db_ser_json.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) val success = ImportExportManager(fileLocator).extractDb(storedFileHelper) @@ -128,7 +128,7 @@ class ImportExportManagerTest { val dbShm = File.createTempFile("newpipe_", "") `when`(fileLocator.db).thenReturn(db) - val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!) + val emptyZip = File(classloader.getResource("settings/nodb_noser_nojson.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) val success = ImportExportManager(fileLocator).extractDb(storedFileHelper) @@ -141,21 +141,21 @@ class ImportExportManagerTest { @Test fun `Contains setting must return true if a settings file exists in the zip`() { - val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) + val zip = File(classloader.getResource("settings/db_ser_json.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(storedFileHelper)) } @Test - fun `Contains setting must return false if a no settings file exists in the zip`() { - val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!) + fun `Contains setting must return false if no settings file exists in the zip`() { + val emptyZip = File(classloader.getResource("settings/nodb_noser_nojson.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) assertFalse(ImportExportManager(fileLocator).exportHasSerializedPrefs(storedFileHelper)) } @Test fun `Preferences must be set from the settings file`() { - val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!) + val zip = File(classloader.getResource("settings/db_ser_json.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(zip)) val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly()) @@ -172,12 +172,10 @@ class ImportExportManagerTest { @Test fun `Importing preferences with a serialization injected class should fail`() { - val emptyZip = File(classloader.getResource("settings/vulnerable_serialization.zip")?.file!!) + val emptyZip = File(classloader.getResource("settings/db_vulnser_json.zip")?.file!!) `when`(storedFileHelper.stream).thenReturn(FileStream(emptyZip)) val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly()) - val editor = Mockito.mock(SharedPreferences.Editor::class.java) - `when`(preferences.edit()).thenReturn(editor) assertThrows(ClassNotFoundException::class.java) { ImportExportManager(fileLocator).loadSerializedPrefs(storedFileHelper, preferences) diff --git a/app/src/test/resources/settings/README.md b/app/src/test/resources/settings/README.md new file mode 100644 index 0000000000..13a3440e1e --- /dev/null +++ b/app/src/test/resources/settings/README.md @@ -0,0 +1,4 @@ +`*.zip` files in this folder are NewPipe database exports, in all possible configurations: +- `db` / `nodb` indicates if there is a `newpipe.db` database included or not +- `ser` / `vulnser` / `noser` indicates if there is a `newpipe.settings` Java-serialized preferences file included, if it is included and contains an injection attack, of if it is not included +- `json` / `nojson` indicates if there is a `preferences.json` JSON preferences file included or not diff --git a/app/src/test/resources/settings/db_noser_json.zip b/app/src/test/resources/settings/db_noser_json.zip new file mode 100644 index 0000000000000000000000000000000000000000..4bbb7523cff0acc4bbd2746598f6673285897cbd GIT binary patch literal 5428 zcmZ{IXIN9+wlyMx1rY>AI)XGok=_$PiZp2g0#X#|JwT`d5oywU?^UVNBPAfc_ujz} zB9Kr61d?3O(f2#gcb-w!8CPZwJk%ct+(?JYSwFd!gg`%>|2JpD8Mwho;2)LHMiMX4v*6jf;Mc~K~8 z*EpyoDhyyJiH{G?w4}p-YI`d8g9IKW&XU_twrrEq*aTwqCdeN&(9$(r8I15~Um;(N z-P36H-VVu|!&6d4#_y4?FBa|gX;vMkpA$Zb1o)0q{?;M$R)Sqx3p8zLu zg=JU&_=#fT+wh()XrZM55TP9>0{O?E^Jvt+=j|! z#s>p~;Y4H>`ykmxP`UIAALQ`SH4Y6Dz0D)}hfjFzCBASYM+rmA0ygB{>IN!4Du`}epnU@@ zRv{w#&}(^m1Yz8~Zvkmot3qWrnmO8!+8U`a?V(p!R@z#g^Si=@p?m|uct<&EH-pAX zzRD7uTUz(w-l9;P4v(gn3VgbyC2Mv4sOIs#*)#6eo*QB%Jr)PA*r1dVLvt${6KV*B zlL@*C6Uht+MJxDz#G_T%1CvzsVT#1fAbeiY+1eYIJoTFO`MwqZ2{@C+<8rzS2Nhhg z-3dZe=n-$?(E|KrVnNTN*1enSmQ?Uw>VvE(1&hf=i|}xG)^NwL&U;^%$2HXt!qwsV z331_jVGrmSjGIAlMa9;;;k0B0lW|e-=@TS!&;4Lw&gG>>m=JV>G%dr>G~L9aJAirr z4mtKxE+^m-@=gr)YQYs8u5@*@ZQA0ZyScg0;0{_Trvd@!%vdsvB-sdtr@_OR>*^9m z)j)BhedjH?`=41SB*Mz~v2O|$68We^0PJ6|Fd1RxrEi&q39(^-8xnw#1v_aki(Fxy zxy-p_C%&RihP+)+t$4<-Q>{(LtQV2j8iL2{z?N)WpDJ(PNl7VrZsafhD?&PeUvZQ1 z_!_BSp!oz)wo{7!L1p*(@Ud6``eb<@>J$QbGHtz6R-MG^TQuyrdO>Wc$bG$Mws?{s zdrwe7zib_~x)2$uF%tZIe(pP?QQvS#du^?kxI*7@+@SaN>s zy4h2!GrD~EPja4gb|=DlWUh@)9E_(b&Tn({zJm5sT9JB~xkLKGgcxL+ySSd`^Dj8L zZHu*OQ|+d%h|$Auu)^3U;SOcEy!PDK+rffu?wn7~NIU&un%Bw0+O%GfhkOjI2JUL^ zJfU8=#k^M-m^Yp`RvNGmGIudgf*kp*8hi)WTE7(FyS?}-XHTMD@coVKdeF@FZT@Hl z;<)L!cMh8)3%?G*U~~3b8v2Af0m)SkZ?*$OsQi_cUaokdj=*qMM{$3hyesO|)2RZ3<;J`_;1nW@6+~{=pRetLt;6-qsD@GE*WprvFxWmN8!K6_ETGC6lP3; zfh)d?t?6+#%_eQ^v?>WE^rVu{;)g#I_hY->-YpI+&SUH+C2n(&q-3U4FhT~FGuvF4 z=8};nTv<@FEw{}Lxbykd<*?LS#mT>^5sG7wCS&stNN+7W(iw3TrO$C)sAse6**HtzgO#xKE2t{0!5lFtM*1PjN8Wr_dnRnxA4}Px-e}~=#*xk1dD|x zdF&-|)d}uB?^VXUw6R&Y!Y=e}wfk7@V$JiLOiWE2?;Gj{Gow-^1FShp&Pq}^E!MlR z?+RO5c$Q)()-w!6K$WKm;p(eBsgj9>2jn42WW@FD{s%*g0hrEZ4*fg3emQ%)5b*4e zs3B~06Y5snYxx8xop+ymsZ^GMejg&g2va z`5=XdoYn^+nO)I(WEb=ogR2oTw6+$!d=~;Jw|VVKnou-Uv}zK#2(V7D$|!z4ArW}~wU z4VFCaqDwz(96puY^U2SE?3{R#6ipU$EeRjgt2ujCM`IemI2ef+!n)@u0JTVdDufDM zl0j89p*cJUNQKz* z1Gp&%!q__>K4cgn3IC1wp5w0f=g$+ZY5%~o{MkJB1W6#U%n<%kRoveBC)3V^o&f)N zWp_IN6TVI${7vUJkk|iA>j341zk3_8;Bo&e4*j2CX~N%n!v^Ao|5qvX+s3f_mn?s# zxhHRN|IhnUl8M|JZPn!L2`So1N>~*?Xyi(k!sl+*T;MP)(5sbHp`-Y#zK!@neRGkm z=TAU3dWU%PHQ?J0xUI%*jSxQj^|sk{kVM?}CfnV)EO? z1*!0h{<-%KfjLoJ=kwi2TWpeDh8T}XFVf{;>Bp4%`?tX`Dq{)3S1D)VXYvkqAk2QU zssBS!D&w$WC5!MN{=Aj%US$r66Fr8R46`t6;rKA69t)olF9%7h0lL|kYV%KT?ZA&E z?xkg$ezEN7*#TUv?HB=}=>$sP?BW5N)LG$Zvu4J?%#DHZx_bbaO3nt7}DL9dlCDMAwiZ3G}dt>|6QAYBVimgn7O0^=~ z1~0;|8>uD^D(+pO|BC91sarNhy7+9YSkVT4*T4RHlFKI4lcBY3c<-=lq$X)Bug5_5 zv3A3wxNX{sn)oU>n9+65zNG-Vu2*1-i8%RyH1TkzRz$HU z4G-JfQ3GmHBQ=L7a8~bXRR%>6YqbpZ&DzWF9p-CdIBmT_;?4JU980EhpXut8Xv9ZE z0gDn-g2}Zh&6bq6gEf)#Rm_0pjHeajlA=Sxog$rd_r# zTNS$U;S{=!MIY<2E3=tTH4~%n%4|i;lPC@`W&To{NJlz}D+n)C&1mo`POrR6^x-vF zjO=(T_GXVQ`E~`5fk~)@yLwzhCjNw?dm|O~&0x|Jct&5@uYI=NqXidH*g68o-&_g=U2x5Sygnfkb8z}k&mj9x8*g> z&7XHQygg+^QxHF{VVv@O)ZIAvZYY~XOwVG*v$l_0O_y<>syS>vo?Mnqb82V~K1Udb z>>3=edDU=Qa(r9pya_7q;OJ@Yl9U0k@Y1_deWp_^uFzvmm-Q{w%4smp*EJh38yd`y zQC`5A`TtMTx3xl8@Z2dER}g$TL|AG>I21e9?Q6_3EXT+jJNTnr7`$QA1bsJfD6#-)gpC(>JcDahIjMqwPe-I>6XIMWPg4V9X7S12nsj zAm_tGGBjD{VZ_Qw07CHSM4E1u$JYB5IsLlC@-dZFxh?4C_cJ`;1Tw!3B`aDPsFL0} zJjT>CCwOFG)|Th3m=05#_nF^l3`67R{4#|CGM>|_!s$}DKCJPh-sqk@l~!?ga;k?M_ANN5vE24%+`%X5&3%UDPN?v+Mx^IoXRg6<0O;DNCR#5gm8zkw63I%sK%;><%}n0 zK}_~Hpz-%Up6dvTn<-?TzVVNDoNIYKKN@Vj8egmmgiZ&PJ1a@=?NcWtskwTP@d7H% zHp-LTS6!8s#_75AC@cJAP4kt%iyBBUTw5jr)mTbo$`^unN!_&=_ZL&F1Zne+-gwec z=;EQQN(&|%S#Q$PhSw{%-!EB|oCZL=4Vy9YM(`|s+Ibwl-q%EY@`_faGv?;CQ?RE_vw<)1vuO}2W&avHwlud1vU3)Zkkon9G zEiW6rF`ie(3A=Tl-c)xY)xMM4wC}vkVNsLkFTUQOuRjY$Lho@_w37Q;^|q-FvS*c$ z+OscP9Z1wtvQk`UEnKDu=Y94|rANGPX(6yJD36HZQv+zt$bZqe}5*bm+`m!Rm0~m9nB&6N-)1-&D1=mO`7=#^YyT;kCBHQk_WdUO{b3 zUQVYQPCAOG^mmQqnka(B`a*}q&?~Lk0w~$ITRuZcJmjd4Plcy}<+-#+apSID%b+NB zl|8=aGVvq2Q+Bu3{fCaIWn?=dzOf`jze?SCtMzPE{&-LzeJwdPP{P}9CzMWz!Et21Au-veufS~x{usFlY!pkt3uV3Q>p7FxuOOGQ#Eu9#&qFZBKrOW^ z#56?zcc=1Cb^XuypT6aP_(cjC}%kh?j9e(Zwh!+bvwWX3OEHOAa=nA^td?DXX{LTfGu$IonO9AX! zMhzgHbj=|$ifM2A&`>q@n~}fvptsDm^eHxXROdR&MVD}r**M#k7H*yv#^E@>-UYVV zWW0Vy;CfW?G}*bgp~l+W_XKG!C6yp<+`voMX(J9?9>E3@5;?+jw<3$b~0+1Z@mVC3tH#U4MS&_DrF6S0eYYm)uy8;X;O_+QjFc?2jACIX+yx{PSpuUX>P41JOjHXe>XGSCcDt9s!J*F=nLDEnU}r|q{ixZ5}M+b(thjOfA5Ck)BE!k(WTJ+Mvro@4HyCHeyqyF~7toFY|^(iycN zQcn+6UTyXhq@WzGo3~=Gc(v&TTWTGU?yuDI*|*Eb8@5}T*ox+U3WVt} zFuWRYJlKWstzNc=v@F#T3R<6exFcOHb@=hb#f63LP7Udo7&*AqP$bPm{eq80Ypqmm z1=T0JZwEgsn_?_s5TJvZ=Bg8sNJR&#%x zr^Z@27sB2Jz8rULvFD0)mfi?^`f3ENxcqSGsaLUH!}83hh0t*fzy6JC9tuc@3Uk}X z-jTf~nZAq0?2n_({2G1x#LlWj;2wrUy0Sjl%%D^ z;$yDx@LF|vW3;ro&c||}Dn+FvU?%o)IK1yRdfMxeevB-9g(W-piA|2ReP5Ws76%*o zSiLap8jd50e4><*O08T(c2C$-`Z_wwEI5ew3u~|dMDE;^c;=NzOg1J)ps6Vxsq3AB z{B+b=wDsnXaiy5*E%MVc&2%X)d4R}Ua*vvv_T2ltvb5wFz-cAGDB4Xmz`jV%csg%7 z(^IM(!CSoP-Kd!R{lIXQFaJUO$(G15H?X6y*z4MJ98hS5hGnSg_c+xsX{}Yhy^|~< z;kM(zy9kx5S8Dr?M)nlKi2L(faL*{n%?am?s``vSg3Cud7AZ_mw8UBar(h^)@}=9F z=2gptMRa_;{%EAe%yc`S<)@L1fw$6)< zhdd>iZ)_4dBA)c4_#zkMlpcw?l#o^AWlf`7-15ymwxAsJxB_HC9M&xH<~xSXwbR+A zt#Q>v-csm&Pg(NKSWp$UxVI?zT;%O;-|IIISrDN;2CQr`-G&d?qW%i62W}c{+`NE3 zD}XNxFaA%=c7jQOVZ>6UHJWj`_~<2yW$Q5NPemgBKig%W}VhfeXR2 zT{$T(9oF3<+&XF2S6P(*OdWZ{G)VsV{5&iiR?IiZ!qg2?<`m%6w8Vv13%F2hicYbl zF`{){t6hsC7_Y9sI9`4s2EH&)w&lb%=2t|7uL4*iRKn-R(8fn3Z?{kh?Kj7YMmU#X z6JyZ?gRx?B@xe1P(I1aI|4L+5InI;1M6`cBF?oG8+aa`HAr~%SzUHY{^Epv!R6%K1 zKO?ZxnRfV?)Ly6P6I>R85G*vPD7oh?!kuH9SvSbFHbq~fQj1v9PJH-cYb$pJt+L-k zqd^zvyKs}GSMiO3#Dg;}op9W<`G*5>ykFc?A_uSFOY8%Uzfx>EH4&;JD6m3wh95pd ztm!shV?dkq$i-#ZiH!cV)*IxsNwzI%v$nDJxctN)(qU93%)-eY zF?D}vcNdd8a?2ccM(t~FVomj7?SL4}ad9ok*IO&6y*s_&iS)p=cI(*#{u_KtpyR zynq;5Bt?ZF4F@hSl>SJr|5(7LvNd~4O_u>(jNXF)EAMO86+Ct$f>0N*E-Q$SM6mMS zN1mL+){z5cd{9X)WMki-xf}%(o0Hpa)_vh$pGax}Q+t>Lu+IIvVT4(>16e}!oEo97 zjp)^5koJpY>oC7)*uSNP=Fbz0CNH{PbK9Wb5izo&tHf)1P+usVZ{F{zDGjC4)uJeZ zgq}7IxS$Vksrz4li7_YV832@q-uU_q*^H}m<|DheT8H_~Zjwk~Dv>_h#=%4A!JYlZ z*TjtFQL*WnnhNmBut!h zC`JS!B*Z()9Q(tgz0lX}=g%`N8U0Nb{29DxA4jJPPSSr-)tuY-t+bQT`@o;*-3`RQ z*O%$^UvlmOdHj3X1gNI}ift)KqyASv;=h+w>A&g?7f1l@Kc&Dwe52D_U)!P)UOD6SZ8?NZN_uR@$(+t^AsXQ|D?Q zLi;6cZ+nVwX}Riip?BanA#kh74W~~YtD4?ToiqbM&6^IV4s9m^F zrcCO0C!6sH55?Vgxa3*oh9KWPQ+)!XqNWu*UG6CxT=hVS6HW0HF%f&zdkTd`?!n-+ z11P*OLF{Oz59dnGaLZMYkRQN#ZO{GvRnOshWDl2>(rr`dVeH{OkefGYE7K-a=nj`v z%*cKF*a+$3h4z3dQ2KcPlRVz39%s4KnEUlP6Nm;-o z5ynNeY)oR_!Q|fFO~g;YXgjRIsjVvn-#}O=2;+!ictUy2rzYg!DAr65pFB=$wtdXf z>(pVct>XSx-eF-LinbQ0gHq*3=UAFOYTk}U;m5Y>6D;p>YIX77zTYS>Zt)N~=xq^5ZroLA7wf)jUmyE*G)l$cAjKW|s}2!7||XhX(WaleJh4WpK8DeK%d z4XJe)FyBkOduJ(p*{sx+6u18hXB9vswLL9pbe>Uolxt~IL;bQ)S(6}=F*4%rb^*|k z6>l&yPIaTyfBq_%OE}RyxLmgJ!Y-X{CVH+kLTb#6*cA1b(8v1S`!O)=|Pefv+*CED?G&u zXP&E(u&|C+^3_Xe$)oMF_pM|R-dmt7?+R8Q1WF>6o)#RP1&MklK3A%F^Tu?%%4a=Y z7OIq_yq&1{;AZXG&78c~KHv&020D}B7E&eh=HZ>;GVw{VSt+hs$a@0X+{6^=OaA&^m(4u&b-k#|U;D>Z6W8?(hBdHOQJWTfO92hn z9Yx=x5of$%A4K~*J}RpL1SPM&c&5nQWIR4lnVQQ|aJx*D8P-nuDR{h@nb%{k1<9ja7GEVU8`rT{v8M{zST^0@|r zGd)b&831}Day;9l&Tq|OLEXG5y?RV%QGE@*+I~m_>_cYO;it+MhU!!|cJ@dO9cg~~ zq^0>8C;lC1$CkiT{SkQTbV#0TSgr>5Lktg8%sZzhuO`VRf@`%=A2I36>=FV4Li#aE zkQ_*<5732-l#M>&cl>Zy-N0zrZfE)n_2s-VSu?MgTlEoAvLZ=UBSCh1W|Szyo8)R^ zyxqODBXu-Agw>;(2%XHH^q^;z~9ITIiHFCYF)P=fpPrn%dffT8C=)(QAqIIUs@4w7x@ z@$RN%-_mXDI)abN*6SN88?SbpMV7m}QTZ(pT2T?*75rf*UUxnW<-v~ytT8zAe7Gj> z_or7zj4|h%vh8y}(v1IJ$VwL1C?(8Lmyzir!~gERf9l2W@!w9|Q1|3NJt`vuBZK0P KvMmWt&;TKgyAy&t1cJL0oZ#*b4I#MGxVr|2V8KIhclR_Bq;c==n|*fn zJLjJ7y;r~HSF6SxbFCkv)|jQLfQUqZfQE*KpcEPK0pULaB*Xt zJ;^>0^-L&{nrV1p*v6i|2|ve_kr#iKT1t+9&>LGyt-)R{S#soI_W9NMovLu;Pc<)@ zVOI{?8X&g@Aq}!y(WX{Qtr?16$qAV=P;=93$d}fUtwHLyDZ?PX{ zM@bK=!H2#FI0}I)8ASr=qtjvzvwV92A4!WIQ2)G?u;>#L5O2~y(I_Shc_4Z`^XYE$ z)!)yy;FU=+FnbtH7!2j_%%_DNQwMT&MK=1YYq42qaV6rPUE&kFU|!A;uC+i{cn_2Z z_)k8Mb~0e#<82$EdIC=(GdMJ-quBNrdVA+!XP)nf#@@0WppC{wk8Ft}FOqs-}K+wB3#A3|?TUs4B$Q>rlF5g%-~rK<>xmZw!5 zQ`Z+QK-P_!)Kd(+(wR2bzZ5p3FeOHo7|Av%7$~+gL|JVGiQ>ZaOVBtTkm&w~YRalT|K|I+KdG9C*H? zBWH8_vVoa!@s6=`0G+RF!0cR;x(zpaY-vMjMqyv_dInH!D3}Gr?(}>WO}p9s+Av*l z96Nb87*i#(T%r&HbF28|q$1DuN4>K#?aAuK^_b?XF-csm_;?M5slx zpjhzy_2J=(`}xX}v$9eISKAIsMwYHorlDDXAo&>%=EIYCULfruPW%I6(K9Htd~>pE z-t4izy}eim2O^zUy)W9AwWb?`v=a)>07sC2{hB$J|W`QHU0PFSA2nx z>-DoX$1ot6^5&fr%MP8a7_g3D8A#&0a|0EOV6m`s^8j_~`Kd~0=3 zH&ZRhe|Pegq}m%Io~CXS`^=RYKHxlKX%kb_FEFM}hm}!lM@ zCqDrzl(Wm7p6w2$FQ8im4Kt!kRTeYsb5NbwvC0t}{>lsTlj5L)>4K^9z%y49XOk4* zrSGQBH_t{ZWe!%XRn5E;fhJCK^xP)bg##@1ILYUrd6237?!?Nk3r|lInnnWRq^}%; zn{+7i66b!m?aO{yJS+KFc02H1P4VGzN z3B$q?=zU_Rf|;I3u>cfjneR_w-x7xLEwYNU<~+qV{8l?NLDubtT{J{;NruEIf^QSY zqn-~x{P_5?G^n(IbQtA%m%Sh^Ij*GMVNeyh^`lWfDoWC`6$P6roBZHs#0O4K+fEFg z`0EcQxQC57Wx+waqA0;aL2FYh>i0LUwgF5sQBVA8ht_@?>U6OxQw7?L!x$XlKkgkr z#~}$lWwR1O%tGcD=yx;B0|xlA+Q_x{92Kj_;#ep61TK|Qe9En0cOP!P)$+Uf75n#5 z7cV%hxX4^j=Op7}@nAQ?zwzG73Tx)Lkx$6)I z(gl-?&oRTKQJ**U1e}kp2Hy0o(`n-z`{$h;13eeN$BsS3wVu8JeUL~}@aL5Avs1?_ z-k}SK#EyL~2CPGCHmJei8cFYx7Zt4=?K{8Ux(h2W)v+||Ta+HTyaZ>BztJwm5Si<$ifS-h5z}u$aLvk007V0DM*FCdj;q@bZ`fpKpR*Vk7Wyc3%HAfny;1aA zT>ll`$AX5~&DEVMa0YmHpQqrpWNq#TTZsKE{=W52-w%kV;MEU6=3UG9t>B4oVHWV{ z+6$>IBO)b!y(F_I2YC%%_W*WJ}c0Z_O|*dxVhcPmaCg`3z`*T3;h zQ+iK*-E%0@gXJ z1pj*d1nPoWwfSK}|7k~gqWi`TTwDbO&YxoLg14(ELr}PA3IiE&MM#HH_6n)*%QS-XZi^JKUrl#GE6IoWvi1H zu;eHuX_NoFg&|c4mcQHZ2)&_bL#)9IpM)X$wZKNTO?dX+TXyXB_sO`xuFe-px3(+C z@?3p>QhBy$Fk22e?9yqCyb$t{n^*r*id&GNmsYYPUYcBC`FfRi<%(-M^aCwmm&mGY zkI3w4?%3Ntqx-riysLlE>RvzAv$fy2!(wHBfew~x!v`^MnZ0bIO65rWWoa}cE6-?m zfyaZ!Jp%mYnCRvc7PiFaXe}YALhLZh2+=%wWS}vdt$%z(;g%g`Uw~6H?Jn|8!rs>P<}B4HfQlDSKVn?kEHaqAVB?#2 zrG4_ufNnO)Vz(7{VubX7neT+Ry`bd?U@^YVB;=#5C$j)yMy^qX#lXOk=;PLro>*HZ z99L{{^_*Jhu6VLtC2M2>J*})ZzH`+n8>4AJokzT}1Jd9LbOUXG<4MBozLauT zyo)=TFNIy8`6agOoVxpzcG!b0W)XBBLr6ua%qSco%2dICjs~+@GcD+VsJbDs7VJsta$?s} z)V8fvWOEaJ{pnEO8*Y#=EIO+(y&w4uPN-9{s+n{Q!v)+G zLCx8)YwZ4=^wWBsS-p1@RkuDYlL7|0_b=?}9OFI;R7XW=&Q!V`By*(+#0#Fs@yoK+ z?6GBLhq!r`?Ss>nQ*8Y!X`-=nZF70Z@bzvKwOzOBp48nZ_ZSr52A8w9C=0XhFZbzx(tMTc=EG}d4}MV!nuzGj zq)t;vJ^%7ew7PO%Z8g`?UCt*VSV!hZ(T(+-#729bn8Ln1$4#sb_Jep5G@jY7MjXOl zNIYL?xmdL82P$bfrl{)ex)!kt)VE6$D2Ej3Gm3#k+ubJ)mm_$xR4A6apUb3(!b2x# zGBj#E_RKfLwZA4;P04MF@3rlIyMu{d1D6llP)jyOYK4z3u5KFIlRR>6w$_&|$u82` z&&Umw#@iB?{Ij_Nv)&WQg8^v_u9?-@)$vBbc)N{CeGBeL*8ZhpzyYuTFcVnhCT9IG z$rX<1@g#pKuA(+-da*POby-(`kjyS15>}pMDT^0=AIo*VJOSY#iMKIQKkwSQV7*@& z*;j;WP$6e5IKWd)*JC=mTK6L|pd8^}JbymKW{yF;L^|I9XMA+ZCo+EBd{OaZU5AD~ z>=2Rne_i7w6brzzHv-vF594EnJ=X}p=-!PSo>zfCZK$Jl8;xEG!8 zAM}wzF?-wtxMD<}(jU0r9hLv|Nh>Q0@AT`vh*4M%oOK}E7u`d!0`$`Ic)WE^iBQLl zDn7^hEi+zM{yXV-6OVza3aDs4d8$3?f3*YL=Lo9*PbZH1yWk%u?q=ccZsTa>=Ak2E zPb&2~XQ_U;c9-8Xn?F4yf)c}>*F~}rjVXjyn{^xMqYh>I*vZ5?*2ens*w{wAnkwU` z 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 O_;{e7sjUBuYX1c!>QLnX literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/db_vulnser_json.zip b/app/src/test/resources/settings/db_vulnser_json.zip new file mode 100644 index 0000000000000000000000000000000000000000..64444acdda9f674ff0e0828f59316da73dba438c GIT binary patch literal 6752 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 literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/db_vulnser_nojson.zip b/app/src/test/resources/settings/db_vulnser_nojson.zip new file mode 100644 index 0000000000000000000000000000000000000000..56d58a22e2d2568311cd041e4d6619b8224a60b1 GIT binary patch literal 5364 zcmZ`-XCPc%x7H#eI*BOZ)d?bkh&CdK2%-~R^d7wr5z(Ue9(|DLy^{!{jWT){VGL$Q z9n6@SoA*m{zd!e`AN%aR&U*H<&-wAJwYHWTF$n_!0Rb6-Moh@7fC8;P9qM088X^Mn zUk^81KX*rW+xs?F@Rc_%EKuO}Lzxs#f#JDfM;G2^`g|+?NAwU*WmSe-{#44^4KAu_ zN+W0p(dp5JmTb&VZ9kP^Pf_l)c?xGp*Dfi8Ll{bbn&M6iBU20EXsl1)8pU!FT%$8! zH?n8}ZQs{WZD!z2j9N)fLGbG|u;5kOkVCh}p#g{l>d*k4hkcbjx}*}y;NvPxh)&MB02Em@QcMD_M! zCFGXB13#9vE977TVdE#)cr-}#w@#jN2n#w(Rr13o@OM{&D9*VMB5gsZlz-@g>13_h ztyoqW-Mtrs7Q-=ws8kf2FCOp~9w)c3W{Itp$NNe3j(K-`d4f(#5?YrSP31wV1O#t~ zY|l@CtXsFOfh`-gh>xunuFeyV#;SMW$n~|go-RcZk8SaGp^*rjs{*~3VQY<0O*z&p zd*B$pES9Vzpy{u=J=@ilzrK0WaPQXq1%LP84axFB>mxnRcAD6+g*A<7HK@|rG*h*i zbS{vp+xK=X_j>Ogvn=&-s7)VGUMlVjk~ zcbHgAIz6|QmAmO;7_XN=lH<2$&tNdP_tDaV`%8^zvGyON*||pMIcC-ap=^iL6zD63 zf>3T4bt0O$gb<6ZSfA*bwZ;r|c9t4aBcByiLjnD{D@Ji7KO(lXx1-sbn$jlJJd-EB zUv?E9e)wlvD!S?rZCa|7CPXU%;HpIT%89G2)aR9^CPf2oNCCi0PO|>ih2lC3c?%hC zLS_9dMF*aZk8}6VwYFIEpT}KmiI{ZqwdLe{UqeZqnOUxA93s0HD;p}Jyv2HYjWjsS za$5dLzYO!8nt{vlQ^`=|+3I1tTO?3;)_(tUUHU&kW#g{v7$PHOezL)N5JUuh>!Fgt z=S{@=Qe2z{I6`r8p@Y@<`*`|LY|L!DXpzt@$0V5fG$}jTA^sHo=J@DTdU2A>;=bJl zQ}Oms3V|FhH~eK>p@U9xuRv{b@Ycj_CGGo+63xAqE;&onlE7JhP_w{?y=@A|E_?H~ z#w}e5V~+aX(j?&+m(SRuzQQERh=)Diyuufx{UN=YWE9aoTF)sW--Xr5A876i(=Xj* zgO`RCO%+X6gdTcYx?83LPXgBsJA51MUy2G*F6$M*rJ5hw-1yk+Ik!tGlAuJCJezFg zvISn+JNEUpAVU8jK*O4?#Z<|}1JL4$3>ckjYi2B7FNaPfH5z;PMS)% zq~=)VSQt*oy!+;A^9`Rb{VLj$2G*Eg9v-#@APtuZTbW$axkPz7g$Ss8xO%J&a|qJa z;fdFrEHdC9CKrwRcIoymfkfsiPv{oX`u*%2?^d==$e?6yJDb6dn|k$Ff)H3z>O>j$TIXFzwDfeg}&!Buyp$GG%EgerHMMtQUvN zTwm>m&6P028v~H%BFktH2!&r^O;p2=&y`3-GV+SMhn zk~F=UYb4=Wa}E`+BOc6>PAfg4h<%!fyWI zYa)R%$yob=aorFNeAC%gau3eFJ%|GPHysrKpcV~cVns#nZ{se)H>>`6Pb$V$9Li5K z63x}m!NCG1iTQ=-DDc+*{pSfbw7;U+e{WuR2E*go=kR~1swwyXX4<)^Gx^`%C;R>X z4d2A$|D0n7H&Q`a9Kwen@i;0m`IFjy3OJ8=zd)C7(=s1-MAU@5VqXxFth z(68cwJ3X3b-Prs*R^S=%lf$=F_nzH%*DW6Xk()9Bs+G4aX-(j@AnmO=y$?iSti|QE|vcEKm_iY=n=(1gc&hg+f93_!9t&o#; zjttS1Jv;O1=P&r3k7*^y#7BU8ZYf>?k&z3MZVpeSHJM!@{75}GhL6h{Qjfuqia#G- zuyF}1i08Xp9Dq5Z)17i91tf-G?nf(MXVh(8NA%K~NIlfcyokAY>f+>yI?ON+;dn%A z5p-x-i?rPc(|lr`<@?u8zW1bVWq&lU zv>hDW2Vge#jpf^O@HF!C%SW6t7o`)Onz6T6q+PRRwb|D*lfnLdL z+lh#29%Xag=uaT=$|ni%SrG1+EGVV*2f{l5)a9~m3n4JVyl0i;u(9Q9#)r*ar8Zl> zBJH#5G*FRou9UD@Tg_ZDW2?=?N`O~Ln_a6b!IALE!@79mr!>mlcOO1$l;Agf9z$mQ zB5hO|PRRTT@jbC=)g0y?_+!nEF|5OY>=lI1A z8nWUv$EUG&R*kBo63~rCmgY|FRU4PZhD2V+0MEyrw{=|0X9^$a8jxtD#Ky~)rDaA? zXj36u3{#eBqj8t*o*ZTjrCmIJYMjeHclFjYR^~>CR;M3pRfo@`>VDom;H=!K)_od7 zrQ2Het{J^HpLbs~ErCvMC*diH^4Q(Hy=T)oFt^7_;!Cx28bZpmYgTE2f`-d+UtUX^ z!Z|Z2s|5_rqNKdllUwp|XH)|}vJmx#5aTE8Rp$YM6H=xhFK@c=xFx)ns{ZgnZ~C*> zZkkx8RHF1z!sF+{H9NvNdGEb^%c0wui|J0mpSeD~cr;qdKgYK$L|Y@+^@@S~C!I!s zpWLHN=+|o~x1vCGFCL*ADrT{>^Qfu(-P3OKRqXpZ9*1{lSD$BjH8e*Rp(c?BhNm0; z4ZOBI^-KNao}e#0gPq@`~;jztgD>O>$lVPF#kiI%7Ton}uC4oSl8anKH8;EoOkr)KvLOaNzD>0hNs0ea%pL z6PN@M=({I}K8;@dBh@VmFkOSfQe6 z74XQ~W&0*5864f8!XVP!SY5Qcdz{&M$Y!cB-k!1$oF^8VtH}6bn<oxVo}6CS1(Q!G_^;qju)n|9;-TNrMx0W*_RW7<%zP2@nT1dV5BHZ zqNBOaQP0M)(B;AiR2{3!aV>k!6`x_X8rRj+293_fibj3%{fUmx8AJNXb>9b02N+g> zv-eP+G=qdzu@UGWjBEoQ3TsLl#5oc0{8FgT5TcsjY@WT<<~wI|TMj zU$`ZF9=NAR>(G2CJ)-<2Gk&MRKeA}Sm{6_%blyb2I`4!EYlCraV(vZ?0s^laznbq~ zsp4O&7^hYvWFVqo)PEuut|N@tsVc*P2y0Qt z)V5#&419k=i6bSPO1oH7pF<+|rq#Y)-xDr-2D@jk z?F?SqVFx$ovD9DJrBItvep7gU(ikQOiOVdJigArAdE#94kLBNrpHb<&_fnCJDI659 zmmRt0GOoH1$EPkRrfRCLVv^L|g=pjGc%eqsGI!kc;-HhZFSuDXtw~8$UcvuHGM&ac z@M;)70gSlC6m@%)WAO1*M8CMhZJuWV!}S#Fz*EsA!~L(krn>$rBu#O~yHNfqHHPFM z=F-9*SbA>;n?D8?b)&NWh-Ax9k|Ma}FQlInSxF?X*2QI7{+dw>P@^%jw zDGX87Z~t_#7WOXeLW=N6%3dp=zJzcRFw_|DF)8}7Pc#D|K~ie zj&n}_=ccq9i40YSIveWnw!$5Q(hq`#Q-&MXk-g?n_p$Ltz_Y+p( zC{mCyKKe zxW*#Y&0EPYHJHhzE4e^PuQ${Lj;PFj%2o@%bnh8^3hA2e?>GlPx@Q?b_=S7-^L4V2 zi#{0vi`1_X@ZsuXXF``ep^O-huXgCB|)>V zs@xTzkQvDVAV?T(e?2OS9T3+q=8nsd%euR_pKT12s%w>2=lUe=cMX0gGixEf@@#Mp zWx>js{C?!?ljfG8+kz0H9Or|SAFEDL#%?1?X;hW3dqQ@j#!-XNC~&ENul{*s;Jr^~ z%n1>kw#y;X81PwUBJxH`1t_(55G$Zb8sc(fGr!YXlTY@AE@(b+=WP?$h@oNk8$axZ zVN#(CVU77^Co#3~5Pc2b*O#IK)GdBON;6WEo9MkhFEhNnmKq@u1Hu1))A+kO{yY9v vKO3umaq`3r1pl8-{za6A1^z0l|Kk3trk2{Z|J>&h{lfeH3U&DR00jR7OC^Z( literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/newpipe.zip b/app/src/test/resources/settings/newpipe.zip deleted file mode 100644 index 1ce8431feb3d4f2b14e770ac7f8a3b7efac07436..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8317 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 diff --git a/app/src/test/resources/settings/nodb_noser_json.zip b/app/src/test/resources/settings/nodb_noser_json.zip new file mode 100644 index 0000000000000000000000000000000000000000..7881f939b1cbb33e98318906a029fd6356fccc53 GIT binary patch literal 1410 zcmV-|1%3KZO9KQH0000808NR0SZL&>KmY{*0J{tT01yBG0B~|;W@U0^ZewM0E^2dc zZhcnUZWK8XedjYq;teGOL9pZvv8;BbWm#?V29f1%SI=1Ewi{nEnV|gpp0fKoG&~Td zeW@x}ojRw!Kih;_8WPco@GVJCLbq~lAISH!?-H!D`sMqhQx?5WTJ=DN#Ec z$;RnwZAg0YEm0!gY_xB4d`kyLhA5N~@zjyH*NPnctSsVa=epFvWlL>EUIhI}qI03x zi$1YAjh6+0TVr-;$Ow zKwhFpx;9ri8wK3}a;`mD9PrzhW-=F|^IUfs4paWQAV%6<;+WjE45kBSCIFJHeV=+`2LkCIDAF8<)_dJA6HK==AUn$?w)didYo}KgKQ>} z;2iLxBE>2(W~8OKcj_$1gR11C&W9Byt-TC;X2-rt%Ojnq25TeTX7M?*5Fic+Af(+6 zi7PPbNya$H+}eS@gP{Npgtl_vZhULuytoHU_s*z3n^)URZF98*q6`9kq-7nV^x$#` zsz&fc~C?R8C6xyDpb|mmlbLO*=mlxZK!mWfTF?$Dsif%sq`ToC~ zkGJB}=bKw`^Y6zGf4%>kgZEjb_{qsFF zL}v2ZR7XX4xb7k#eqKtwvkp-pXm#X)qy@gDs8Wn5$1+i6D<*CUZc;_}8WLJTxDJIk z$cM}~@7}$hG44Q-3wq3zDGcs{E;Q9Yte_Y3kO$;)2XUv9UB5Q}nUu%a0ZWg0_! zF%5nG^y%Y2w>OjiGo50{#`r>3tqeP5`pz?N*?so(KTt~n0u%rg0000808NR0SZL&> zKmY{*0J{tT01yBG0000000000000000001Ra%E;^a%FB~WpgfSb8l`?O9ci100001 Q0096#0000k1poj504D>Qy#N3J literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/empty.zip b/app/src/test/resources/settings/nodb_noser_nojson.zip similarity index 100% rename from app/src/test/resources/settings/empty.zip rename to app/src/test/resources/settings/nodb_noser_nojson.zip diff --git a/app/src/test/resources/settings/nodb_ser_json.zip b/app/src/test/resources/settings/nodb_ser_json.zip new file mode 100644 index 0000000000000000000000000000000000000000..aa8316c6ca38c2c5198f21f7c2fd5ee18a9eb604 GIT binary patch literal 3177 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 Q066()!~Wcn)BlJ51y#WA{{R30 literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/nodb_ser_nojson.zip b/app/src/test/resources/settings/nodb_ser_nojson.zip new file mode 100644 index 0000000000000000000000000000000000000000..437d30d8bb8b73e9257a4b58a4f3556f246568cc GIT binary patch literal 1789 zcmVaA|O5E^}pcbZKs9 zb9GleY#c=xo!GI-uj4p&oH&?-h#(4dHgQCv2q}alRxIHJVOJoS%G+*rC)vb0I}m9Ru8Y8Fv%&VemXWrlqp+J^d7aU} zJDq*_eIGx!9DC6QB9h!`ZtrHoNUmbh#P5VB+Np*Lgi|`P{aquGG;42Y9Z8Y=a>m^M z?W=$PnqYh0VN-1pWwM(x$Cj{T7E7)ZF0xQ*-q1m2c~hw5F8he>b!6`9kHox&A5oKA+=&Zxl2kOw+(GI7Os&jvV?;de8cNt$XX z6Z^wgV$VgwIvz&c^-{^(DwO&ugQrrRW+`@yWn7nrTeT~Bsx1Yx6m4>kV>5KPV#mQI zOa83dsHRE;qAEZkn!0+$L^WHEyB4B5b|4gHr+hh?Mxs|2!49`OD#f;uOoD3k#Yn}9 z(5YjJhr$V-SpehMLOwGXLJAX%pOOh7$_J61aFR#LI)9TH$BzGhNuI%CcBw&I8Hw2d zhfEv~TEZZ8(m1w<1BWaPDWpi#D%hcHh%9mxOh*}|!m;U#=U3BLR~ukZ`eU2@AkgG9pp*C?tir6}KVszeTcKnox>KT2t zG&)thjQ`F7g&j;SjtN=UnH&2NMvSv1r-!Bpf1j7GW5+_>NvM=9k3|9A^nM;5 zp&@>w3%VYAx^*s(eJjrKoLpOl4xX&~Ecg}S+`F=phcl3C1Hp%TmX~d0QDg&DR@K{1 z_aHb~eaIoH0(h?NCBSoijZYP!Pkv_Dk%76dqb%H`CqIAl`)hyy32R?3t=+KM!Kzys z+2W@UA1ojI;*ZZKNTRQxrGRV|W|0Wk9{Gfokiq$DH?O|Ebz_@v-rm~gTW{UCwsG}M zB)~G`UE3KQfqQLKbzB*kFAS<9=*%re94i4jLIBwXXE+_^^1+PQFk;e+iFXa<1qf(-34`o}ypX;#3om_dOv6TFw zhG_$$*_3AP@z>w}^YLewfa*qxDh1wVZ1ORLxYYGhj4MJs=kuSNgtm&vQe6LNUM%R| zBIODU=7-1V&>4a(6et!{9fol}RtXx2UEjCKE;h#Dkk*8^3Jq3?vd=;RI1uC{t zTF!DVjiItQFQ_QHflPf_RN$P&W8V3Ad1}hC$n$&|e5X^e9KA--$WeZyj_6r3u%aFu zn<~1egHg4y8rJl@6&=#_MHQzISgi5?`OYc#IXm9xQlm+Bjji?lqvCbt(~1!tcpW^h zkU~7kIEzU0@VJVfiVwCk?R0GytYq`0`Q`r8(A2@ALCjw}vW^^1gLX(?382TmPwPG@ zmVHl~5%4*b)J57pIce1$XxwyV&$3m|_uQYq#ur3YGHgn6R<^)fdBrbN17An$jh(#j z4!H^aiSg8Va`BR-I7%cNW5i&t?o}d@u)sN4u!J=l2b_pX~kzP)h*< z6aW+e000O8O^JS3j?uFWdj$g-o literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/nodb_vulnser_json.zip b/app/src/test/resources/settings/nodb_vulnser_json.zip new file mode 100644 index 0000000000000000000000000000000000000000..84f97aac0cfd73aad44a0bfdb47e0525e5cd55d6 GIT binary patch literal 2734 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 E01*6)NdN!< literal 0 HcmV?d00001 diff --git a/app/src/test/resources/settings/vulnerable_serialization.zip b/app/src/test/resources/settings/vulnerable_serialization.zip deleted file mode 100644 index d57a5f8d0150cd11dec35d7c29ed2bcb9e65a774..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 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 D$x)Jd From 7da1d3001037ec91baf7a99980deeadf7d0f1c9e Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 30 Mar 2024 18:47:20 +0100 Subject: [PATCH 08/23] Expose all import/export errors to the user --- .../settings/export/ImportExportManager.kt | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 5558a1b374..85370449a3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -27,38 +27,34 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { file.create() ZipOutputStream(SharpOutputStream(file.stream).buffered()).use { outZip -> - try { - // add the database - ZipHelper.addFileToZip( - outZip, - BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path, - ) - - // add the legacy vulnerable serialized preferences (will be removed in the future) - ZipHelper.addFileToZip( - outZip, - BackupFileLocator.FILE_NAME_SERIALIZED_PREFS - ) { byteOutput -> - ObjectOutputStream(byteOutput).use { output -> - output.writeObject(preferences.all) - output.flush() - } + // add the database + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_DB, + fileLocator.db.path, + ) + + // add the legacy vulnerable serialized preferences (will be removed in the future) + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_SERIALIZED_PREFS + ) { byteOutput -> + ObjectOutputStream(byteOutput).use { output -> + output.writeObject(preferences.all) + output.flush() } + } - // add the JSON preferences - ZipHelper.addFileToZip( - outZip, - BackupFileLocator.FILE_NAME_JSON_PREFS - ) { byteOutput -> - JsonWriter - .indent("") - .on(byteOutput) - .`object`(preferences.all) - .done() - } - } catch (e: Exception) { - Log.e(TAG, "Unable to export serialized settings", e) + // add the JSON preferences + ZipHelper.addFileToZip( + outZip, + BackupFileLocator.FILE_NAME_JSON_PREFS + ) { byteOutput -> + JsonWriter + .indent("") + .on(byteOutput) + .`object`(preferences.all) + .done() } } } @@ -133,7 +129,7 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { } if (!editor.commit()) { - Log.e(TAG, "Unable to loadSerializedPrefs") + throw IOException("Unable to commit loadSerializedPrefs") } } }.let { fileExists -> @@ -168,7 +164,7 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { } if (!editor.commit()) { - Log.e(TAG, "Unable to loadJsonPrefs") + throw IOException("Unable to commit loadJsonPrefs") } }.let { fileExists -> if (!fileExists) { From 2756ef6d2fd998cf30b689db3a60fdc32ccf3c3b Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 30 Mar 2024 18:53:45 +0100 Subject: [PATCH 09/23] Show notification when failing to import settings --- .../newpipe/settings/BackupRestoreSettingsFragment.java | 8 +++++++- .../schabi/newpipe/settings/export/ImportExportManager.kt | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java index f4080acd34..20af8c1509 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -217,7 +217,7 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri manager.loadSerializedPrefs(file, prefs); } } catch (IOException | ClassNotFoundException | JsonParserException e) { - showErrorSnackbar(e, "Importing preferences"); + createErrorNotification(e, "Importing preferences"); return; } cleanImport(context, prefs); @@ -290,4 +290,10 @@ private void saveLastImportExportDataUri(final Uri importExportDataUri) { private void showErrorSnackbar(final Throwable e, final String request) { ErrorUtil.showSnackbar(this, new ErrorInfo(e, UserAction.DATABASE_IMPORT_EXPORT, request)); } + private void createErrorNotification(final Throwable e, final String request) { + ErrorUtil.createNotification( + requireContext(), + new ErrorInfo(e, UserAction.DATABASE_IMPORT_EXPORT, request) + ); + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 85370449a3..843806b802 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -1,7 +1,6 @@ package org.schabi.newpipe.settings.export import android.content.SharedPreferences -import android.util.Log import com.grack.nanojson.JsonArray import com.grack.nanojson.JsonParser import com.grack.nanojson.JsonParserException From 7abf0f4886fefc922d0801d079716f48553ca31a Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 1 Apr 2024 14:23:04 +0200 Subject: [PATCH 10/23] Update NewPipeExtractor to YT comments fix PR https://github.com/TeamNewPipe/NewPipeExtractor/pull/1163 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7a3b28661f..72cab42de8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.Stypox:NewPipeExtractor:aaf3231fc75d7b4177549fec4aa7e672bfe84015' + implementation 'com.github.AudricV:NewPipeExtractor:2eca6d3ae62dde0d0d3933ba01031ec07a1a940e' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ From f704721b59200faf35ed358a9dbea7965fc66bb9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 1 Apr 2024 14:23:48 +0200 Subject: [PATCH 11/23] Release v0.27.0 (997) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 72cab42de8..2c4ea383cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 21 targetSdk 33 - versionCode 996 - versionName "0.26.1" + versionCode 997 + versionName "0.27.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From f0db2aa43cbfa4d0523e8571042807427c2be2f6 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Thu, 4 Apr 2024 11:38:57 +0200 Subject: [PATCH 12/23] Improve documentation --- .../settings/BackupRestoreSettingsFragment.java | 4 +++- .../newpipe/settings/export/ImportExportManager.kt | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java index 20af8c1509..97df1549b2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -187,6 +187,7 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri throw new IOException("Could not create databases dir"); } + // replace the current database if (!manager.extractDb(file)) { Toast.makeText(requireContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG) @@ -265,7 +266,7 @@ private void cleanImport(@NonNull final Context context, } /** - * Save import path and restart system. + * Save import path and restart app. * * @param importDataUri The import path to save */ @@ -290,6 +291,7 @@ private void saveLastImportExportDataUri(final Uri importExportDataUri) { private void showErrorSnackbar(final Throwable e, final String request) { ErrorUtil.showSnackbar(this, new ErrorInfo(e, UserAction.DATABASE_IMPORT_EXPORT, request)); } + private void createErrorNotification(final Throwable e, final String request) { ErrorUtil.createNotification( requireContext(), diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 843806b802..9a0d842a47 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -67,11 +67,17 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir() } + /** + * Extracts the database from the given file to the app's database directory. + * The current app's database will be overwritten. + * @param file the .zip file to extract the database from + * @return true if the database was successfully extracted, false otherwise + */ fun extractDb(file: StoredFileHelper): Boolean { val success = ZipHelper.extractFileFromZip( - file, - BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path, + file, + BackupFileLocator.FILE_NAME_DB, + fileLocator.db.path, ) if (success) { From 13baaa31cd94831688df5c15838c7a3703e7306e Mon Sep 17 00:00:00 2001 From: bg1722 Date: Sat, 6 Apr 2024 07:58:05 +0200 Subject: [PATCH 13/23] add an intuitive prefix for the duration of lists on UI, and avoid using the new prefix for single videos --- .../fragments/list/playlist/PlaylistFragment.java | 2 +- .../newpipe/local/playlist/LocalPlaylistFragment.java | 3 ++- .../main/java/org/schabi/newpipe/util/Localization.java | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 998ea06241..9afb063441 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -506,7 +506,7 @@ private void setStreamCountAndOverallDuration(final List list, Localization.concatenateStrings( Localization.localizeStreamCount(activity, streamCount), Localization.getDurationString(playlistOverallDurationSeconds, - isDurationComplete)) + isDurationComplete, true)) ); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index e2d0f59866..d5ae431fad 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -837,7 +837,8 @@ private void setStreamCountAndOverallDuration(final ArrayList itemsLi headerBinding.playlistStreamCount.setText( Localization.concatenateStrings( Localization.localizeStreamCount(activity, streamCount), - Localization.getDurationString(playlistOverallDurationSeconds)) + Localization.getDurationString(playlistOverallDurationSeconds, + true, true)) ); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 5d73d21f0a..bc113e8f86 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -245,7 +245,7 @@ public static String likeCount(@NonNull final Context context, final int likeCou * @return a formatted duration String or {@code 0:00} if the duration is zero. */ public static String getDurationString(final long duration) { - return getDurationString(duration, true); + return getDurationString(duration, true, false); } /** @@ -254,9 +254,11 @@ public static String getDurationString(final long duration) { * duration string. * @param duration the duration in seconds * @param isDurationComplete whether the given duration is complete or whether info is missing + * @param showDurationPrefix whether the duration-prefix shall be shown * @return a formatted duration String or {@code 0:00} if the duration is zero. */ - public static String getDurationString(final long duration, final boolean isDurationComplete) { + public static String getDurationString(final long duration, final boolean isDurationComplete, + final boolean showDurationPrefix) { final String output; final long days = duration / (24 * 60 * 60L); /* greater than a day */ @@ -274,8 +276,9 @@ public static String getDurationString(final long duration, final boolean isDura } else { output = String.format(Locale.US, "%d:%02d", minutes, seconds); } + final String durationPrefix = showDurationPrefix ? "⏱ " : ""; final String durationPostfix = isDurationComplete ? "" : "+"; - return output + durationPostfix; + return durationPrefix + output + durationPostfix; } /** From 0ba73b11c162ddcfdd70bfc3f0b74536b3ac3bd8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 7 Apr 2024 23:44:22 +0200 Subject: [PATCH 14/23] Update NewPipeExtractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2c4ea383cb..a4fde4ccce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.AudricV:NewPipeExtractor:2eca6d3ae62dde0d0d3933ba01031ec07a1a940e' + implementation 'com.github.AudricV:NewPipeExtractor:f4dcab16004e566f1969bc6f627facf198573fee' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ From 3738e309495ae7de404268b20a85b9ebe87cb4d7 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 9 Apr 2024 20:18:21 +0200 Subject: [PATCH 15/23] Fix NPE when avatarUrl is empty --- .../newpipe/database/subscription/SubscriptionEntity.java | 4 +++- .../org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt | 2 +- .../schabi/newpipe/local/subscription/SubscriptionManager.kt | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java index a61a22a844..df5a3067af 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.database.subscription; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; @@ -95,11 +96,12 @@ public void setName(final String name) { this.name = name; } + @Nullable public String getAvatarUrl() { return avatarUrl; } - public void setAvatarUrl(final String avatarUrl) { + public void setAvatarUrl(@Nullable final String avatarUrl) { this.avatarUrl = avatarUrl; } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt index 84cd8ed59a..b44eec3533 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt @@ -18,7 +18,7 @@ data class FeedUpdateInfo( @NotificationMode val notificationMode: Int, val name: String, - val avatarUrl: String, + val avatarUrl: String?, val url: String, val serviceId: Int, // description and subscriberCount are null if the constructor info is from the fast feed method diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index 488d8b3d28..474add4f41 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -100,7 +100,9 @@ class SubscriptionManager(context: Context) { val subscriptionEntity = subscriptionTable.getSubscription(info.uid) subscriptionEntity.name = info.name - subscriptionEntity.avatarUrl = info.avatarUrl + + // some services do not provide an avatar URL + info.avatarUrl?.let { subscriptionEntity.avatarUrl = it } // these two fields are null if the feed info was fetched using the fast feed method info.description?.let { subscriptionEntity.description = it } From 67b41b970da9e6f039b5d877220ef2c557c75b3f Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 10 Apr 2024 10:52:47 +0200 Subject: [PATCH 16/23] Fix not saving comment replies state on config change --- .../fragments/list/comments/CommentRepliesFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java index a816b149f1..304eaf55a1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java @@ -30,6 +30,7 @@ import java.util.Queue; import java.util.function.Supplier; +import icepick.State; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -38,7 +39,8 @@ public final class CommentRepliesFragment public static final String TAG = CommentRepliesFragment.class.getSimpleName(); - private CommentsInfoItem commentsInfoItem; // the comment to show replies of + @State + CommentsInfoItem commentsInfoItem; // the comment to show replies of private final CompositeDisposable disposables = new CompositeDisposable(); From 00770fc63484b9946a53a1a800fe9fe9add3573b Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 20 Apr 2024 13:11:08 +0200 Subject: [PATCH 17/23] Update NewPipeExtractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a4fde4ccce..1f3d1f7591 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.AudricV:NewPipeExtractor:f4dcab16004e566f1969bc6f627facf198573fee' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:fbe9e6223aceac8d6f6b352afaed4cb61aed1c79' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ From c3c39a7b24af5c3b062999103e90be3e56547850 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 12:16:06 +0200 Subject: [PATCH 18/23] Fix free storage space check for all APIs See https://stackoverflow.com/q/31171838 See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html --- .../newpipe/download/DownloadDialog.java | 25 +++--- .../streams/io/StoredDirectoryHelper.java | 79 +++++++++---------- .../java/us/shandian/giga/util/Utility.java | 14 ---- 3 files changed, 51 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index bbdb462922..db2066b278 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -859,20 +859,19 @@ private void prepareSelectedDownload() { return; } - // Check for free memory space (for api 24 and up) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - final long freeSpace = mainStorage.getFreeMemory(); - if (freeSpace <= size) { - Toast.makeText(context, getString(R. - string.error_insufficient_storage), Toast.LENGTH_LONG).show(); - // move the user to storage setting tab - final Intent storageSettingsIntent = new Intent(Settings. - ACTION_INTERNAL_STORAGE_SETTINGS); - if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) { - startActivity(storageSettingsIntent); - } - return; + // Check for free storage space + final long freeSpace = mainStorage.getFreeStorageSpace(); + if (freeSpace <= size) { + Toast.makeText(context, getString(R. + string.error_insufficient_storage), Toast.LENGTH_LONG).show(); + // move the user to storage setting tab + final Intent storageSettingsIntent = new Intent(Settings. + ACTION_INTERNAL_STORAGE_SETTINGS); + if (storageSettingsIntent.resolveActivity(context.getPackageManager()) + != null) { + startActivity(storageSettingsIntent); } + return; } // check for existing file with the same name diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index 0fe2e04082..8dd8192939 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -1,24 +1,29 @@ package org.schabi.newpipe.streams.io; +import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; +import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Build; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; +import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStatVfs; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.documentfile.provider.DocumentFile; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; +import java.io.FileDescriptor; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -27,16 +32,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; -import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; -import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; - -import us.shandian.giga.util.Utility; - public class StoredDirectoryHelper { private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -45,6 +43,7 @@ public class StoredDirectoryHelper { private Path ioTree; private DocumentFile docTree; + // will be `null` for non-SAF files, i.e. files that use `ioTree` private Context context; private final String tag; @@ -176,41 +175,41 @@ public boolean isDirect() { } /** - * Get free memory of the storage partition (root of the directory). - * @return amount of free memory in the volume of current directory (bytes) + * Get free memory of the storage partition this file belongs to (root of the directory). + * See StackOverflow and + * + * {@code statvfs()} and {@code fstatvfs()} docs + * + * @return amount of free memory in the volume of current directory (bytes), or {@link + * Long#MAX_VALUE} if an error occurred */ - @RequiresApi(api = Build.VERSION_CODES.N) // Necessary for `getStorageVolume()` - public long getFreeMemory() { - final Uri uri = getUri(); - final StorageManager storageManager = (StorageManager) context. - getSystemService(Context.STORAGE_SERVICE); - final List volumes = storageManager.getStorageVolumes(); - - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - if (split.length > 0) { - final String volumeId = split[0]; - - for (final StorageVolume volume : volumes) { - // if the volume is an internal system volume - if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) { - return Utility.getSystemFreeMemory(); - } + public long getFreeStorageSpace() { + try { + final StructStatVfs stat; - // if the volume is a removable volume (normally an SD card) - if (volume.isRemovable() && !volume.isPrimary()) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - try { - final String sdCardUUID = volume.getUuid(); - return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID)); - } catch (final Exception e) { - // do nothing - } + if (ioTree != null) { + // non-SAF file, use statvfs with the path directly (also, `context` would be null + // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway) + stat = Os.statvfs(ioTree.toString()); + + } else { + // SAF file, we can't get a path directly, so obtain a file descriptor first + // and then use fstatvfs with the file descriptor + try (ParcelFileDescriptor parcelFileDescriptor = + context.getContentResolver().openFileDescriptor(getUri(), "r")) { + if (parcelFileDescriptor == null) { + return Long.MAX_VALUE; } + final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + stat = Os.fstatvfs(fileDescriptor); } } + + // this is the same formula used inside the FsStat class + return stat.f_bavail * stat.f_frsize; + } catch (final IOException | ErrnoException e) { + return Long.MAX_VALUE; } - return Long.MAX_VALUE; } /** diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index c75269757a..86a08c57f8 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -40,20 +40,6 @@ public enum FileType { UNKNOWN } - /** - * Get amount of free system's memory. - * @return free memory (bytes) - */ - public static long getSystemFreeMemory() { - try { - final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath()); - return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); - } catch (final Exception e) { - // do nothing - } - return -1; - } - public static String formatBytes(long bytes) { Locale locale = Locale.getDefault(); if (bytes < 1024) { From 23a087c498c8c897059195df005ffae1865addf0 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 23 Apr 2024 18:00:52 +0200 Subject: [PATCH 19/23] Translated using Weblate (Romanian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Croatian) Currently translated at 99.5% (735 of 738 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (French (Louisiana)) Currently translated at 0.2% (2 of 738 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (738 of 738 strings) Added translation using Weblate (French (Louisiana)) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (French) Currently translated at 99.7% (736 of 738 strings) Added translation using Weblate (Arabic (Tunisian)) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (ryu (generated) (ryu)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Kannada) Currently translated at 5.8% (43 of 738 strings) Translated using Weblate (Kannada) Currently translated at 5.1% (4 of 78 strings) Translated using Weblate (Italian) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Russian) Currently translated at 99.8% (737 of 738 strings) Translated using Weblate (German) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (German) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (ryu (generated) (ryu)) Currently translated at 99.0% (731 of 738 strings) Translated using Weblate (Portuguese) Currently translated at 99.7% (736 of 738 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Russian) Currently translated at 99.7% (736 of 738 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Vietnamese) Currently translated at 50.0% (39 of 78 strings) Translated using Weblate (Sardinian) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Greek) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Japanese) Currently translated at 99.4% (734 of 738 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Italian) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Hindi) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Punjabi) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Punjabi) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Hindi) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Serbian) Currently translated at 100.0% (738 of 738 strings) Translated using Weblate (Slovak) Currently translated at 21.7% (17 of 78 strings) Translated using Weblate (Czech) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 62.8% (49 of 78 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (78 of 78 strings) Translated using Weblate (Polish) Currently translated at 61.5% (48 of 78 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 23.0% (18 of 78 strings) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Alex25820 Co-authored-by: AudricV Co-authored-by: Fjuro Co-authored-by: Flavian <3zorro.1@gmail.com> Co-authored-by: Hosted Weblate Co-authored-by: Ihor Hordiichuk Co-authored-by: Jonatan Nyberg Co-authored-by: Jose Delvani Co-authored-by: Linerly Co-authored-by: MS-PC Co-authored-by: Milan Co-authored-by: Milo Ivir Co-authored-by: NEXI Co-authored-by: Philip Goto Co-authored-by: Random Co-authored-by: Ray Co-authored-by: Rex_sa Co-authored-by: Sergio Marques Co-authored-by: ShareASmile Co-authored-by: Tấn Lực Trương Co-authored-by: Vasilis K Co-authored-by: VfBFan Co-authored-by: abhijithkjg Co-authored-by: gallegonovato Co-authored-by: kaajjo Co-authored-by: kuragehime Co-authored-by: ngocanhtve Co-authored-by: ssantos Co-authored-by: yosrinajar Co-authored-by: zeineb-b Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/kn/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-aeb/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 5 + app/src/main/res/values-el/strings.xml | 11 + app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 11 + app/src/main/res/values-frc/strings.xml | 20 + app/src/main/res/values-hi/strings.xml | 11 + app/src/main/res/values-hr/strings.xml | 16 +- app/src/main/res/values-it/strings.xml | 4 + app/src/main/res/values-ja/strings.xml | 10 + app/src/main/res/values-kn/strings.xml | 2 + app/src/main/res/values-nl/strings.xml | 6 +- app/src/main/res/values-pa/strings.xml | 11 + app/src/main/res/values-pt-rBR/strings.xml | 423 +++++++++--------- app/src/main/res/values-pt-rPT/strings.xml | 11 + app/src/main/res/values-pt/strings.xml | 11 + app/src/main/res/values-ro/strings.xml | 15 +- app/src/main/res/values-ru/strings.xml | 7 + app/src/main/res/values-ryu/strings.xml | 11 + app/src/main/res/values-sc/strings.xml | 20 +- app/src/main/res/values-sr/strings.xml | 4 + app/src/main/res/values-sv/strings.xml | 10 + app/src/main/res/values-uk/strings.xml | 10 + app/src/main/res/values-vi/strings.xml | 46 +- app/src/main/res/values-zh-rHK/strings.xml | 10 + .../metadata/android/ar/changelogs/997.txt | 17 + .../metadata/android/cs/changelogs/997.txt | 17 + .../metadata/android/de/changelogs/990.txt | 1 + .../metadata/android/de/changelogs/997.txt | 17 + .../metadata/android/es/changelogs/810.txt | 8 +- .../metadata/android/es/changelogs/961.txt | 4 +- .../metadata/android/es/changelogs/977.txt | 2 +- .../metadata/android/es/changelogs/997.txt | 17 + .../metadata/android/hi/changelogs/997.txt | 17 + .../metadata/android/id/changelogs/997.txt | 15 + .../metadata/android/it/changelogs/997.txt | 17 + .../metadata/android/kn-IN/changelogs/830.txt | 1 + .../metadata/android/kn-IN/changelogs/850.txt | 1 + .../android/kn-IN/full_description.txt | 1 + .../android/kn-IN/short_description.txt | 1 + .../metadata/android/pa/changelogs/997.txt | 17 + .../metadata/android/pl/changelogs/997.txt | 17 + .../metadata/android/pt-PT/changelogs/996.txt | 2 + .../metadata/android/pt-PT/changelogs/997.txt | 16 + .../metadata/android/pt/changelogs/997.txt | 16 + .../metadata/android/sk/changelogs/997.txt | 17 + .../metadata/android/sv/changelogs/997.txt | 17 + .../metadata/android/uk/changelogs/997.txt | 17 + .../metadata/android/vi/changelogs/70.txt | 25 ++ .../metadata/android/vi/changelogs/985.txt | 1 + .../metadata/android/vi/changelogs/997.txt | 17 + .../android/zh-Hant/changelogs/997.txt | 17 + .../android/zh_Hant_HK/changelogs/997.txt | 17 + 53 files changed, 775 insertions(+), 246 deletions(-) create mode 100644 app/src/main/res/values-aeb/strings.xml create mode 100644 app/src/main/res/values-frc/strings.xml create mode 100644 fastlane/metadata/android/ar/changelogs/997.txt create mode 100644 fastlane/metadata/android/cs/changelogs/997.txt create mode 100644 fastlane/metadata/android/de/changelogs/997.txt create mode 100644 fastlane/metadata/android/es/changelogs/997.txt create mode 100644 fastlane/metadata/android/hi/changelogs/997.txt create mode 100644 fastlane/metadata/android/id/changelogs/997.txt create mode 100644 fastlane/metadata/android/it/changelogs/997.txt create mode 100644 fastlane/metadata/android/kn-IN/changelogs/830.txt create mode 100644 fastlane/metadata/android/kn-IN/changelogs/850.txt create mode 100644 fastlane/metadata/android/kn-IN/full_description.txt create mode 100644 fastlane/metadata/android/kn-IN/short_description.txt create mode 100644 fastlane/metadata/android/pa/changelogs/997.txt create mode 100644 fastlane/metadata/android/pl/changelogs/997.txt create mode 100644 fastlane/metadata/android/pt-PT/changelogs/996.txt create mode 100644 fastlane/metadata/android/pt-PT/changelogs/997.txt create mode 100644 fastlane/metadata/android/pt/changelogs/997.txt create mode 100644 fastlane/metadata/android/sk/changelogs/997.txt create mode 100644 fastlane/metadata/android/sv/changelogs/997.txt create mode 100644 fastlane/metadata/android/uk/changelogs/997.txt create mode 100644 fastlane/metadata/android/vi/changelogs/70.txt create mode 100644 fastlane/metadata/android/vi/changelogs/985.txt create mode 100644 fastlane/metadata/android/vi/changelogs/997.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/997.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/997.txt diff --git a/app/src/main/res/values-aeb/strings.xml b/app/src/main/res/values-aeb/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/app/src/main/res/values-aeb/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3a308fb25a..fcc38bf0bc 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -819,4 +819,9 @@ Ja Nein Sichern und Wiederherstellen + NewPipe kann von Zeit zu Zeit automatisch nach neuen Versionen suchen und dich benachrichtigen, sobald sie verfügbar sind. +\nMöchtest du dies aktivieren? + Wenn du alle Einstellungen zurücksetzt, werden alle deine bevorzugten Einstellungen verworfen und die App wird neu gestartet. +\n +\nMöchtest du wirklich fortfahren? \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index cb5958dc55..6e9525459a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -813,4 +813,15 @@ Εμφάνιση περισσοτέρων Εμφάνιση λιγότερων Επεξεργαστείτε κάθε ενέργεια ειδοποίησης παρακάτω πατώντας σε αυτήν. Οι τρεις πρώτες ενέργειες (αναπαραγωγή/παύση, προηγούμενηο και επόμενο) ορίζονται από το σύστημα και δεν μπορούν να τροποποιηθούν. + Δεν υπάρχει αρκετός ελεύθερος χώρος στη συσκευή + Όχι + Ναι + Αντίγραφο ασφαλείας και επαναφορά + Το NewPipe μπορεί να ελέγχει αυτόματα για νέες εκδόσεις και να σας ειδοποιεί μόλις είναι διαθέσιμες. +\nΘέλετε να το ενεργοποιήσετε; + Επαναφορά ρυθμίσεων + Επαναφορά όλων των ρυθμίσεων στις αρχικές τιμές τους + Η επαναφορά όλων των ρυθμίσεων θα απορρίψει όλες τις τροποποιημένες ρυθμίσεις σας και θα επανεκκινήσει την εφαρμογή. +\n +\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε; \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ce0b326039..c7b780a1f0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -782,7 +782,7 @@ Suscriptores Qué pestañas se muestran en las páginas de los canales Pestañas del canal - Cortos + Shorts Cargando los metadatos… Recuperar las fichas del canal Acerca de diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9994abb4ca..66418e7cfe 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -829,4 +829,15 @@ Modifiez chaque action de notification ci-dessous en appuyant dessus. Les trois premières actions (lire/pause, précédent, suivant) sont définies par le système et ne peuvent pas être personnalisées. Afficher plus Afficher moins + Réinitialiser tous les paramètres à leurs valeurs par défaut + Non + La réinitialisation de tous les paramètres va supprimer toutes vos préférences de paramètres et redémarrer l\'application. +\n +\nÊtes-vous sûr de vouloir poursuivre ? + Sauvegarde et restauration + Oui + NewPipe peut automatiquement vérifier la disponibilité de nouvelles versions de temps en temps et vous notifier lorsqu\'elles sont disponibles. +\nVoulez-vous activer cette vérification ? + Réinitialiser les paramètres + Pas assez d\'espace disponible sur l\'appareil \ No newline at end of file diff --git a/app/src/main/res/values-frc/strings.xml b/app/src/main/res/values-frc/strings.xml new file mode 100644 index 0000000000..5b919711cc --- /dev/null +++ b/app/src/main/res/values-frc/strings.xml @@ -0,0 +1,20 @@ + + + aucun streamer trouvé . Installez VLC? + non + ouvrir dans le browser + ouvrir dans le popup mode + ouvrir avec + partagez + installer le fichier stream + chercher + parameters + installer + Installer + marquer comme vu + "publié le %1$s" + aucun joueur de stream n\'est trouvé ( vous pouvez installez VLC pour jouer) + Annuler + OK + Oui + \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 756b1878a5..51455fafbb 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -813,4 +813,15 @@ और दिखाओ नीचे दी गई प्रत्येक अधिसूचना कार्रवाई पर टैप करके उसे संपादित करें। पहली तीन क्रियाएँ (चलाएँ/रोकें, पिछली और अगली) सिस्टम द्वारा निर्धारित की जाती हैं और इन्हें अनुकूलित नहीं किया जा सकता है। कम दिखाएं + न्यूपाइप समय-समय पर स्वचालित रूप से नए संस्करणों की जांच कर सकती है और उपलब्ध होने पर आपको सूचित कर सकती है। +\nक्या आप इसे सक्षम करना चाहते हैं? + सेटिंग्स रीसेट करें + सभी सेटिंग्स को उनके डिफ़ॉल्ट मानों पर रीसेट करें + सभी सेटिंग्स को रीसेट करने से आपकी सभी पसंदीदा सेटिंग्स खारिज हो जाएंगी और ऐप पुनः प्रारंभ हो जाएगा। +\n +\nक्या आप सुनिश्चित रूप से आगे बढ़ना चाहते हैं? + हाँ + नहीं + डिवाइस पर पर्याप्त खाली स्थान नहीं है + बैकअप और रिस्टोर \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index f6c2120f7f..419f4619ed 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -574,7 +574,7 @@ \nYouTube je primjer usluge koja nudi ovaj brzi način sa svojim RSS feedom. \n \nDakle, izbor se svodi na ono što više voliš: brzinu ili precizne informacije. - Izračunavanje šifriranja + Izračunavanje šifre Obavijest šifriranja videa Obavijesti o napretku šifriranja videa Nedavni @@ -733,7 +733,7 @@ Najava Razvrstaj Koristi razervnu funkciju ExoPlayer dekodera - Ignoriranje hardverskih medijskih gumba + Ignoriraj događaje hardverskih medijskih gumba Korisno, na primjer, ako koristite slušalice s pokvarenim fizičkim gumbima Odaberite zvučni zapis s opisima za slabovidne osobe ako je dostupan Preferiraj originalni zvuk @@ -821,4 +821,16 @@ %s odgovora Tuneliranje medija je standardno deaktivirano na tvom uređaju jer je poznato da model tvog uređaja to ne podržava. + Da + Ne + Aktiviraj ovu opciju ako imaš problema s inicijaliziranjem dekodera, što vraća dekodere nižeg prioriteta ako inicijaliziranje primarnih dekodera ne uspije. To može rezultirati lošijim performansama reprodukcije u odnosu na korištenje primarnih dekodera + Nedovoljno memorije na uređaju + Spremanje sigurnosne kopije i obnavljanje + NewPipe može automatski tražiti nove verzije i obavijestiti te. +\nŽeliš li aktivirati tu mogućnost? + Obnovi postavke + Obnovi sve postavke na zadane vrijednosti + Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti. +\n +\nStvarno želiš nastaviti? \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35a722a570..2a5ac16d39 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -834,4 +834,8 @@ \n \nSei sicuro di voler procedere? Azzera tutte le impostazioni ai loro valori predefiniti + + No + NewPipe può cercare automaticamente nuove versioni di tanto in tanto e avvisarti quando sono disponibili. +\nVuoi attivarlo? \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6cbc3c0847..74924125a2 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -800,4 +800,14 @@ 表示を少なくする 以下の通知アクションをタップして編集します。 最初の3つのアクション (再生/一時停止、前へ、次へ)はシステムによって設定されており、カスタマイズすることはできません。 デバイスの空き容量が不足しています + バックアップと復元 + はい + いいえ + NewPipe は定期的に新しいバージョンを自動的にチェックし、更新可能になると通知します。 +\n有効にしますか? + 設定をリセット + 全ての設定をデフォルト状態にリセットします + 全ての設定をリセットすると、優先設定が全て破棄され、アプリが再起動します。 +\n +\n続行しますか? \ No newline at end of file diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index ec598114d5..2ba73e1781 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -40,4 +40,6 @@ ಟ್ಯಾಬ್ ಆಯ್ಕೆಮಾಡಿ ಹಿನ್ನೆಲೆ ಅನ್‌ಸಬ್‌ಸ್ಕ್ರೈಬ್ ಮಾಡಿ + ಹೌದು + ಇಲ್ಲ \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 2a52066992..b4629a03f6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -205,8 +205,8 @@ Pop-upspeler Bezig met ophalen van informatie… Bezig met laden van gevraagde inhoud - Databank importeren - Databank exporteren + Data­base importeren + Data­base exporteren Dit overschrijft je huidige geschiedenis, abonnementen, afspeellijsten en instellingen Exporteer geschiedenis, abonnementen, afspeellijsten en instellingen Geëxporteerd @@ -637,7 +637,7 @@ Reacties zijn uitgeschakeld Zoeksuggesties op afstand Lokale zoeksuggesties - Markeer als bekeken + Markeren als bekeken %1$s download verwijderd %1$s downloads verwijderd diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 196b5b7851..814cdb8851 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -813,4 +813,15 @@ ਹੋਰ ਵਿਖਾਓ ਇਸ \'ਤੇ ਟੈਪ ਕਰਕੇ ਹੇਠਾਂ ਹਰੇਕ ਸੂਚਨਾ ਕਾਰਵਾਈ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰੋ। ਪਹਿਲੀਆਂ ਤਿੰਨ ਕਾਰਵਾਈਆਂ (ਪਲੇ/ਪੌਜ਼, ਪਿਛਲਾ ਅਤੇ ਅਗਲਾ) ਸਿਸਟਮ ਦੁਆਰਾ ਸੈੱਟ ਕੀਤੀਆਂ ਗਈਆਂ ਹਨ ਅਤੇ ਉਹਨਾਂ ਨੂੰ ਅਨੁਕੂਲਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਘੱਟ ਦਿਖਾਓ + ਹਾਂ + ਨਿਊਪਾਈਪ ਸਮੇਂ-ਸਮੇਂ \'ਤੇ ਨਵੇਂ ਸੰਸਕਰਣਾਂ ਦੀ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਜਾਂਚ ਕਰ ਸਕਦੀ ਹੈ ਅਤੇ ਇੱਕ ਵਾਰ ਉਪਲਬਧ ਹੋਣ \'ਤੇ ਤੁਹਾਨੂੰ ਸੂਚਿਤ ਕਰ ਸਕਦੀ ਹੈ। +\nਕੀ ਤੁਸੀਂ ਇਸਨੂੰ ਇਨੇਬਲ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? + ਬੈਕਅੱਪ ਅਤੇ ਰੀਸਟੋਰ + ਸੈਟਿੰਗਾਂ ਨੂੰ ਰੀਸੈਟ ਕਰੋ + ਸਾਰੀਆਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਉਹਨਾਂ ਦੇ ਡਿਫ਼ਾਲਟ ਮੁੱਲਾਂ \'ਤੇ ਰੀਸੈਟ ਕਰੋ + ਸਾਰੀਆਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨ ਨਾਲ ਤੁਹਾਡੀਆਂ ਸਾਰੀਆਂ ਤਰਜੀਹੀ ਸੈਟਿੰਗਾਂ ਰੱਦ ਹੋ ਜਾਣਗੀਆਂ ਅਤੇ ਐਪ ਰੀਸਟਾਰਟ ਹੋ ਜਾਵੇਗਾ। +\n +\nਕੀ ਤੁਸੀਂ ਯਕੀਨੀ ਤੌਰ \'ਤੇ ਅੱਗੇ ਵਧਣਾ ਚਾਹੁੰਦੇ ਹੋ? + ਡਿਵਾਈਸ \'ਤੇ ਲੋੜੀਂਦੀ ਖਾਲੀ ਥਾਂ ਨਹੀਂ ਹੈ + ਨਹੀਂ \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1d241de510..5a203e0f25 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -17,8 +17,8 @@ Excluir Não gostei Curtidas - Baixar - Baixar + Download + Download Detalhes: Relatar por e-mail Relatório de erro @@ -31,7 +31,7 @@ Erro Copiado para a área de transferência Nome do arquivo - Toque para detalhes + Toque para mais detalhes O NewPipe está baixando Por favor, espere… Erro de rede @@ -57,30 +57,30 @@ Downloads Downloads Você quis dizer \"%1$s\"\? - Aplicativo/IU parou + O aplicativo travou Reproduzindo em segundo plano O menu de download não pôde ser configurado Reproduzir vídeo, duração: - Miniatura do avatar do uploader + Foto de perfil do autor Escolha a pasta de download para arquivos de áudio Áudios baixados são salvos aqui Pasta para áudios baixados Escolha a pasta de download para arquivos de vídeo Vídeos baixados são salvos aqui Pasta para vídeos baixados - Instalar o aplicativo Kore\? + Instalar Kore? Toque na lupa para começar. - Threads + Processos Por favor, defina uma pasta de download depois nas configurações - Sem reprodutor de transmissão. Instalar VLC? + Player de vídeo não encontrado. Instalar VLC? O site não pôde ser analisado Áudio - Reproduzir com Kodi + Reproduzir no Kodi Pesquisar - Mostra uma opção para ver vídeo pelo media center do Kodi - Usar reprodutor de áudio externo - Usar reprodutor de vídeo externo - Mostrar opção \"Reproduzir com Kodi\" + Mostrar opção para reproduzir o vídeo no Kodi + Usar player de áudio externo + Usar player de vídeo externo + Mostrar opção \"Reproduzir no Kodi\" O que aconteceu:\\nPedido:\\nIdioma do conteúdo:\\nPaís do conteúdo:\\nIdioma do aplicativo:\\nServiço:\\nHora GMT:\\nPacote:\\nVersão:\\nVersão do SO: Abrir no modo Popup Resolução padrão do Popup @@ -88,11 +88,11 @@ Apenas alguns dispositivos suportam vídeos em 2K/4K Formato de vídeo padrão Reproduzindo em modo Popup - Tudo + Todos Desativado - k - M - Bi + mil + mi + bi Essa permissão é necessária \npara abrir em modo Popup Limpar @@ -100,7 +100,7 @@ Segundo plano Lembrar propriedades do Popup Lembrar último tamanho e posição do Popup - Remove o som em algumas resoluções + Remove áudio em algumas resoluções Sugestões de pesquisa Escolha as sugestões a serem exibidas enquanto estiver buscando Melhor resolução @@ -115,7 +115,7 @@ Colaborar © %1$s %2$s protegido pela licença %3$s Sobre o NewPipe - Baixar + Download Caracteres permitidos em nome de arquivos Os caracteres inválidos são substituídos por este valor Caractere de substituição @@ -124,20 +124,20 @@ Inscrever-se Inscrito Inscrição cancelada - Não foi possível alterar inscrição - Não foi possível atualizar inscrição + Não foi possível alterar a inscrição + Não foi possível atualizar a inscrição Inscrições Novidades Continuar reprodução Continua vídeo após interrupções (ex: ligações) Histórico de pesquisa Armazena histórico de pesquisa localmente - Histórico de vídeo - Armazena histórico de vídeos assistidos + Histórico de exibição + Mantenha o controle dos vídeos assistidos Histórico Histórico Notificação do NewPipe - Notificações para o reprodutor do NewPipe + Notificações para o player NewPipe Comportamento Histórico e cache Desfazer @@ -160,29 +160,29 @@ %s vídeos %s vídeos - Reprodutor + Player Nada aqui além de grilos Deseja excluir este item do histórico de busca\? Conteúdo da página inicial Página em branco - Página do Quiosque - Página de canais + Página do Kiosk + Página do canal Selecione um canal Nenhuma inscrição ainda - Selecione uma banca + Selecione um Kiosk Em Alta Top 50 Novos e tendências Mostrar dica \"Segure para pôr na fila\" Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\" do vídeo Reproduzir tudo - Não é possível reproduzir esta transmissão - Ocorreu um erro irrecuperável no reprodutor - Se recuperando do erro do reprodutor + Não é possível reproduzir este vídeo + Ocorreu um erro irrecuperável na reprodução + Se recuperando de um erro durante a reprodução Remover Detalhes Configurações de áudio - Segure para pôr na fila + Toque longo para pôr na fila [Desconhecido] Reproduzir em segundo plano Reproduzir em um Popup @@ -191,52 +191,52 @@ Retribuir Site oficial Visite o site do NewPipe para mais informações e novidades. - Reprodutor de transmissão não encontrado (pode instalar o VLC para assistir). + Player de vídeo não encontrado (você pode instalar o VLC para reproduzi-lo). País padrão do conteúdo Sempre - Uma vez + Apenas uma vez Mudar para segundo plano Mudar para Popup Mudar para principal - Reprodutores externos não suportam estes tipos de URL + Players externos não suportam estes tipos de URL Nenhuma transmissão de vídeo encontrada Nenhuma transmissão de áudio encontrada - Reprodutor de vídeo - Reprodutor em segundo plano - Reprodutor Popup + Player de vídeo + Reprodução em segundo plano + Reprodução em Popup Obtendo informação… Carregando conteúdo solicitado Importar base de dados Exportar base de dados - Substitui seu histórico atual, inscrições, listas de reprodução e (opcionalmente) configurações - Exporta histórico, inscrições, listas de reprodução e configurações + Substitui seu histórico atual, inscrições, playlists e (opcionalmente) configurações + Exporta histórico, inscrições, playlists e configurações Exportado Importado Nenhum arquivo ZIP válido Aviso: Não foi possível importar todos os arquivos. Isso irá sobrescrever suas configurações atuais. - Baixar arquivo de transmissão + Baixar arquivo Mostrar informação - Listas de reprodução favoritas + Playlists favoritas Adicionar a Arraste para ordenar Criar - Dispensar + Descartar Renomear - Último reproduzido - Mais reproduzido + Última reprodução + Mais assistidos Sempre perguntar - Nova lista de reprodução + Nova playlist Renomear Nome - Adicionar à lista de reprodução - Definir como miniatura da lista de reprodução - Favoritar lista de reprodução + Adicionar à playlist + Definir como miniatura da playlist + Salvar como playlist favorita Remover dos favoritos - Excluir esta lista de reprodução? - lista de reprodução criada - Adicionado à lista de reprodução - Miniatura da lista de reprodução alterada. + Excluir esta playlist? + Playlist criada + Adicionado à playlist + Miniatura da playlist alterada. Sem legendas Ajustar Preencher @@ -246,11 +246,11 @@ Reportar erros de out-of-lifecycle Forçar entrega de relatórios de erros Rx fora de um fragmento ou atividade de lifecycle após o descarte Usar busca de posição rápida (inexata) - A busca inexata permite que o reprodutor de vídeo ache posições mais rápido com a precisão reduzida. Não funciona para voltar ou avançar 5, 15 ou 25 segundos + A busca inexata permite que o player de vídeo ache posições mais rápido com a precisão reduzida. Não funciona para voltar ou avançar 5, 15 ou 25 segundos Enfileirar a próxima transmissão automaticamente Continua a reprodução da fila (sem repetição) adicionando mais transmissões similares Arquivo - Pasta não existe + Essa pasta não existe Arquivo/fonte do conteúdo não existe O arquivo não existe ou não há permissão para leitura ou escrita O nome do arquivo não pode ficar vazio @@ -262,78 +262,78 @@ Exportando… Importar arquivo Exportação anterior - Não foi possível importar inscrições - Não foi possível exportar inscrições - Importe inscrições do YouTube pelo Google takeout: + Não foi possível importar as inscrições + Não foi possível exportar as inscrições + Importar inscrições do YouTube em Google Takeout: \n \n1. Acesse este URL: %1$s \n2. Faça login quando solicitado -\n3. Clique em \"Todos os dados incluídos\", depois em \"Desmarcar todos\", em seguida, selecione apenas \"assinaturas\" e clique em \"OK\" -\n4. Clique em \"Próximo passo\" e em seguida, em \"Criar exportação\" -\n5. Clique no botão \"Baixar\" quando ele aparecer +\n3. Clique em \"Todos os dados incluídos\", depois em \"Desmarcar todos\", selecione apenas \"Inscrições\" e clique em \"OK\" +\n4. Clique em \"Próxima etapa\" e, em seguida, em \"Criar exportação\" +\n5. Clique no botão \"Download\" quando ele aparecer \n6. Clique em IMPORTAR ARQUIVO abaixo e selecione o arquivo .zip baixado -\n7. Caso a importação do arquivo .zip falhe: Extraia o arquivo .csv (geralmente em \"YouTube e YouTube Music/subscriptions/subscriptions.csv\", clique em IMPORTAR ARQUIVO abaixo e selecione o arquivo csv extraído - Importe um perfil do SoundCloud digitando o URL ou seu ID: +\n7. [Se a importação do .zip falhar] Extraia o arquivo .csv (geralmente em \"YouTube e YouTube Music/subscriptions/subscriptions.csv\"), clique em IMPORTAR ARQUIVO abaixo e selecione o arquivo csv extraído + Importar um perfil do SoundCloud digitando o URL ou seu ID: \n -\n1. Ative o \"modo desktop\" em um navegador (o site não está disponível em aparelhos celulares) +\n1. Ative o \"Modo desktop (computador)\" em um navegador da Web (o site não está disponível para dispositivos móveis) \n2. Acesse este URL: %1$s \n3. Faça login quando solicitado -\n4. Copie o URL do perfil que você foi redirecionado. +\n4. Copie o URL do perfil para o qual você foi redirecionado. seuID, soundcloud.com/seuid Tenha em mente que esta operação poderá consumir muitos dados. \n \nVocê deseja continuar\? - Cache de imagens limpo + Cache de imagens removidos Limpar cache de metadados Remove todos os dados de páginas em cache - Cache de metadados limpo - Controles de velocidade de reprodução + Cache de metadados removidos + Controles para velocidade de reprodução Velocidade Afinação Desvincular (pode causar distorção) Ação de \'abrir\' preferida Ação padrão ao abrir conteúdo — %s - Nenhuma transmissão disponível para baixar + Nenhum vídeo disponível para download Abrir gaveta Fechar gaveta Legendas Mudar tamanho da legenda e estilos de plano de fundo. Requer reiniciar o aplicativo para ter efeito - Excluir histórico de vídeo - Exclui o histórico de transmissões exibidas e as posições de reprodução - Excluir todo o histórico de vídeo\? - Histórico de vídeos excluído - Excluir histórico de pesquisa - Exclui o histórico de palavras-chave de pesquisa - Excluir todo o histórico de pesquisa\? - Histórico de busca limpo + Limpar histórico de exibição + Remove histórico de vídeos assistidos e as posições de reprodução + Remover todo o histórico de exibição? + Histórico de exibição removido + Remover histórico de pesquisas + Remove histórico de pesquisas + Remover todo histórico de pesquisas? + Histórico de pesquisa removido 1 item excluído. NewPipe é um copyleft de software livre: Você pode usar, estudar, compartilhar e melhorar a seu gosto. Especificamente você pode redistribuir e/ou modificá-lo sob os termos da GNU General Public License como publicado pela Fundação de Software Livre, na versão 3 da Licença, ou (a seu critério) qualquer versão posterior. Você também quer importar as configurações? Política de privacidade do NewPipe O projeto NewPipe leva sua privacidade muito a sério. Por isso, o aplicativo não coleta nenhum dado sem seu consentimento. \nA política de privacidade do NewPipe explica em detalhes quais dados são envidados e salvos quando você manda um relatório de erro. - Ler a política de privacidade + Ver política de privacidade A fim de cumprir com o Regulamento Geral sobre a Proteção de Dados da UE (RGPD), chamamos sua atenção para a política de privacidade do NewPipe. Por favor, leia com atenção. \nVocê deve aceitá-la para nos enviar o relatório de erros. Aceitar Recusar Ilimitado - Limitar a resolução quando estiver usando dados móveis - Minimizar ao trocar entre aplicativos - Ação ao mudar para outro aplicativo a partir do reprodutor de vídeo principal — %s + Limitar resolução de vídeos ao usar dados móveis + Minimizar ao mudar de aplicativos + Ação ao mudar de aplicativo a partir do player principal - %s Nenhum - Minimizar para segundo plano - Minimizar para reprodutor Popup + Minimizar reprodução para o modo de segundo plano + Minimizar reprodução para o modo Popup Avançar durante o silêncio Passo Redefinir Canais - Listas de reprodução + Playlists Faixas Usuários Cancelar inscrição - Selecionar aba - Debug + Escolha a guia + Depuração Atualizações Eventos Arquivo excluído @@ -341,26 +341,26 @@ Notificações para novas versões do NewPipe Armazenamento externo indisponível Não é possível baixar para o cartão SD externo. Redefinir o local da pasta de download\? - Não foi possível carregar as abas salvas, carregando as abas padrão - Restaurar padrões - Deseja restaurar padrões\? + Não foi possível ler as guias salvas, portanto, usamos as guias padrão + Restaurar configurações + Deseja restaurar os padrões? Número de inscritos indisponível - Que abas são visíveis na página inicial + Quais guias são exibidas na página inicial Conferências Atualizações Notificar quando uma nova versão do aplicativo estiver disponível - Modo de exibição em lista + Modo de exibição da lista Lista Grade Automático - Atualização do NewPipe disponível! + Uma atualização do NewPipe está disponível! Finalizado pausado na fila pós-processamento - Fila + Colocar na fila Ação negada pelo sistema - O download falhou + Download falhou Gerar nome único Sobrescrever Um arquivo baixado com esse nome já existe @@ -368,11 +368,11 @@ Mostrar erro O arquivo não pode ser criado A pasta de destino não pode ser criada - Uma conexão segura não pôde ser estabelecida - O servidor não pôde ser encontrado + Não foi possível estabelecer uma conexão segura + Não foi possível encontrar o servidor Não foi possível se conectar ao servidor O servidor não envia dados - O servidor não aceita downloads em multi-thread, tente de novo com @string/msg_threads = 1 + O servidor não aceita downloads multi-processo, tente novamente com @string/msg_threads = 1 Não encontrado Falha no pós-processamento Parar @@ -392,7 +392,7 @@ Posições em listas Mostra indicadores de posição de reprodução em listas Excluir dados - Posições de reprodução limpas + Posições de reprodução removidas Arquivo movido ou excluído Já existe um arquivo com este nome O arquivo não pode ser sobrescrito @@ -403,24 +403,24 @@ Tempo limite de conexão Excluir todo o histórico de downloads ou excluir todos os arquivos baixados\? Limitar fila de downloads - Faz downloads um de cada vez + Permitir apenas um download de cada vez Iniciar downloads Pausar downloads Perguntar onde salvar o arquivo Você será questionado onde salvar cada download. \nAtive o seletor de pasta do sistema (SAF) se você quiser baixar em um cartão SD externo Usar o seletor de pastas do sistema (SAF) - O \'Storage Access Framework\' permite baixar em um cartão SD externo - Excluir posição das reproduções - Exclui todas as posições de reprodução - Excluir todas as posições de reprodução\? - Alternar serviço, selecionados: - Quiosque Padrão - Ninguém está vendo + A \"Estrutura de acesso ao armazenamento\" permite baixar em um cartão SD externo + Remover posições de reprodução + Remove todas as posições de reprodução + Remover todas as posições de reprodução? + Alternar serviço, atualmente selecionado: + Kiosk padrão + Ninguém está assistindo %s assistindo %s assistindo - %s estão vendo + %s assistindo Ninguém está ouvindo @@ -429,14 +429,14 @@ %s ouvintes O idioma será alterado após reiniciar o aplicativo - Duração do salto para avançar/retroceder - Instâncias do PeerTube - Escolha suas instâncias do PeerTube favoritas + Duração de avanço/retrocesso rápido + Instâncias PeerTube + Selecione suas instâncias favoritas do PeerTube Encontre as instâncias que gosta em %s Adicionar instância Insira o URL da instância Erro ao validar a instância - Apenas os URL HTTPS são suportados + Somente URL HTTPS são compatíveis A instância já existe Local Adicionado recentemente @@ -447,7 +447,7 @@ Escolha uma instância Limpar histórico de downloads Excluir arquivos baixados - Dar permissão para mostrar por cima de outros aplicativos + Obter permissão para exibir sobre outros aplicativos Idioma do aplicativo Padrão do sistema Toque em \"Pronto\" ao resolver @@ -521,34 +521,34 @@ Este vídeo tem restrição de idade. \n \nAtive \"%1$s\" nas configurações se quiser vê-lo. - Sim, e vídeos parcialmente vistos - Os vídeos que foram vistos antes e depois de terem sidos adicionados à lista de reprodução serão removidos. + Sim, e vídeos parcialmente assistidos + Os vídeos que foram assistidos antes e depois de terem sidos adicionados à playlist serão removidos. \nTem certeza? Esta ação não pode ser desfeita! - Remover vídeos vistos\? - Remover vistos + Remover vídeos assistidos? + Remover assistidos Textos originais dos serviços serão visíveis nos itens de transmissão Mostrar tempo original nos itens Ativar o \"Modo Restrito\" do YouTube Por %s Criado por %s - Miniatura do avatar do canal + Foto de perfil do canal Mostrar apenas inscrições não agrupadas Mostrando resultados para: %s - Ainda não há listas de reprodução favoritas - Página da lista de reprodução - Selecione uma lista de reprodução - Por favor verifique se uma issue discutindo este problema já existe. Ao criar tickets duplicados, você tira de nós um tempo no qual poderíamos estar usando para corrigir um bug real. + Ainda não há playlist favoritas + Página da playlist + Selecione uma playlist + Verifique se o erro já foi informado. Ao informar erros duplicados, você nos toma o tempo que poderíamos dedicar a outras correções de erros. Reporte no GitHub Copiar relatório formatado Nunca - Apenas no Wi-Fi + Apenas em Wi-Fi Iniciar reprodução automaticamente — %s - Reproduzir fila + Fila de reprodução Não foi possível reconhecer a URL. Abrir com outro aplicativo\? Pôr na fila automaticamente - A fila do reprodutor ativo será substituída - Mudar de um reprodutor de vídeo para outro pode substituir sua fila - Pedir confirmação antes de limpar uma fila + A fila de reprodução atual será substituída + Mudar de um player para outro pode substituir sua fila + Pedir confirmação antes de limpar a fila Aleatório Carregando Nada @@ -560,8 +560,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Cortar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 - Cortar a miniatura para a proporção de 1:1 + Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 + Ajustar miniatura para a proporção de 1:1 Mostrar vazamentos de memória Na fila Pôr na fila @@ -575,8 +575,8 @@ Usar miniatura para o plano de fundo da tela de bloqueio e notificações Mostrar miniatura Calculando hash - Notificações para o progresso do hash do vídeo - Notificação de hash do vídeo + Notificações sobre o progresso do hashing de vídeo + Notificar hash de vídeo Desative para ocultar as caixas de informações de metadados com informações adicionais sobre o criador, conteúdo da transmissão ou uma solicitação de pesquisa Mostrar informação de metadados Recente @@ -605,8 +605,8 @@ Automático (tema do dispositivo) Tema noturno Mostrar detalhes do canal - Desative o tunelamento de mídia se aparecer uma tela preta ou se tiver engasgos durante a reprodução do vídeo. - Desativar tunelamento de mídia + Desative o túnel de mídia se aparecer uma tela preta ou se tiver travamento durante a reprodução do vídeo. + Desativar túnel de mídia Interno Privado Não Listado @@ -631,52 +631,52 @@ \nDeseja cancelar a inscrição neste canal\? Não foi possível carregar o feed para \'%s\'. Erro ao carregar o feed - O \'Storage Access Framework\' é compatível apenas com versões a partir do Android 10 + A \"Estrutura de acesso ao armazenamento\" é compatível apenas com versões a partir do Android 10 Você será questionado onde salvar cada download Nenhuma pasta de download definida ainda, escolha a pasta de download padrão agora - Desligado - Ligado + Desativado + Ativado Modo tablet Não mostrar - Baixa qualidade (menor) - Alta qualidade (maior) + Baixa qualidade (pior) + Alta qualidade (melhor) Pré visualização da miniatura da barra de busca Os comentários estão desabilitados - Marcar como visto + Marcar como assistido Curtido pelo criador Exibir fitas coloridas no topo das imagens indicando sua fonte: vermelho para rede, azul para disco e verde para memória - %1$s download apagado - %1$s downloads apagados - %1$s downloads apagados + %1$s download excluído + %1$s downloads excluídos + %1$s downloads excluídos %s download concluído %s downloads concluídos %s downloads concluídos - Exibir indicadores com imagem + Mostrar indicadores de imagem Adicionado na próxima posição da fila Enfileira a próxima - Deslize items para remove-los - Não inicia os vídeos no reprodutor reduzido, mas muda direto para o modo de tela cheia, se a rotação automática estiver travada. Você ainda consegue acessar o reprodutor reduzido saindo da tela cheia - Iniciar o reprodutor principal em tela cheia + Deslize os itens para remove-los + Não inicie os vídeos no mini player, mas vá diretamente para o modo de tela cheia, se a rotação automática estiver bloqueada. Você ainda pode acessar o mini player saindo da tela cheia + Iniciar player principal em tela cheia Sugestões de busca remotas Sugestões de busca locais Processando… Pode demorar um pouco - Procurar por atualizações - Procurar manualmente por novas versões - Procurando por atualizações… - Travar o reprodutor de vídeo - Mostrar \"Fechar o reprodutor\" - Mostra uma opção de travamento ao usar o reprodutor + Buscar atualizações + Verificar manualmente se há novas versões + Buscando atualizações… + Travar reprodução + Mostrar \"Travar reprodução\" + Mostra uma opção para travar a reprodução Novos itens do feed Notificação de relatório de erro Notificações para reportar erros - O NewPipe encontrou um erro, toque para reportar + O NewPipe encontrou um erro, toque para relatar Crie uma notificação de erro Nenhum gerenciador de arquivos apropriado foi encontrado para esta ação. -\nInstale um gerenciador de arquivos compatível com o Storage Access Framework +\nInstale um gerenciador de arquivos compatível com a \"Estrutura de acesso ao armazenamento\" Ocorreu um erro, consulte a notificação Mostrar um snackbar de erro Nenhum gerenciador de arquivos apropriado foi encontrado para esta ação. @@ -684,36 +684,36 @@ Comentário fixado O LeakCanary não está disponível ExoPlayer padrão - Notificação do reprodutor - Configurar a notificação da reprodução da transmissão atual + Notificação de reprodução + Configurar notificação da reprodução do vídeo atual Notificações - Novas transmissões - Notificações sobre novas transmissões para inscrições - Notificações de novas transmissões - Notificar sobre novas transmissões de inscrições + Novos vídeos + Notificações sobre novos vídeos de inscrições + Notificações sobre novos vídeos + Notificar sobre novos vídeos de inscrições Frequência de verificação Nenhuma rede - Excluir todos os arquivos baixados do disco\? + Excluir todos os arquivos baixados? Agora você se inscreveu neste canal Alternar tudo , Carregando detalhes da transmissão… - %s nova transmissão - %s novas transmissões - %s novas transmissões + %s novo vídeo + %s novos vídeos + %s novos vídeos - Verifica por novas transmissões + Buscar novos vídeos Conexão de rede necessária As notificações estão desativadas Seja notificado Por cento Semitom - A transmissão selecionada não é compatível com reprodutores externos - Nenhum transmissão de áudio está disponível para reprodutores externos - Transmissões que ainda não são suportadas pelo baixador não são exibidos - Nenhum vídeo de transmissão está disponível para reprodutores externos - Selecione a qualidade para reprodutores externos + O vídeo selecionado não é compatível com players externos + Nenhuma transmissão de áudio está disponível para players externos + Os vídeos que ainda não são suportados pelo assistente de download não são exibidos + Nenhuma transmissão de vídeo está disponível para players externos + Selecione a qualidade para players externos Formato desconhecido Qualidade desconhecida Tamanho do intervalo de carregamento da reprodução @@ -722,32 +722,32 @@ Perguntas frequentes Classificar Modo rápido - Importar ou exportar inscrições do menu de 3 pontos + Importar ou exportar inscrições no menu com 3 pontos Toque para baixar %s - Você está executando a versão mais recente do NewPipe + Você já possui a atualização mais recente do NewPipe Esta opção só está disponível se %s for selecionado para Tema Desativar miniatura permanente Cartão Falha ao copiar para a área de transferência Duplicata adicionada %d vez(es) - As listas de reprodução em cinza já contêm este item. + As playlists em cinza já contêm este item. Ignorar eventos de botão de mídia de hardware Útil, por exemplo, se você estiver usando um fone de ouvido com botões físicos quebrados Remover duplicados Remover duplicados\? - Deseja remover todos as transmissões duplicadas nesta lista de reprodução? - Mostrar as transmissões seguintes - Mostrar/ocultar transmissões + Deseja remover todos os vídeos duplicados nesta playlist? + Mostrar próximos vídeos + Mostrar/ocultar vídeos Parcialmente assistido Em breve Totalmente assistido - Escolha o gesto da mão esquerda da tela do reprodutor - Ação do gesto esquerdo - Escolha o gesto da mão direita da tela do reprodutor + Escolha o gesto para a parte esquerda na tela de reprodução + Ação para o gesto à esquerda + Escolha o gesto para a parte direita na tela de reprodução Brilho Volume Nenhum - Ação do gesto direito + Ação para o gesto à direita Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo Dar preferência ao áudio original Selecionar o áudio original e independentemente do idioma @@ -755,69 +755,69 @@ Selecionar um áudio com descrição para pessoas com dificuldades de visão, se disponível Áudio: %s Faixa de áudio - Seleciona faixa de áudio para reprodutor externo + Selecione a faixa de áudio para players externo Desconhecido - Configurações de ExoPlayer - Gerenciar algumas configurações de ExoPlayer. É necessário reiniciar o reprodutor para aplicar as mudanças + Configurar ExoPlayer + Gerenciar algumas configurações do ExoPlayer. É necessário reiniciar o player para aplicar as mudanças %1$s %2$s original dublado descritivo Esta solução alternativa libera os codificadores de vídeo quando ocorre uma alteração de superfície, no lugar de definir a superfície para o Codec diretamente. Já usado pelo ExoPlayer em alguns dispositivos com esse problema, essa configuração só tem efeito no Android 6 e superior \n -\nAtivar esta opção pode evitar erros de reprodução ao alternar o reprodutor de vídeo atual ou alternar para tela cheia - Uma faixa de áudio já deve estar presente nesta transmissão - Utilizar a contingência do decodificador do ExoPlayer - Sempre utilizar o configuração de saída de vídeo alternativa do ExoPlayer +\nAtivar esta opção pode evitar erros de reprodução ao alternar o player de vídeo atual ou alternar para tela cheia + Uma faixa de áudio já deve estar presente neste vídeo + Usar decodificador alternativo do ExoPlayer + Usar sempre a solução alternativa de configuração da superfície de saída de vídeo do ExoPlayer Habilite essa opção se você tiver problemas de inicialização do decodificador, que retorna codificadores de baixa prioridade se o decodificador primário falhar. Isso pode resultar em pior desempenho de reprodução - Mova o seletor da aba principal para a parte inferior - Posição das abas principais - Nenhuma transmissão + Mover o seletor da guia principal para a parte inferior + Posição de guias principais + Nenhum conteúdo Nenhuma transmissão ao vivo - O tunelamento de mídia foi desabilitado por padrão em seu dispositivo porque seu modelo é conhecido por não suportá-lo. + O túnel de mídia foi desabilitado por padrão em seu dispositivo porque seu modelo é conhecido por não suportá-lo. Vídeos Inscritos - Quais guias são mostradas nas páginas do canal - Guias de canal + Quais guias são mostradas na página do canal + Guias do canal Shorts Carregando metadados… Buscar guias de canal Sobre Álbuns Guias a serem buscadas ao atualizar o feed. Esta opção não tem efeito se um canal for atualizado usando o modo rápido. - Listas de reprodução + Playlists Faixas Canais Ao vivo Qualidade da imagem \? - Compartilhar lista URL - Compartilhar com Títulos + Compartilhar URL + Compartilhar com título %1$s \n%2$s - Alterna a orientação da tela + Alternar orientação da tela Baixa qualidade Alternar tela cheia - Avatares - Próxima transmissão - Avatares do subcanal - Abrir a fila de reprodução + Fotos + Próxima vídeo + Fotos de perfil do subcanal + Abrir fila de reprodução Não carregar imagens Alta qualidade - Compartilhar Lista de Reprodução + Compartilhar playlist Avançar Retroceder - Repete - Compartilhar lista de reprodução com detalhes como nome da lista de reprodução e títulos de vídeo ou como uma lista simples dos URL de vídeos + Repetir + Compartilhar playlist com detalhes como o nome da playlist e títulos de vídeo ou como uma lista simples dos URL de vídeos Qualidade média - Avatares do carregador + Fotos de perfil do autor - %1$s: %2$s - Escolha a qualidade das imagens ou se deve carregá-las como estão, para reduzir o uso de dados e memória. Alterações limpam a memória e o cache de imagem no disco — %s + Escolha a qualidade das imagens e se as imagens devem ser carregadas, para reduzir o uso de dados e memória. As alterações limpam o cache de imagens na memória e no disco - %s Reproduzir Mais opções Miniaturas Duração - Transmissão anterior + Vídeo anterior Banners Mostrar mais Edite cada ação de notificação abaixo tocando nela. As três primeiras ações (reproduzir/pausar, anterior e seguinte) são definidas pelo sistema e não podem ser personalizadas. @@ -827,4 +827,15 @@ %s respostas Mostrar menos + Não há espaço livre suficiente no dispositivo + Sim + Não + Backup e restauração + O NewPipe pode verificar automaticamente se há novas versões de tempos em tempos e notificá-lo quando elas estiverem disponíveis. +\nDeseja ativar essa opção? + Restaurar configurações + Restaurar todas as configurações para seus valores padrão + A restauração de todas as configurações descartará todas as suas configurações preferidas e reiniciará o aplicativo. +\n +\nTem certeza de que deseja continuar? \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 325c79d3c8..498a49a53b 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -827,4 +827,15 @@ %s respostas Mostrar menos + O NewPipe pode verificar automaticamente se há novas versões de tempos em tempos e notificá-lo quando elas estiverem disponíveis. +\nDeseja ativar essa opção? + Repor valores originais de todas as definições + A restauração de todas as configurações descartará todas as suas configurações preferidas e reiniciará a app. +\n +\nTem certeza que deseja continuar? + Sim + Não + Backup e restauro + Repor definições + Não há espaço suficiente no aparelho \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e69f79a685..5534dbf0b5 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -827,4 +827,15 @@ Mostrar menos Edite cada ação de notificação abaixo a tocar nela. As três primeiras ações (reproduzir/pausa, anterior e seguinte) são definidas pelo sistema e não podem ser personalizadas. + Não há espaço suficiente no dispositivo + Sim + Não + Repor valores originais de todas as definições + Backup e restauro + Repor definições + A restauração de todas as configurações descartará todas as suas configurações preferidas e reiniciará a app. +\n +\nTem certeza que deseja continuar? + O NewPipe pode verificar automaticamente se há novas versões de tempos em tempos e notificá-lo quando elas estiverem disponíveis. +\nDeseja ativar essa opção? \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index c9eaaf2c94..bcef4d9524 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -765,15 +765,14 @@ Schimbați pe ecran complet Preluați filele canalului Avatare - Activați această opțiune dacă aveți probleme cu inițializarea decodorului care trece înapoi la decodoare cu prioritate mai scăzută dacă inițializarea decodoarelor principale eșuează. Asta poate duce la performanță de redare mai slabă decât atunci când se utilizează decodoarele principale. + Activați această opțiune dacă aveți probleme cu inițializarea decodorului care trece înapoi la decodoare cu prioritate mai scăzută dacă inițializarea decodoarelor principale eșuează. Asta poate duce la performanță de redare mai slabă decât atunci când se utilizează decodoarele principale Acțiunea gestului din dreapta %1$s %2$s Utilizați întotdeauna configurarația suprafeței de ieșire video din ExoPlayer ca soluție alternativă Transmisia viitoare Tunelizarea media a fost dezactivată în mod implicit pe dispozitivul dumneavoastră deoarece se cunoaște despre acest model de dispozitiv că nu o suportă. Avatarele subcanalelor - Această soluție alternativă eliberează și reinstanțează codecurile video când se întamplă o schimbare a suprafeței, în loc de a seta suprafața pentru codec direct. Deja -\n folosită de ExoPlayer pe unele dispozitive cu această problemă, această setare are efect doar pe Android 6 sau mai mare + Această soluție alternativă eliberează și reinstanțează codecurile video când se întamplă o schimbare a suprafeței, în loc de a seta suprafața pentru codec direct. Deja folosită de ExoPlayer pe unele dispozitive cu această problemă, această setare are efect doar pe Android 6 sau mai mare \n \nActivarea acestei opțiuni poate preveni erorile de redare când se schimbă playerul video curent sau se trece pe ecran complet O coloană sonoră ar trebui să fie deja prezentă în această transmisie @@ -829,4 +828,14 @@ Arată mai puține Piste Nu este suficient spațiu liber pe dispozitiv + Backup și restabilire + NewPipe poate verifica automat pentru versiuni noi din când în când și te poate notifica când acestea sunt disponibile. +\nDoriți să activați acest lucru? + Resetează toate setările la valorile inițiale + Da + Nu + Resetează setări + Resetarea tuturor setărilor va elimina toate setările tale preferate și va reporni aplicația. +\n +\nSigur doriți să continuați? \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index df566853b2..9e79b165fc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -833,4 +833,11 @@ Отредактируйте каждое действие уведомления ниже, нажав на него. Первые три действия (воспроизведение/пауза, предыдущее и следующее) задаются системой и не подлежат настройке. Недостаточно свободного места на устройстве + Сбросить все настройки на их значения по умолчанию + Да + Нет + Резервное копирование и восстановление + Сбросить настройки + NewPipe может автоматически проверять наличие обновлений и уведомить вас, когда они будут доступны. +\nЖелаете включить эту функцию? \ No newline at end of file diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml index 195eb22b3f..5a4f35de5f 100644 --- a/app/src/main/res/values-ryu/strings.xml +++ b/app/src/main/res/values-ryu/strings.xml @@ -813,4 +813,15 @@ なーふぃんんーじゅん ひょうじいきらくすん いかぬちうちアクションタップしへんしゅうさびーん。さいしょぬみーちぬアクション (さいせい/いちじていし、めーんかい、ちぎんかい)ーシステムにゆってぃしっていさりてぃうぅい、カスタマイズすしぇーなやびらん。 + はい + NewPipeーてぃんじちーがみーさるバージョンじちゃーてぃきんかいチェックしー、こうしんがのうないるとぅちうちさびーん。 +\nゆうこうなさびーが? + デバイスぬあきゆういょうがふすくそーいびーん + うぅーうぅー + バックアップとぅふくぎん + しっていリセット + まじりぬしっていデフォルトじょうたいんかいリセットさびーん + まじりぬしっていリセットしーねー、ゆーいるしんしっていぬまじりはちされい、アプリぬさいきちゃーさびーん。 +\n +\nずっこうさびーが? \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index a497844ab4..6b89b32c3c 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -539,7 +539,7 @@ Colende dae unu riproduidore a s\'àteru dias pòdere remplasare sa lista tua Pedi una cunfirma in antis de iscantzellare una lista Òrdine casuale - Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra + Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra. Sega sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 Nudda Carrighende @@ -806,4 +806,22 @@ Canales Flussu antepostu Diretas + Modìfica cada atzione de notìfica inoghe in suta tochende·la. Sas primas tres atziones (riprodutzione/pàusa, antepostu e imbeniente) sunt impostadas dae su sistema e non si podent personalizare. + Non b\'at ispàtziu lìberu bastante in su dispositivu + Mustra de mancu + NewPipe podet chircare in automàticu versiones noas cada tantu e notificare·ti cando sunt a disponimentu. +\nLu boles abilitare? + Nono + Còpia de seguresa e riprìstinu + + %s risposta + %s rispostas + + Mustra de prus + Eja + Reseta sas impostatziones + Reseta totu sas impostatziones a sos valores predefinidos issoro + Resetende totu sas impostatziones as a iscartare totu sas impostatziones preferidas tuas e a torrare a allùghere s\'aplicatzione. +\n +\nSes seguru de bòlere sighire? \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 996b3fa2ee..5f14a0e51c 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -834,4 +834,8 @@ Ресетовање свих подешавања ће одбацити сва жељена подешавања и поново покренути апликацију. \n \nЖелите ли заиста да наставите? + Не + Да + NewPipe може аутоматски да проверава да ли постоје нове верзије с времена на време и да вас обавести када буду доступне. +\nЖелите ли да омогућите ово? \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 54aab0cdf1..8cf57f6054 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -814,4 +814,14 @@ Visa mindre Redigera varje aviseringsåtgärd nedan genom att trycka på den. De tre första åtgärderna (spela/pausa, föregående och nästa) är satta av systemet och kan inte ändras. Inte tillräckligt med ledigt utrymme på enheten + Nej + Säkerhetskopiering och återställning + Ja + NewPipe kan automatiskt söka efter nya versioner då och då och meddela dig när de är tillgängliga. +\nVill du aktivera detta? + Återställ inställningar + Återställ alla inställningar till deras standardvärden + Om du återställer alla inställningar försvinner alla dina föredragna inställningar och appen startas om. +\n +\nÄr du säker på att du vill fortsätta? \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fa63499ea0..c347086093 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -833,4 +833,14 @@ Показати менше Недостатньо вільного простору на пристрої + Скинути налаштування + Резервне копіювання і відновлення + NewPipe може час від часу автоматично перевіряти наявність нових версій і сповіщати вас про їх появу. +\nХочете увімкнути цю функцію? + Так + Ні + Скинути всі налаштування до усталених значень + Скидання всіх налаштувань призведе до скидання всіх вибраних вами налаштувань і перезапуску застосунку. +\n +\nВи впевнені, що хочете продовжити? \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index dcb8d2f84e..0d71173afb 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -8,7 +8,7 @@ Mở trong trình duyệt Mở trong chế độ bật lên Chia sẻ - Tải về + Tải xuống Tìm kiếm Cài đặt Ý bạn là \"%1$s\"\? @@ -16,7 +16,7 @@ Sử dụng trình phát video bên ngoài Loại bỏ âm thanh ở một số độ phân giải Sử dụng trình phát âm thanh bên ngoài - Trình phát nổi + Bật lên Thư mục video tải về Video đã tải về được lưu ở đây Chọn thư mục tải xuống cho các tệp video @@ -43,7 +43,7 @@ Ghi nhớ kích thước và vị trí cuối cùng của cửa sổ bật lên Đề xuất tìm kiếm Chọn các đề xuất để hiển thị khi tìm kiếm - Tải về + Tải xuống Hiện các video \"Tiếp theo\" và \"Tương tự\" URL không hỗ trợ Vẻ ngoài @@ -105,12 +105,12 @@ Giới thiệu về NewPipe Giấy phép của bên thứ ba © %1$s bởi %2$s dưới %3$s - Thông tin & FAQ + Giới thiệu & Câu hỏi thường gặp Giấy phép Phát trực tuyến nhẹ tự do trên Android. Xem trên GitHub Giấy phép của NewPipe - Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code, thêm tính năng hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết ! + Cho dù bạn có ý tưởng về: dịch thuật, thay đổi thiết kế, dọn mã hoặc thay đổi mã thực sự nhiều— sự trợ giúp luôn được hoan nghênh. Làm càng nhiều thì càng tốt! Đọc giấy phép Đóng góp Ngôn ngữ nội dung ưu tiên @@ -134,7 +134,7 @@ Tua ít chính xác cho phép trình phát giảm độ chính xác để tua tới vị trí nhanh hơn. Tua khoảng 5, 15 hoặc 25 giây không hoạt động với điều này Đã xóa bộ nhớ cache hình ảnh Xóa sạch siêu dữ liệu đã lưu đệm - Xóa bỏ mọi dữ liệu trang web đã lưu đệm + Xóa tất cả dữ liệu trang web được lưu trong bộ nhớ cache Đã xóa bộ nhớ cache siêu dữ liệu Tự động xếp hàng luồng phát tiếp theo Tiếp tục hàng đợi (không lặp lại) bằng cách thêm một luồng phát liên quan @@ -212,9 +212,9 @@ Ký tự thay thế Chỉ chữ cái và chữ số Hầu hết các ký tự đặc biệt - Đóng góp - NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chút xiền để giúp chúng tôi làm NewPipe tốt hơn nữa (Nếu bạn muốn). - Đôn Nét + Quyên tặng + NewPipe được phát triển bởi các tình nguyện viên dành thời gian rảnh rỗi để mang lại cho bạn trải nghiệm người dùng tốt nhất. Hãy đền đáp để giúp các nhà phát triển làm cho NewPipe thậm chí còn tốt hơn nữa trong khi họ thưởng thức một tách cà phê. + Đền đáp Trang web Truy cập website chính thức của NewPipe để biết thêm thông tin và tin tức. Chính sách bảo mật của NewPipe @@ -283,7 +283,7 @@ Nhập từ Xuất sang Đang nhập… - Đang xuất … + Đang xuất… Nhập tệp Xuất trước Không thể nhập đăng ký @@ -310,7 +310,7 @@ Tốc độ Độ cao Bỏ gắn (có thể gây méo) - Tua nhanh trong im lặng + Chuyển nhanh qua khoảng lặng Bước Đặt lại Để tuân thủ Quy định bảo vệ dữ liệu chung của châu Âu (GDPR), chúng tôi sẽ thu hút sự chú ý của bạn đến chính sách bảo mật của NewPipe. Vui lòng đọc kỹ. @@ -318,7 +318,7 @@ Chấp nhận Từ chối Không giới hạn - Giới hạn độ phân giải khi sử dụng 3G, 4G + Giới hạn độ phân giải khi sử dụng dữ liệu di động Thu nhỏ khi chuyển qua ứng dụng khác Hành động khi chuyển sang ứng dụng khác từ trình phát video chính — %s Không @@ -353,7 +353,7 @@ đang xử lý Xếp hàng Thao tác bị từ chối bởi hệ thống - Tải về không thành công + Tải xuống thất bại Tạo tên riêng biệt Ghi đè Có một tệp đã tải về trùng tên @@ -426,10 +426,10 @@ %d giây Có, và video đã xem một phần - Những video đã xem trước và sau khi thêm vào danh sách phát sẽ bị xóa. + Những video đã xem trước và sau khi thêm vào danh sách phát sẽ bị loại bỏ. \nBạn có chắc không? Điều này không thể được hoàn tác! - Xóa video đã xem? - Xóa đã xem + Xóa các video đã xem? + Loại bỏ đã xem Mặc định hệ thống Ngôn ngữ ứng dụng \'Khung truy cập lưu trữ\' cho phép tải xuống một thẻ SD bên ngoài @@ -563,7 +563,7 @@ Bình luận Thông báo cho quá trình băm video Thông báo băm video - Tắt để ẩn các hộp thông tin meta có thông tin bổ sung về người tạo luồng, nội dung luồng hoặc yêu cầu tìm kiếm + Tắt để ẩn các hộp siêu dữ liệu có thông tin bổ sung về người tạo luồng, nội dung luồng hoặc yêu cầu tìm kiếm Hiển thị thông tin meta Tắt để ẩn mô tả video và các thông tin bổ sung Hiện mô tả @@ -624,7 +624,7 @@ Bình luận đã bị tắt Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem - Hiện ruy băng được tô màu Picasso ở trên cùng các hình ảnh và chỉ ra nguồn của chúng: đỏ đối với mạng, xanh lam đối với ổ đĩa và xanh lá đối với bộ nhớ + Hiển thị các dải băng màu Picasso trên đầu các hình ảnh cho biết nguồn của chúng: màu đỏ cho mạng, màu lam cho đĩa và màu lục cho bộ nhớ Hiện dấu chỉ hình ảnh Đề xuất tìm kiếm trên mạng Đề xuất tìm kiếm cục bộ @@ -634,7 +634,7 @@ %s lượt tải xuống đã hoàn tất - Vuốt các mục để xóa chúng + Vuốt các mục để loại bỏ chúng Không bắt đầu các video ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình Khởi động trình phát chính ở toàn màn hình Đã xếp kế tiếp vào hàng @@ -683,7 +683,7 @@ Phần trăm , Nửa cung - Luồng băng hình mà không được trình tải xuống hỗ trợ sẽ không hiển thị + Các luồng chưa được trình tải xuống hỗ trợ sẽ không được hiển thị Không có luồng video nào khả dụng cho trình phát bên ngoài Luồng phát đã chọn không được trình phát ngoài hỗ trợ Không có luồng âm thanh nào khả dụng cho máy phát bên ngoài @@ -705,9 +705,9 @@ Chế độ tăng tốc Danh sách phát màu xám thì đã chứa mục này. Hữu ích trong trường hợp phím bấm âm lượng trên tai nghe hoặc thiết bị của bạn bị hỏng - Không nhận phím điều khiển âm lượng vật lý - Loại bỏ mục trùng lặp - Loại bỏ mục trùng lặp\? + Bỏ qua nhận nút phương tiện vật lý + Loại bỏ các bản trùng lặp + Loại bỏ các bản trùng lặp? Bạn có muốn loại bỏ mọi luồng trùng lặp trong danh sách phát này\? Hiện/Ẩn luồng phát Hiển thị các luồng phát sau diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index a59da9c4ec..518a1290fc 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -800,4 +800,14 @@ 拉開 撳下面嘅掣去更改對應嘅通知動作。頭三個動作 (播放/暫停、上一個、下一個) 系統預設咗,冇得揀。 部機冇晒位 + + NewPipe 可以周不時自動睇過有冇新版本,一出咗就通知您。 +\n您想唔想啟用呢個功能? + 推翻所有設定 + 推翻所有設定就會全盤抹走晒您喜好過嘅設定,然後重新開過個 app 個囉噃。 +\n +\n您確定要不惜一切推倒重來? + 備份與還原 + 唔使喇 + 顛覆所有設定,光復成預設值重新開始 \ No newline at end of file diff --git a/fastlane/metadata/android/ar/changelogs/997.txt b/fastlane/metadata/android/ar/changelogs/997.txt new file mode 100644 index 0000000000..f338558a18 --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/997.txt @@ -0,0 +1,17 @@ +جديد +• إضافة ردود التعليق +• السماح بإعادة ترتيب قوائم التشغيل +• عرض وصف قائمة التشغيل ومدتها +• السماح بإعادة ضبط الإعدادات + +تحسين +• [Android 13+] استعادة إجراءات الإشعارات المخصصة +• طلب الموافقة للتحقق من التحديث +• السماح بتشغيل/إيقاف الإشعارات مؤقتًا أثناء التخزين المؤقت +• إعادة ترتيب بعض الإعدادات + +مُثَبَّت +• [YouTube] إصلاح مشكلة عدم تحميل التعليقات، بالإضافة إلى إصلاحات وتحسينات أخرى +• حل مشكلة عدم الحصانة في إعدادات الاستيراد والتحويل إلى JSON +• إصلاحات التنزيل المختلفة +• تقليم نص البحث diff --git a/fastlane/metadata/android/cs/changelogs/997.txt b/fastlane/metadata/android/cs/changelogs/997.txt new file mode 100644 index 0000000000..c2fbe35a4e --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/997.txt @@ -0,0 +1,17 @@ +Nové +• Odpovědi na komentáře +• Změna pořadí playlistů +• Zobrazení popisu a trvání playlistu +• Možnost resetu nastavení + +Vylepšeno +• [Android 13+] Obnovení vlastních akcí oznámení +• Žádost o souhlas pro kontrolu aktualizací +• Povolení přehrání/pozastavení oznámení při načítání +• Změna pořadí některých nastavení + +Opraveno +• [YouTube] Opraveno nenačítání komentářů a další opravy a vylepšení +• Oprava závislosti v importu nastavení a přechod na JSON +• Různé opravy stahování +• Trim vyhledávaného textu diff --git a/fastlane/metadata/android/de/changelogs/990.txt b/fastlane/metadata/android/de/changelogs/990.txt index 6e56a8e70d..d3c926d8fe 100644 --- a/fastlane/metadata/android/de/changelogs/990.txt +++ b/fastlane/metadata/android/de/changelogs/990.txt @@ -11,3 +11,4 @@ Verbessert: Behoben: • Verschiedene Probleme mit Player-Benachrichtigung: veraltete/fehlende Medieninfos, verzerrte Miniaturansicht +… diff --git a/fastlane/metadata/android/de/changelogs/997.txt b/fastlane/metadata/android/de/changelogs/997.txt new file mode 100644 index 0000000000..f55e72b053 --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/997.txt @@ -0,0 +1,17 @@ +Neu +• Antwort auf Kommentar +• Wiedergabelisten umordnen +• Wiedergabelisten-Beschreibung und -Dauer +• Rücksetzen der Einstellungen + +Verbessert +• [Android 13+] Wiederherstellen benutzerdef. Benachrichtigungsaktionen +• Zustimmung zur Update-Prüfung +• Während Pufferung Abspielen/Pause über Benachrichtigung +• Neuordnung einiger Einstellungen + +Behoben +• [YouTube] Kommentare wurden nicht geladen, weitere Korrekturen und Verbesserungen +• Sicherheitslücke beim Einstellungsimport und Umstellung auf JSON +• Verschiedene Download-Korrekturen +• Suchtext gekürzt diff --git a/fastlane/metadata/android/es/changelogs/810.txt b/fastlane/metadata/android/es/changelogs/810.txt index 2f569dc5bf..caacfec03e 100644 --- a/fastlane/metadata/android/es/changelogs/810.txt +++ b/fastlane/metadata/android/es/changelogs/810.txt @@ -8,12 +8,12 @@ Mejorado - Añadir una miniatura ficticia para listas de reproducción locales vacías - Usar la extensión de archivos *.opus en lugar de *.webm y mostrar "opus" en etiqueta de formato en lugar de "WebM Opus" en menú desplegable de descargas - Añadir un botón para eliminar archivos descargados o el historial de descargas en "Descargas" -- YouTube] Añadir soporte a los enlaces de canal /c/shortened_url +- [YouTube] Añadir soporte a los enlaces de canal /c/shortened_url Corregidos - Corregidos múltiples problemas al compartir un video a NewPipe y al descargar sus secuencias directamente - Corregido el acceso al reproductor fuera de su hilo de creación - Corregida la paginación de resultados de búsqueda -- YouTube] Corregido el cambio a nulo que causaba NPE -- YouTube] Corregida la visualización de comentarios al abrir una url de invidio.us -- SoundCloud] Actualizado client_id +- [YouTube] Corregido el cambio a nulo que causaba NPE +- [YouTube] Corregida la visualización de comentarios al abrir una url de invidio.us +- [SoundCloud] Actualizado client_id diff --git a/fastlane/metadata/android/es/changelogs/961.txt b/fastlane/metadata/android/es/changelogs/961.txt index ce3a21b582..56230f9404 100644 --- a/fastlane/metadata/android/es/changelogs/961.txt +++ b/fastlane/metadata/android/es/changelogs/961.txt @@ -8,5 +8,5 @@ - Se ha corregido la posibilidad de compartir vídeos desde el reproductor - Corregida la vista web de ReCaptcha en blanco - Corregido el fallo que se producía al eliminar un stream de una lista -- PeerTube] Corregidos los flujos relacionados -- YouTube] Corregida la búsqueda de música en YouTube +- [PeerTube] Corregidos los flujos relacionados +- [YouTube] Corregida la búsqueda de música en YouTube diff --git a/fastlane/metadata/android/es/changelogs/977.txt b/fastlane/metadata/android/es/changelogs/977.txt index d5b4dad2a5..a8869fbcbf 100644 --- a/fastlane/metadata/android/es/changelogs/977.txt +++ b/fastlane/metadata/android/es/changelogs/977.txt @@ -1,5 +1,5 @@ • Añade botón de "reproducir siguiente" al menú presionado -• Añadido prefijo de ruta de Youtube shorts +• Añadido prefijo de ruta de YouTube Shorts • Corregida importación de ajustes • Intercambio entre barra y botones en pantalla de cola • Correcciones relacionadas con MediasessionManager diff --git a/fastlane/metadata/android/es/changelogs/997.txt b/fastlane/metadata/android/es/changelogs/997.txt new file mode 100644 index 0000000000..ee9a61d8c3 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/997.txt @@ -0,0 +1,17 @@ +Nuevo +• Añadir respuestas a comentarios +• Reordenar listas de reproducción +• Mostrar descripción y duración de listas de reproducción +• Restaurar ajustes + +Mejorado +• [Android 13+] acciones de notificación personalizadas +• Permiso para comprobar actualizaciones +• Reproducir/pausar notificaciones al almacenar en búfer +• Algunos ajustes + +Corregido +• [YouTube] Corregir comentarios que no se cargan y mejoras +• Fallo al importar de ajustes y JSON +• Corrección de varias descargas +• texto de búsqueda diff --git a/fastlane/metadata/android/hi/changelogs/997.txt b/fastlane/metadata/android/hi/changelogs/997.txt new file mode 100644 index 0000000000..652935884e --- /dev/null +++ b/fastlane/metadata/android/hi/changelogs/997.txt @@ -0,0 +1,17 @@ +नया +• टिप्पणियों के उत्तर जोड़ें +• प्लेलिस्ट को पुन: व्यवस्थित करने की अनुमति दें +• प्लेलिस्ट विवरण और अवधि दिखाएं +• सेटिंग्स रीसेट करने की अनुमति दें + +सुधार +• [एंड्रॉइड 13+] कस्टम अधिसूचना क्रियाएं पुनर्स्थापित करें +• अपडेटस की जांच के लिए सहमति का अनुरोध करें +• बफ़रिंग के दौरान नोटीफिकेशन से चलाने/रोकने की अनुमति दें +• कुछ सेटिंग्स पुनः व्यवस्थित करें + +ठीक किए +• [यूट्यूब] लोड न होने वाली टिप्पणियों को ठीक करें, साथ ही अन्य फिक्स और सुधार भी +• सेटिंग्स आयात में भेद्यता को हल करें और JSON पर स्विच करें +• विभिन्न डाउनलोड सुधार +• खोज पाठ को ट्रिम करें diff --git a/fastlane/metadata/android/id/changelogs/997.txt b/fastlane/metadata/android/id/changelogs/997.txt new file mode 100644 index 0000000000..7895e84e31 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/997.txt @@ -0,0 +1,15 @@ +Baru +• Tambah balasan komentar +• Izinkan susunan ulg playlist +• Tampilkan deskripsi & durasi daftar putar +• Izinkan setelan ulg +• [Android 13+] Pulihkan tindakan notifikasi khusus +• Minta persetujuan utk pemeriksaan update +• Izinkan pemutaran notifikasi sementara buffering +• Susun ulg bbrp setelan + +Perbaikan +• [YouTube] Perbaiki komentar yg tdk dapat dimuat, ditambah perbaikan & peningkatan +• Atasi kerentanan pengimporan pengaturan & ubah ke JSON +• Perbaikan unduhan +• Pangkas teks pencarian diff --git a/fastlane/metadata/android/it/changelogs/997.txt b/fastlane/metadata/android/it/changelogs/997.txt new file mode 100644 index 0000000000..f888551280 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/997.txt @@ -0,0 +1,17 @@ +Novità +• Risposte ai commenti +• Riordinamento playlist +• Mostra descrizione e durata playlist +• Consenti ripristino impostazioni + +Migliorie +• [Android 13+] Ripristina azioni di notifica personalizzate +• Richiedi consenso per ricerca aggiornamenti +• Notifiche avvio/pausa durante il buffer +• Riordinate alcune impostazioni + +Correzioni +• [YouTube] I commenti non si caricavano, altre correzioni +• Vulnerabilità in importa impostazioni e passaggio a JSON +• Correzioni sui download +• Trim delle ricerche diff --git a/fastlane/metadata/android/kn-IN/changelogs/830.txt b/fastlane/metadata/android/kn-IN/changelogs/830.txt new file mode 100644 index 0000000000..266c384e26 --- /dev/null +++ b/fastlane/metadata/android/kn-IN/changelogs/830.txt @@ -0,0 +1 @@ +SoundCloud ಸಮಸ್ಯೆಗಳನ್ನು ಸರಿಪಡಿಸಲು SoundCloud client_id ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ. diff --git a/fastlane/metadata/android/kn-IN/changelogs/850.txt b/fastlane/metadata/android/kn-IN/changelogs/850.txt new file mode 100644 index 0000000000..ca21e74722 --- /dev/null +++ b/fastlane/metadata/android/kn-IN/changelogs/850.txt @@ -0,0 +1 @@ +ಈ ಬಿಡುಗಡೆಯ ಯೂಟ್ಯೂಬ್ ವೆಬ್‌ಸೈಟ್ ಆವೃತ್ತಿಯನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ. ಹಳೆಯ ವೆಬ್‌ಸೈಟ್ ಆವೃತ್ತಿಯು ಮಾರ್ಚ್‌ನಲ್ಲಿ ಸ್ಥಗಿತಗೊಳ್ಳಲಿದೆ ಮತ್ತು ಆದ್ದರಿಂದ ನೀವು ನ್ಯೂಪೈಪ್ ಅನ್ನು ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ. diff --git a/fastlane/metadata/android/kn-IN/full_description.txt b/fastlane/metadata/android/kn-IN/full_description.txt new file mode 100644 index 0000000000..58a5e25507 --- /dev/null +++ b/fastlane/metadata/android/kn-IN/full_description.txt @@ -0,0 +1 @@ +ನ್ಯೂಪೈಪ್ ಯಾವುದೇ ಗೂಗಲ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಲೈಬ್ರರಿಗಳನ್ನು ಅಥವಾ ಯೂಟ್ಯೂಬ್‌ ಏಪಿಐ ಅನ್ನು ಬಳಸುವುದಿಲ್ಲ. ಇದು ಅಗತ್ಯವಿರುವ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ವೆಬ್‌ಸೈಟ್ ಅನ್ನು ಮಾತ್ರ ಪಾರ್ಸ್ ಮಾಡುತ್ತದೆ. ಆದ್ದರಿಂದ ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಗೂಗಲ್ ಸೇವೆಗಳನ್ನು ಸ್ಥಾಪಿಸದೆಯೇ ಸಾಧನಗಳಲ್ಲಿ ಬಳಸಬಹುದು. ಅಲ್ಲದೆ, ನ್ಯೂಪೈಪ್ ಅನ್ನು ಬಳಸಲು ನಿಮಗೆ ಯೂಟ್ಯೂಬ್ ಖಾತೆಯ ಅಗತ್ಯವಿಲ್ಲ ಮತ್ತು ಅದು FLOSS ಆಗಿದೆ. diff --git a/fastlane/metadata/android/kn-IN/short_description.txt b/fastlane/metadata/android/kn-IN/short_description.txt new file mode 100644 index 0000000000..e8caefb88f --- /dev/null +++ b/fastlane/metadata/android/kn-IN/short_description.txt @@ -0,0 +1 @@ +ಆಂಡ್ರಾಯ್ಡ್‌ಗಾಗಿ ತಯಾರಿಸಿರುವ ಯೂಟ್ಯೂಬ್ ಆಧಾರಿತ ಉಚಿತ ಕಿರುತಂತ್ರಾಂಶ. diff --git a/fastlane/metadata/android/pa/changelogs/997.txt b/fastlane/metadata/android/pa/changelogs/997.txt new file mode 100644 index 0000000000..8e0cb85f20 --- /dev/null +++ b/fastlane/metadata/android/pa/changelogs/997.txt @@ -0,0 +1,17 @@ +ਨਵਾਂ +• ਟਿੱਪਣੀਆਂ ਦੇ ਜਵਾਬ ਸ਼ਾਮਿਲ ਕਰੋ +• ਪਲੇਲਿਸਟਸ ਨੂੰ ਮੁੜ ਕ੍ਰਮਬੱਧ ਕਰਨ ਦਿਓ +• ਪਲੇਲਿਸਟ ਵਰਣਨ ਅਤੇ ਮਿਆਦ ਦਿਖਾਓ +• ਸੈਟਿੰਗਾਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨ ਦਿਓ + +ਸੁਧਾਰ +• [ਐਂਡਰੌਇਡ 13+] ਕਸਟਮ ਨੋਟੀਫਿਕੇਸ਼ਨ ਕਿਰਿਆਵਾਂ ਨੂੰ ਮੁੜ ਲਾਗੂ ਕਰੋ +• ਅੱਪਡੇਟ ਜਾਂਚ ਲਈ ਸਹਿਮਤੀ ਦੀ ਬੇਨਤੀ ਕਰੋ +• ਬਫਰਿੰਗ ਦੌਰਾਨ ਨੋਟੀਫਿਕੇਸ਼ਨ ਤੋਂ ਚਲਾਉਣ/ਰੋਕਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ +• ਕੁਝ ਸੈਟਿੰਗਾਂ ਨੂੰ ਮੁੜ ਕ੍ਰਮਬੱਧ ਕਰੋ + +ਠੀਕ ਕੀਤਾ +• [ਯੂਟਿਊਬ] ਲੋਡ ਨਾ ਹੋਣ ਵਾਲੀਆਂ ਟਿੱਪਣੀਆਂ ਨੂੰ ਠੀਕ ਕਰੋ, ਨਾਲ ਹੀ ਹੋਰ ਫਿਕਸ ਅਤੇ ਸੁਧਾਰ +• ਸੈਟਿੰਗਾਂ ਆਯਾਤ ਕਰਨ ਵਿੱਚ ਕਮਜ਼ੋਰੀ ਨੂੰ ਹੱਲ ਕਰੋ ਅਤੇ JSON 'ਤੇ ਸਵਿੱਚ ਕਰੋ +• ਕਈ ਡਾਊਨਲੋਡ ਫਿਕਸ +• ਖੋਜ ਟੈਕਸਟ ਨੂੰ ਟਰਿੱਮ ਕਰੋ diff --git a/fastlane/metadata/android/pl/changelogs/997.txt b/fastlane/metadata/android/pl/changelogs/997.txt new file mode 100644 index 0000000000..24a300ec91 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/997.txt @@ -0,0 +1,17 @@ +Nowe +• Odpowiadanie na komentarze +• Zmiana kolejności playlist +• Pokazyw. opisu i czasu trwania playlist +• Reset ustawień + +Ulepszone +• [Android 13+] Przywrócono niestand. akcje powiadomień +• Prośba o zgodę na sprawdzanie aktualizacji +• Odtw./wstrzym. powiadomień podczas bufor. +• Zmiana kolejność niektórych ustawień + +Naprawione +• [YouTube] Nieładujące się komentarze i inne poprawki i ulepszenia +• Luka w imporcie ustawień i przełączaniu na JSON +• Różne poprawki pobierania +• Przycięto wyszuk. tekst diff --git a/fastlane/metadata/android/pt-PT/changelogs/996.txt b/fastlane/metadata/android/pt-PT/changelogs/996.txt new file mode 100644 index 0000000000..230e76b54a --- /dev/null +++ b/fastlane/metadata/android/pt-PT/changelogs/996.txt @@ -0,0 +1,2 @@ +Um NullPointerException foi corrigido ao abrir um canal / conferência em media.ccc.de. +O Grinch tentou estragar o nosso presente de Natal para si, mas corrigimo-lo. diff --git a/fastlane/metadata/android/pt-PT/changelogs/997.txt b/fastlane/metadata/android/pt-PT/changelogs/997.txt new file mode 100644 index 0000000000..3e9da2ade7 --- /dev/null +++ b/fastlane/metadata/android/pt-PT/changelogs/997.txt @@ -0,0 +1,16 @@ +Novo +• Adicionar respostas de comentários +• Permitir reordenar listas de reprodução +... + +Melhorado +• [Android 13+] Restaurar ações de notificação personalizadas +• Solicitar consentimento para verificação de atualização +• Permitir reprodução/pausa de notificação durante o buffer +... + +Fixado +• [Tube] Corrigir comentários que não carregam, além de outras correções e melhorias +• Resolver vulnerabilidade nas configurações importação e mudar para JSON +• Várias correções de descargas +... diff --git a/fastlane/metadata/android/pt/changelogs/997.txt b/fastlane/metadata/android/pt/changelogs/997.txt new file mode 100644 index 0000000000..3e9da2ade7 --- /dev/null +++ b/fastlane/metadata/android/pt/changelogs/997.txt @@ -0,0 +1,16 @@ +Novo +• Adicionar respostas de comentários +• Permitir reordenar listas de reprodução +... + +Melhorado +• [Android 13+] Restaurar ações de notificação personalizadas +• Solicitar consentimento para verificação de atualização +• Permitir reprodução/pausa de notificação durante o buffer +... + +Fixado +• [Tube] Corrigir comentários que não carregam, além de outras correções e melhorias +• Resolver vulnerabilidade nas configurações importação e mudar para JSON +• Várias correções de descargas +... diff --git a/fastlane/metadata/android/sk/changelogs/997.txt b/fastlane/metadata/android/sk/changelogs/997.txt new file mode 100644 index 0000000000..98bd604bb8 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/997.txt @@ -0,0 +1,17 @@ +Nové +• Pridané odpovede na komentáre +• Umožnená zmena poradia zoznamov skladieb +• Zobrazenie popisu a trvania playlistu +• Umožnené resetovanie nastavení + +Vylepšené +• [Android 13+] Obnovenie vlastných akcií upozornenia +• Vyžiadanie súhlasu na kontrolu aktualizácie +• Povolenie oznámenia prehrávania/pozastavenia počas načítania +• Zmena poradia niektorých nastavení + +Opravené +• [YouTube] Oprava nenačítania komentárov, ďalšie opravy a vylepšenia +• Vyriešenie zraniteľnosti pri importe nastavení a prepnutie na JSON +• Rôzne opravy sťahovania +• Orezanie textu vyhľadávania diff --git a/fastlane/metadata/android/sv/changelogs/997.txt b/fastlane/metadata/android/sv/changelogs/997.txt new file mode 100644 index 0000000000..f5c18754dc --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/997.txt @@ -0,0 +1,17 @@ +Nytt +• La till kommentars svar +• Tillåt omordnande av spellistor +• Visa spellistors beskrivning och varaktighet +• Tillåt återställning av inställningar + +Förbättrat +• [Android 13+] Återställ anpassade aviserings åtgärder +• Begär samtycke för uppdateringskontroll +• Tillåt spela/pausa från avisering under buffring +• Omordnade några inställningar + +Fixat +• [YouTube] Fixade kommentarer som inte laddas +• Lös sårbarhet i inställnings import och byt till JSON +• Olika hämtnings fixar +• Trimma sök text diff --git a/fastlane/metadata/android/uk/changelogs/997.txt b/fastlane/metadata/android/uk/changelogs/997.txt new file mode 100644 index 0000000000..4c591d2781 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/997.txt @@ -0,0 +1,17 @@ +Нове +• Додано коментарі відповідей +• Дозвіл перевпор. плейлістів +• Показ опису й тривалості плейлістів +• Дозвіл скидати налаштування + +Удосконалено +• [Android 13+] Відновлено власні дії сповіщень +• Запит згоди на пошук оновлень +• Дозвіл відтвор./паузи зі сповіщень під час буферизації +• Перевпор. деякі налаштування + +Виправлено +• [YouTube] Усунуто незавантаж. коментарів та інші виправлення +• Усунуто вразливість імпорту налаштув. і переходу на JSON +• Виправлення завантажень +• Обрізання тексту пошуку diff --git a/fastlane/metadata/android/vi/changelogs/70.txt b/fastlane/metadata/android/vi/changelogs/70.txt new file mode 100644 index 0000000000..7e52b52871 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/70.txt @@ -0,0 +1,25 @@ +LƯU Ý: Phiên bản này có thể là một lỗi nghiêm trọng, giống như phiên bản trước. Tuy nhiên do đã tắt hoàn toàn kể từ ngày 17. một phiên bản bị hỏng còn tốt hơn là không có phiên bản nào. Phải không? ¯\_(ツ)_/¯ + +### Cải tiến +* bây giờ có thể mở các tệp đã tải xuống bằng một cú nhấp chuột #1879 +* bỏ hỗ trợ cho android 4.1 - 4.3 #1884 +* xóa trình phát cũ #1884 +* xóa luồng khỏi hàng phát hiện tại bằng cách vuốt chúng sang phải #1915 +* xóa luồng được xếp hàng tự động khi luồng mới được xếp hàng thủ công #1878 +* Xử lý hậu kỳ để tải xuống và triển khai các tính năng còn thiếu #1759 của @kapodamy +* Cơ sở hạ tầng hậu xử lý +* Xử lý lỗi thích hợp "cơ sở hạ tầng" (dành cho người tải xuống) +* Xếp hàng thay vì tải xuống nhiều lần +* Di chuyển các lượt tải xuống đang chờ xử lý nối tiếp (tệp `.giga`) sang dữ liệu ứng dụng +* Triển khai thử tải xuống tối đa +* Tạm dừng tải xuống đa luồng thích hợp +* Dừng tải xuống khi chuyển sang mạng di động (không bao giờ hoạt động, xem điểm thứ 2) +* Lưu số lượng chủ đề cho lần tải tiếp theo +* Đã sửa rất nhiều lỗi không mạch lạc + +### Đã sửa +* Khắc phục sự cố với độ phân giải mặc định được đặt thành độ phân giải dữ liệu di động tốt nhất và hạn chế #1835 +* Đã sửa lỗi trình phát bật lên #1874 +* NPE khi cố mở trình phát nền #1901 +* Sửa lỗi chèn luồng mới khi bật xếp hàng tự động #1878 +* Đã khắc phục sự cố tắt máy giải mã diff --git a/fastlane/metadata/android/vi/changelogs/985.txt b/fastlane/metadata/android/vi/changelogs/985.txt new file mode 100644 index 0000000000..d2086b62c1 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/985.txt @@ -0,0 +1 @@ +Đã sửa lỗi YouTube không phát bất kỳ luồng nào diff --git a/fastlane/metadata/android/vi/changelogs/997.txt b/fastlane/metadata/android/vi/changelogs/997.txt new file mode 100644 index 0000000000..d143823d34 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/997.txt @@ -0,0 +1,17 @@ +Mới + • Thêm câu trả lời nhận xét + • Cho phép sắp xếp lại danh sách phát + • Hiển thị mô tả và thời lượng danh sách phát + • Cho phép đặt lại cài đặt + + Cải thiện + • [Android 13+] Khôi phục các tác vụ thông báo tùy chỉnh + • Yêu cầu sự đồng ý để kiểm tra cập nhật + • Cho phép phát/tạm dừng thông báo trong khi lưu vào bộ đệm + • Sắp xếp lại một số cài đặt + + Đã sửa + • [YouTube] Khắc phục lỗi không tải được bình luận, cùng với các bản sửa lỗi và cải tiến khác + • Giải quyết lỗ hổng trong cài đặt nhập và chuyển sang JSON + • Nhiều bản sửa lỗi tải xuống khác nhau + • Cắt bớt văn bản tìm kiếm diff --git a/fastlane/metadata/android/zh-Hant/changelogs/997.txt b/fastlane/metadata/android/zh-Hant/changelogs/997.txt new file mode 100644 index 0000000000..e5df621ab6 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/997.txt @@ -0,0 +1,17 @@ +新增 +• 顯示留言回覆 +• 允許重新排序播放清單 +• 顯示播放清單描述與總時長 +• 允許重設所有設定 + +改進 +• [Android 13+] 還原自訂通知動作 +• 更新檢查徵求同意 +• 緩衝時允許自通知中播放/暫停 +• 重新排序部分設定 + +修正 +• [YouTube] 修正留言未能載入,以及其他修正與改進 +• 解決設定匯入的漏洞並改用 JSON +• 若干下載修正 +• 修剪搜尋文字 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/997.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/997.txt new file mode 100644 index 0000000000..009bd31fc4 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/997.txt @@ -0,0 +1,17 @@ +新嘢 +• 留言睇埋回覆 +• 播放清單有得執排位 +• 騷埋播放清單描述同總片長 +• 設定有得推倒重來 + +進步 +• [Android 13+] 自訂通知動作回歸 +• 徵得同意至睇過有冇更新 +• 緩衝緊通知都照撳得播放/暫停 +• 一啲設定執過位 + +修正 +• [YouTube] 執返掂啲留言 lo 唔到,同埋其他執漏同進步 +• 解決設定匯入漏洞兼轉會用 JSON +• 若干下載執漏 +• 搵嘢飛起頭尾啲空格 From caa3812e13d888f31b23487505462eea85f12e96 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 18:05:31 +0200 Subject: [PATCH 20/23] Ignore all errors when getting free storage space It's not a critical check that needs to be perfomed, so in case something does not work on some device/version, let's just ignore the error. --- .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index 8dd8192939..3f6bf37eaa 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -11,7 +11,6 @@ import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; -import android.system.ErrnoException; import android.system.Os; import android.system.StructStatVfs; import android.util.Log; @@ -207,7 +206,9 @@ public long getFreeStorageSpace() { // this is the same formula used inside the FsStat class return stat.f_bavail * stat.f_frsize; - } catch (final IOException | ErrnoException e) { + } catch (final Throwable e) { + // ignore any error + Log.e(TAG, "Could not get free storage space", e); return Long.MAX_VALUE; } } From b8daf16b920e90ed98d3a06b4c6ddf30a21171f4 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 18:39:56 +0200 Subject: [PATCH 21/23] Update app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java Co-authored-by: Tobi --- .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index 3f6bf37eaa..bb47a4b911 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -42,7 +42,10 @@ public class StoredDirectoryHelper { private Path ioTree; private DocumentFile docTree; - // will be `null` for non-SAF files, i.e. files that use `ioTree` + /** + * Context is `null` for non-SAF files, i.e. files that use `ioTree`. + */ + @Nullable private Context context; private final String tag; From 83ca6b9468e34a1bf88df2e61a2f7c4493ca60eb Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 19:25:13 +0200 Subject: [PATCH 22/23] Update NewPipeExtractor to v0.24.0 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1f3d1f7591..28a2081951 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:fbe9e6223aceac8d6f6b352afaed4cb61aed1c79' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.0' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ From 9828586762997ef1d0ada61cf8a8cd42c0f18800 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 20:16:04 +0200 Subject: [PATCH 23/23] Fix indentation for ktlint --- .../schabi/newpipe/settings/export/ImportExportManager.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 9a0d842a47..93c1bfb810 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -75,9 +75,9 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { */ fun extractDb(file: StoredFileHelper): Boolean { val success = ZipHelper.extractFileFromZip( - file, - BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path, + file, + BackupFileLocator.FILE_NAME_DB, + fileLocator.db.path, ) if (success) {