2
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-08-22 10:09:39 +00:00

Merge pull request #12415 from Isira-Seneviratne/Zip-migrate-Path

Migrate zip import/export to use Path
This commit is contained in:
Isira Seneviratne 2025-07-12 06:04:31 +05:30 committed by GitHub
commit 183cc580fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 107 deletions

View File

@ -17,7 +17,6 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -35,12 +34,10 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ZipHelper; import org.schabi.newpipe.util.ZipHelper;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class BackupRestoreSettingsFragment extends BasePreferenceFragment { public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
@ -61,13 +58,12 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
@Override @Override
public void onCreatePreferences(@Nullable final Bundle savedInstanceState, public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
@Nullable final String rootKey) { @Nullable final String rootKey) {
final File homeDir = ContextCompat.getDataDir(requireContext()); final var dbDir = requireContext().getDatabasePath(BackupFileLocator.FILE_NAME_DB).toPath()
Objects.requireNonNull(homeDir); .getParent();
manager = new ImportExportManager(new BackupFileLocator(homeDir)); manager = new ImportExportManager(new BackupFileLocator(dbDir));
importExportDataPathKey = getString(R.string.import_export_data_path); importExportDataPathKey = getString(R.string.import_export_data_path);
addPreferencesFromResourceRegistry(); addPreferencesFromResourceRegistry();
final Preference importDataPreference = requirePreference(R.string.import_data); final Preference importDataPreference = requirePreference(R.string.import_data);
@ -183,9 +179,7 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
} }
try { try {
if (!manager.ensureDbDirectoryExists()) { manager.ensureDbDirectoryExists();
throw new IOException("Could not create databases dir");
}
// replace the current database // replace the current database
if (!manager.extractDb(file)) { if (!manager.extractDb(file)) {

View File

@ -1,11 +1,12 @@
package org.schabi.newpipe.settings.export 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. * 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 { companion object {
const val FILE_NAME_DB = "newpipe.db" const val FILE_NAME_DB = "newpipe.db"
@Deprecated( @Deprecated(
@ -16,13 +17,9 @@ class BackupFileLocator(private val homeDir: File) {
const val FILE_NAME_JSON_PREFS = "preferences.json" const val FILE_NAME_JSON_PREFS = "preferences.json"
} }
val dbDir by lazy { File(homeDir, "/databases") } val dbDir = homeDir / "databases"
val db = homeDir / FILE_NAME_DB
val db by lazy { File(dbDir, FILE_NAME_DB) } val dbJournal = homeDir / "$FILE_NAME_DB-journal"
val dbShm = dbDir / "$FILE_NAME_DB-shm"
val dbJournal by lazy { File(dbDir, "$FILE_NAME_DB-journal") } val dbWal = dbDir / "$FILE_NAME_DB-wal"
val dbShm by lazy { File(dbDir, "$FILE_NAME_DB-shm") }
val dbWal by lazy { File(dbDir, "$FILE_NAME_DB-wal") }
} }

View File

@ -12,6 +12,8 @@ import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
class ImportExportManager(private val fileLocator: BackupFileLocator) { class ImportExportManager(private val fileLocator: BackupFileLocator) {
companion object { companion object {
@ -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 // previous file size, the file will retain part of the previous content and be corrupted
ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip -> ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip ->
// add the database // add the database
ZipHelper.addFileToZip( val name = BackupFileLocator.FILE_NAME_DB
outZip, ZipHelper.addFileToZip(outZip, name, fileLocator.db)
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
)
// add the legacy vulnerable serialized preferences (will be removed in the future) // add the legacy vulnerable serialized preferences (will be removed in the future)
ZipHelper.addFileToZip( ZipHelper.addFileToZip(
@ -61,11 +60,10 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) {
/** /**
* Tries to create database directory if it does not exist. * Tries to create database directory if it does not exist.
*
* @return Whether the directory exists afterwards.
*/ */
fun ensureDbDirectoryExists(): Boolean { @Throws(IOException::class)
return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir() 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 * @return true if the database was successfully extracted, false otherwise
*/ */
fun extractDb(file: StoredFileHelper): Boolean { fun extractDb(file: StoredFileHelper): Boolean {
val success = ZipHelper.extractFileFromZip( val name = BackupFileLocator.FILE_NAME_DB
file, val success = ZipHelper.extractFileFromZip(file, name, fileLocator.db)
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
)
if (success) { if (success) {
fileLocator.dbJournal.delete() fileLocator.dbJournal.deleteIfExists()
fileLocator.dbWal.delete() fileLocator.dbWal.deleteIfExists()
fileLocator.dbShm.delete() fileLocator.dbShm.deleteIfExists()
} }
return success return success

View File

@ -6,12 +6,12 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; 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.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -37,9 +37,6 @@ import java.util.zip.ZipOutputStream;
*/ */
public final class ZipHelper { public final class ZipHelper {
private static final int BUFFER_SIZE = 2048;
@FunctionalInterface @FunctionalInterface
public interface InputStreamConsumer { public interface InputStreamConsumer {
void acceptStream(InputStream inputStream) throws IOException; void acceptStream(InputStream inputStream) throws IOException;
@ -55,17 +52,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 outZip the ZipOutputStream where the data should be stored in
* @param nameInZip the path of the file inside the zip * @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, public static void addFileToZip(final ZipOutputStream outZip,
final String nameInZip, final String nameInZip,
final String fileOnDisk) throws IOException { final Path path) throws IOException {
try (FileInputStream fi = new FileInputStream(fileOnDisk)) { try (var inputStream = Files.newInputStream(path)) {
addFileToZip(outZip, nameInZip, fi); addFileToZip(outZip, nameInZip, inputStream);
} }
} }
@ -80,13 +77,13 @@ public final class ZipHelper {
final String nameInZip, final String nameInZip,
final OutputStreamConsumer streamConsumer) throws IOException { final OutputStreamConsumer streamConsumer) throws IOException {
final byte[] bytes; final byte[] bytes;
try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { try (var byteOutput = new ByteArrayOutputStream()) {
streamConsumer.acceptStream(byteOutput); streamConsumer.acceptStream(byteOutput);
bytes = byteOutput.toByteArray(); bytes = byteOutput.toByteArray();
} }
try (ByteArrayInputStream byteInput = new ByteArrayInputStream(bytes)) { try (var byteInput = new ByteArrayInputStream(bytes)) {
ZipHelper.addFileToZip(outZip, nameInZip, byteInput); addFileToZip(outZip, nameInZip, byteInput);
} }
} }
@ -97,49 +94,26 @@ public final class ZipHelper {
* @param nameInZip the path of the file inside the zip * @param nameInZip the path of the file inside the zip
* @param inputStream the content to put inside the file * @param inputStream the content to put inside the file
*/ */
public static void addFileToZip(final ZipOutputStream outZip, private static void addFileToZip(final ZipOutputStream outZip,
final String nameInZip, final String nameInZip,
final InputStream inputStream) throws IOException { final InputStream inputStream) throws IOException {
final byte[] data = new byte[BUFFER_SIZE]; outZip.putNextEntry(new ZipEntry(nameInZip));
try (BufferedInputStream bufferedInputStream = inputStream.transferTo(outZip);
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);
}
}
} }
/** /**
* 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 zipFile the zip file to extract from
* @param nameInZip the path of the file inside the zip * @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 * @return will return true if the file was found within the zip file
*/ */
public static boolean extractFileFromZip(final StoredFileHelper zipFile, public static boolean extractFileFromZip(final StoredFileHelper zipFile,
final String nameInZip, final String nameInZip,
final String fileOnDisk) throws IOException { final Path path) throws IOException {
return extractFileFromZip(zipFile, nameInZip, input -> { return extractFileFromZip(zipFile, nameInZip, input ->
// delete old file first Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING));
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);
}
}
});
} }
/** /**

View File

@ -10,7 +10,9 @@ import org.schabi.newpipe.streams.io.StoredFileHelper
import us.shandian.giga.io.FileStream import us.shandian.giga.io.FileStream
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.file.Files import kotlin.io.path.createTempFile
import kotlin.io.path.exists
import kotlin.io.path.fileSize
class ImportAllCombinationsTest { class ImportAllCombinationsTest {
@ -47,10 +49,10 @@ class ImportAllCombinationsTest {
BackupFileLocator::class.java, BackupFileLocator::class.java,
Mockito.withSettings().stubOnly() Mockito.withSettings().stubOnly()
) )
val db = File.createTempFile("newpipe_", "") val db = createTempFile("newpipe_", "")
val dbJournal = File.createTempFile("newpipe_", "") val dbJournal = createTempFile("newpipe_", "")
val dbWal = File.createTempFile("newpipe_", "") val dbWal = createTempFile("newpipe_", "")
val dbShm = File.createTempFile("newpipe_", "") val dbShm = createTempFile("newpipe_", "")
Mockito.`when`(fileLocator.db).thenReturn(db) Mockito.`when`(fileLocator.db).thenReturn(db)
Mockito.`when`(fileLocator.dbJournal).thenReturn(dbJournal) Mockito.`when`(fileLocator.dbJournal).thenReturn(dbJournal)
Mockito.`when`(fileLocator.dbShm).thenReturn(dbShm) Mockito.`when`(fileLocator.dbShm).thenReturn(dbShm)
@ -62,7 +64,7 @@ class ImportAllCombinationsTest {
Assert.assertFalse(dbJournal.exists()) Assert.assertFalse(dbJournal.exists())
Assert.assertFalse(dbWal.exists()) Assert.assertFalse(dbWal.exists())
Assert.assertFalse(dbShm.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 { } else {
runTest { runTest {
@ -70,7 +72,7 @@ class ImportAllCombinationsTest {
Assert.assertTrue(dbJournal.exists()) Assert.assertTrue(dbJournal.exists())
Assert.assertTrue(dbWal.exists()) Assert.assertTrue(dbWal.exists())
Assert.assertTrue(dbShm.exists()) Assert.assertTrue(dbShm.exists())
Assert.assertEquals(0, Files.size(db.toPath())) Assert.assertEquals(0, db.fileSize())
} }
} }

View File

@ -25,8 +25,14 @@ import org.schabi.newpipe.streams.io.StoredFileHelper
import us.shandian.giga.io.FileStream import us.shandian.giga.io.FileStream
import java.io.File import java.io.File
import java.io.ObjectInputStream import java.io.ObjectInputStream
import java.nio.file.Files
import java.util.zip.ZipFile 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
@RunWith(MockitoJUnitRunner::class) @RunWith(MockitoJUnitRunner::class)
class ImportExportManagerTest { class ImportExportManagerTest {
@ -46,7 +52,7 @@ class ImportExportManagerTest {
@Test @Test
fun `The settings must be exported successfully in the correct format`() { 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) `when`(fileLocator.db).thenReturn(db)
val expectedPreferences = mapOf("such pref" to "much wow") val expectedPreferences = mapOf("such pref" to "much wow")
@ -81,8 +87,8 @@ class ImportExportManagerTest {
@Test @Test
fun `Ensuring db directory existence must work`() { fun `Ensuring db directory existence must work`() {
val dir = Files.createTempDirectory("newpipe_").toFile() val dir = createTempDirectory("newpipe_")
Assume.assumeTrue(dir.delete()) Assume.assumeTrue(dir.deleteIfExists())
`when`(fileLocator.dbDir).thenReturn(dir) `when`(fileLocator.dbDir).thenReturn(dir)
ImportExportManager(fileLocator).ensureDbDirectoryExists() ImportExportManager(fileLocator).ensureDbDirectoryExists()
@ -91,7 +97,7 @@ class ImportExportManagerTest {
@Test @Test
fun `Ensuring db directory existence must work when the directory already exists`() { 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) `when`(fileLocator.dbDir).thenReturn(dir)
ImportExportManager(fileLocator).ensureDbDirectoryExists() ImportExportManager(fileLocator).ensureDbDirectoryExists()
@ -100,10 +106,10 @@ class ImportExportManagerTest {
@Test @Test
fun `The database must be extracted from the zip file`() { fun `The database must be extracted from the zip file`() {
val db = File.createTempFile("newpipe_", "") val db = createTempFile("newpipe_", "")
val dbJournal = File.createTempFile("newpipe_", "") val dbJournal = createTempFile("newpipe_", "")
val dbWal = File.createTempFile("newpipe_", "") val dbWal = createTempFile("newpipe_", "")
val dbShm = File.createTempFile("newpipe_", "") val dbShm = createTempFile("newpipe_", "")
`when`(fileLocator.db).thenReturn(db) `when`(fileLocator.db).thenReturn(db)
`when`(fileLocator.dbJournal).thenReturn(dbJournal) `when`(fileLocator.dbJournal).thenReturn(dbJournal)
`when`(fileLocator.dbShm).thenReturn(dbShm) `when`(fileLocator.dbShm).thenReturn(dbShm)
@ -117,15 +123,15 @@ class ImportExportManagerTest {
assertFalse(dbJournal.exists()) assertFalse(dbJournal.exists())
assertFalse(dbWal.exists()) assertFalse(dbWal.exists())
assertFalse(dbShm.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 @Test
fun `Extracting the database from an empty zip must not work`() { fun `Extracting the database from an empty zip must not work`() {
val db = File.createTempFile("newpipe_", "") val db = createTempFile("newpipe_", "")
val dbJournal = File.createTempFile("newpipe_", "") val dbJournal = createTempFile("newpipe_", "")
val dbWal = File.createTempFile("newpipe_", "") val dbWal = createTempFile("newpipe_", "")
val dbShm = File.createTempFile("newpipe_", "") val dbShm = createTempFile("newpipe_", "")
`when`(fileLocator.db).thenReturn(db) `when`(fileLocator.db).thenReturn(db)
val emptyZip = File(classloader.getResource("settings/nodb_noser_nojson.zip")?.file!!) val emptyZip = File(classloader.getResource("settings/nodb_noser_nojson.zip")?.file!!)
@ -136,7 +142,7 @@ class ImportExportManagerTest {
assertTrue(dbJournal.exists()) assertTrue(dbJournal.exists())
assertTrue(dbWal.exists()) assertTrue(dbWal.exists())
assertTrue(dbShm.exists()) assertTrue(dbShm.exists())
assertEquals(0, Files.size(db.toPath())) assertEquals(0, db.fileSize())
} }
@Test @Test