Refactor zip import/export using Path

This commit is contained in:
Isira Seneviratne 2025-07-07 07:52:54 +05:30 committed by Aayush Gupta
parent 668af4fc3e
commit 223b240299
6 changed files with 67 additions and 86 deletions

View file

@ -35,7 +35,6 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
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;
@ -70,13 +69,12 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
@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));
final var dbDir = Objects.requireNonNull(ContextCompat.getDataDir(requireContext()))
.toPath();
manager = new ImportExportManager(new BackupFileLocator(dbDir));
importExportDataPathKey = getString(R.string.import_export_data_path);
addPreferencesFromResourceRegistry();
final Preference importDataPreference = requirePreference(R.string.import_data);
@ -204,9 +202,7 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
}
try {
if (!manager.ensureDbDirectoryExists()) {
throw new IOException("Could not create databases dir");
}
manager.ensureDbDirectoryExists();
// replace the current database
if (!manager.extractDb(file)) {

View file

@ -1,11 +1,12 @@
package org.schabi.newpipe.settings.export
import java.io.File
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(homeDir: Path) {
companion object {
const val FILE_NAME_DB = "newpipe.db"
@ -17,13 +18,9 @@ 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 dbDir = homeDir / "databases"
val db = homeDir / FILE_NAME_DB
val dbJournal = homeDir / "$FILE_NAME_DB-journal"
val dbShm = dbDir / "$FILE_NAME_DB-shm"
val dbWal = dbDir / "$FILE_NAME_DB-wal"
}

View file

@ -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.createDirectories
import kotlin.io.path.deleteExisting
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.dbDir.createDirectories()
}
/**
@ -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.deleteExisting()
fileLocator.dbWal.deleteExisting()
fileLocator.dbShm.deleteExisting()
}
return success

View file

@ -6,12 +6,12 @@ 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.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;
@ -55,17 +55,17 @@ public final class 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);
}
}
@ -113,33 +113,18 @@ public final class ZipHelper {
}
/**
* 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));
}
/**

View file

@ -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())
}
}

View file

@ -4,8 +4,14 @@ import android.content.SharedPreferences
import com.grack.nanojson.JsonParser
import java.io.File
import java.io.ObjectInputStream
import java.nio.file.Files
import java.util.zip.ZipFile
import kotlin.io.path.Path
import kotlin.io.path.createTempDirectory
import kotlin.io.path.createTempFile
import kotlin.io.path.deleteIfExists
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 +52,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 = Path(classloader.getResource("settings/newpipe.db")!!.file)
`when`(fileLocator.db).thenReturn(db)
val expectedPreferences = mapOf("such pref" to "much wow")
@ -81,8 +87,8 @@ class ImportExportManagerTest {
@Test
fun `Ensuring db directory existence must work`() {
val dir = Files.createTempDirectory("newpipe_").toFile()
Assume.assumeTrue(dir.delete())
val dir = createTempDirectory("newpipe_")
Assume.assumeTrue(dir.deleteIfExists())
`when`(fileLocator.dbDir).thenReturn(dir)
ImportExportManager(fileLocator).ensureDbDirectoryExists()
@ -91,7 +97,7 @@ class ImportExportManagerTest {
@Test
fun `Ensuring db directory existence must work when the directory already exists`() {
val dir = Files.createTempDirectory("newpipe_").toFile()
val dir = createTempDirectory("newpipe_")
`when`(fileLocator.dbDir).thenReturn(dir)
ImportExportManager(fileLocator).ensureDbDirectoryExists()
@ -100,10 +106,10 @@ class ImportExportManagerTest {
@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 +123,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 +142,7 @@ class ImportExportManagerTest {
assertTrue(dbJournal.exists())
assertTrue(dbWal.exists())
assertTrue(dbShm.exists())
assertEquals(0, Files.size(db.toPath()))
assertEquals(0, db.fileSize())
}
@Test