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 b5d30328844..11c4daedec4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java @@ -16,7 +16,6 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceManager; @@ -35,12 +34,10 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ZipHelper; -import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -70,13 +67,10 @@ public void onAttach(@NonNull final Context context) { @Override public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) { - final File homeDir = ContextCompat.getDataDir(requireContext()); - Objects.requireNonNull(homeDir); - manager = new ImportExportManager(new BackupFileLocator(homeDir)); + manager = new ImportExportManager(new BackupFileLocator(requireContext())); importExportDataPathKey = getString(R.string.import_export_data_path); - addPreferencesFromResourceRegistry(); final Preference importDataPreference = requirePreference(R.string.import_data); @@ -204,9 +198,7 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri } try { - if (!manager.ensureDbDirectoryExists()) { - throw new IOException("Could not create databases dir"); - } + manager.ensureDbDirectoryExists(); // replace the current database if (!manager.extractDb(file)) { 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 index f44d4f3e2d5..97a7e642f1a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt @@ -1,11 +1,13 @@ package org.schabi.newpipe.settings.export -import java.io.File +import android.content.Context +import java.nio.file.Path +import kotlin.io.path.div /** * Locates specific files of NewPipe based on the home directory of the app. */ -class BackupFileLocator(private val homeDir: File) { +class BackupFileLocator(context: Context) { companion object { const val FILE_NAME_DB = "newpipe.db" @@ -17,13 +19,8 @@ class BackupFileLocator(private val homeDir: File) { 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") } + val db: Path = context.getDatabasePath(FILE_NAME_DB).toPath() + val dbJournal: Path = db.resolveSibling("$FILE_NAME_DB-journal") + val dbShm: Path = db.resolveSibling("$FILE_NAME_DB-shm") + val dbWal: Path = db.resolveSibling("$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 83cca2e0b66..b5ab72f517c 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,8 @@ import java.io.FileNotFoundException import java.io.IOException import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream +import kotlin.io.path.createParentDirectories +import kotlin.io.path.deleteIfExists import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper @@ -28,11 +30,8 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { // previous file size, the file will retain part of the previous content and be corrupted ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip -> // add the database - ZipHelper.addFileToZip( - outZip, - BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path - ) + val name = BackupFileLocator.FILE_NAME_DB + ZipHelper.addFileToZip(outZip, name, fileLocator.db) // add the legacy vulnerable serialized preferences (will be removed in the future) ZipHelper.addFileToZip( @@ -61,11 +60,10 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { /** * Tries to create database directory if it does not exist. - * - * @return Whether the directory exists afterwards. */ - fun ensureDbDirectoryExists(): Boolean { - return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir() + @Throws(IOException::class) + fun ensureDbDirectoryExists() { + fileLocator.db.createParentDirectories() } /** @@ -75,16 +73,13 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { * @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 - ) + val name = BackupFileLocator.FILE_NAME_DB + val success = ZipHelper.extractFileFromZip(file, name, fileLocator.db) if (success) { - fileLocator.dbJournal.delete() - fileLocator.dbWal.delete() - fileLocator.dbShm.delete() + fileLocator.dbJournal.deleteIfExists() + fileLocator.dbWal.deleteIfExists() + fileLocator.dbShm.deleteIfExists() } return success 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 b2aebac426b..bccfc7f3874 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java @@ -6,12 +6,12 @@ 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.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -37,9 +37,6 @@ */ public final class ZipHelper { - - private static final int BUFFER_SIZE = 2048; - @FunctionalInterface public interface InputStreamConsumer { void acceptStream(InputStream inputStream) throws IOException; @@ -55,17 +52,17 @@ private ZipHelper() { } /** - * This function helps to create zip files. Caution this will overwrite 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 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 + * @param path the path of the file on the disk that should be added to zip */ 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); + final Path path) throws IOException { + try (var inputStream = Files.newInputStream(path)) { + addFileToZip(outZip, nameInZip, inputStream); } } @@ -80,13 +77,13 @@ public static void addFileToZip(final ZipOutputStream outZip, final String nameInZip, final OutputStreamConsumer streamConsumer) throws IOException { final byte[] bytes; - try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { + try (var byteOutput = new ByteArrayOutputStream()) { streamConsumer.acceptStream(byteOutput); bytes = byteOutput.toByteArray(); } - try (ByteArrayInputStream byteInput = new ByteArrayInputStream(bytes)) { - ZipHelper.addFileToZip(outZip, nameInZip, byteInput); + try (var byteInput = new ByteArrayInputStream(bytes)) { + addFileToZip(outZip, nameInZip, byteInput); } } @@ -97,49 +94,26 @@ public static void addFileToZip(final ZipOutputStream outZip, * @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 (BufferedInputStream bufferedInputStream = - new BufferedInputStream(inputStream, BUFFER_SIZE)) { - final ZipEntry entry = new ZipEntry(nameInZip); - outZip.putNextEntry(entry); - int count; - while ((count = bufferedInputStream.read(data, 0, BUFFER_SIZE)) != -1) { - outZip.write(data, 0, count); - } - } + private static void addFileToZip(final ZipOutputStream outZip, + final String nameInZip, + final InputStream inputStream) throws IOException { + outZip.putNextEntry(new ZipEntry(nameInZip)); + inputStream.transferTo(outZip); } /** - * This will extract data from ZipInputStream. Caution this will overwrite the original file. + * 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 + * @param path 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); - } - } - }); + final Path path) throws IOException { + return extractFileFromZip(zipFile, nameInZip, input -> + Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING)); } /** diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt index c7f53f3aca8..7a90a472125 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt @@ -3,7 +3,9 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences import java.io.File import java.io.IOException -import java.nio.file.Files +import kotlin.io.path.createTempFile +import kotlin.io.path.exists +import kotlin.io.path.fileSize import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -47,10 +49,10 @@ class ImportAllCombinationsTest { 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_", "") + val db = createTempFile("newpipe_", "") + val dbJournal = createTempFile("newpipe_", "") + val dbWal = createTempFile("newpipe_", "") + val dbShm = createTempFile("newpipe_", "") Mockito.`when`(fileLocator.db).thenReturn(db) Mockito.`when`(fileLocator.dbJournal).thenReturn(dbJournal) Mockito.`when`(fileLocator.dbShm).thenReturn(dbShm) @@ -62,7 +64,7 @@ class ImportAllCombinationsTest { Assert.assertFalse(dbJournal.exists()) Assert.assertFalse(dbWal.exists()) Assert.assertFalse(dbShm.exists()) - Assert.assertTrue("database file size is zero", Files.size(db.toPath()) > 0) + Assert.assertTrue("database file size is zero", db.fileSize() > 0) } } else { runTest { @@ -70,7 +72,7 @@ class ImportAllCombinationsTest { Assert.assertTrue(dbJournal.exists()) Assert.assertTrue(dbWal.exists()) Assert.assertTrue(dbShm.exists()) - Assert.assertEquals(0, Files.size(db.toPath())) + Assert.assertEquals(0, db.fileSize()) } } 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 e2ff221344d..482b382375f 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -4,8 +4,15 @@ import android.content.SharedPreferences import com.grack.nanojson.JsonParser import java.io.File import java.io.ObjectInputStream -import java.nio.file.Files +import java.nio.file.Paths import java.util.zip.ZipFile +import kotlin.io.path.createTempDirectory +import kotlin.io.path.createTempFile +import kotlin.io.path.deleteIfExists +import kotlin.io.path.div +import kotlin.io.path.exists +import kotlin.io.path.fileSize +import kotlin.io.path.inputStream import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows @@ -46,7 +53,7 @@ class ImportExportManagerTest { @Test fun `The settings must be exported successfully in the correct format`() { - val db = File(classloader.getResource("settings/newpipe.db")!!.file) + val db = Paths.get(classloader.getResource("settings/newpipe.db")!!.toURI()) `when`(fileLocator.db).thenReturn(db) val expectedPreferences = mapOf("such pref" to "much wow") @@ -81,29 +88,29 @@ class ImportExportManagerTest { @Test fun `Ensuring db directory existence must work`() { - val dir = Files.createTempDirectory("newpipe_").toFile() - Assume.assumeTrue(dir.delete()) - `when`(fileLocator.dbDir).thenReturn(dir) + val path = createTempDirectory("newpipe_") / BackupFileLocator.FILE_NAME_DB + Assume.assumeTrue(path.parent.deleteIfExists()) + `when`(fileLocator.db).thenReturn(path) ImportExportManager(fileLocator).ensureDbDirectoryExists() - assertTrue(dir.exists()) + assertTrue(path.parent.exists()) } @Test fun `Ensuring db directory existence must work when the directory already exists`() { - val dir = Files.createTempDirectory("newpipe_").toFile() - `when`(fileLocator.dbDir).thenReturn(dir) + val path = createTempDirectory("newpipe_") / BackupFileLocator.FILE_NAME_DB + `when`(fileLocator.db).thenReturn(path) ImportExportManager(fileLocator).ensureDbDirectoryExists() - assertTrue(dir.exists()) + assertTrue(path.parent.exists()) } @Test fun `The database must be extracted from the zip file`() { - val db = File.createTempFile("newpipe_", "") - val dbJournal = File.createTempFile("newpipe_", "") - val dbWal = File.createTempFile("newpipe_", "") - val dbShm = File.createTempFile("newpipe_", "") + val db = createTempFile("newpipe_", "") + val dbJournal = createTempFile("newpipe_", "") + val dbWal = createTempFile("newpipe_", "") + val dbShm = createTempFile("newpipe_", "") `when`(fileLocator.db).thenReturn(db) `when`(fileLocator.dbJournal).thenReturn(dbJournal) `when`(fileLocator.dbShm).thenReturn(dbShm) @@ -117,15 +124,15 @@ class ImportExportManagerTest { assertFalse(dbJournal.exists()) assertFalse(dbWal.exists()) assertFalse(dbShm.exists()) - assertTrue("database file size is zero", Files.size(db.toPath()) > 0) + assertTrue("database file size is zero", db.fileSize() > 0) } @Test fun `Extracting the database from an empty zip must not work`() { - val db = File.createTempFile("newpipe_", "") - val dbJournal = File.createTempFile("newpipe_", "") - val dbWal = File.createTempFile("newpipe_", "") - val dbShm = File.createTempFile("newpipe_", "") + val db = createTempFile("newpipe_", "") + val dbJournal = createTempFile("newpipe_", "") + val dbWal = createTempFile("newpipe_", "") + val dbShm = createTempFile("newpipe_", "") `when`(fileLocator.db).thenReturn(db) val emptyZip = File(classloader.getResource("settings/nodb_noser_nojson.zip")?.file!!) @@ -136,7 +143,7 @@ class ImportExportManagerTest { assertTrue(dbJournal.exists()) assertTrue(dbWal.exists()) assertTrue(dbShm.exists()) - assertEquals(0, Files.size(db.toPath())) + assertEquals(0, db.fileSize()) } @Test